    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki.xmlrpc;
    020
    021import org.apache.logging.log4j.LogManager;
    022import org.apache.logging.log4j.Logger;
    023import org.apache.wiki.api.core.Attachment;
    024import org.apache.wiki.api.core.Context;
    025import org.apache.wiki.api.core.ContextEnum;
    026import org.apache.wiki.api.core.Engine;
    027import org.apache.wiki.api.core.Page;
    028import org.apache.wiki.api.spi.Wiki;
    029import org.apache.wiki.attachment.AttachmentManager;
    030import org.apache.wiki.auth.AuthenticationManager;
    031import org.apache.wiki.auth.AuthorizationManager;
    032import org.apache.wiki.auth.WikiSecurityException;
    033import org.apache.wiki.auth.permissions.PermissionFactory;
    034import org.apache.wiki.pages.PageManager;
    035import org.apache.wiki.pages.PageTimeComparator;
    036import org.apache.wiki.plugin.WeblogEntryPlugin;
    037import org.apache.wiki.plugin.WeblogPlugin;
    038import org.apache.xmlrpc.XmlRpcException;
    039
    040import java.io.ByteArrayInputStream;
    041import java.util.Date;
    042import java.util.Hashtable;
    043import java.util.Iterator;
    044import java.util.List;
    045
    046/**
    047 *  Provides handlers for all RPC routines of the MetaWeblog API.
    048 *  <P>
    049 *  JSPWiki does not support categories, and therefore we always return
    050 *  an empty list for getCategories().  Note also that this API is not
    051 *  suitable for general Wiki editing, since JSPWiki formats the entries
    052 *  in a wiki-compatible manner.  And you cannot choose your page names
    053 *  either.  Since 2.1.94 the entire MetaWeblog API is supported.
    054 *
    055 *  @since 2.1.7
    056 */
    057
    058public class MetaWeblogHandler implements WikiRPCHandler {
    059
    060    private static final Logger LOG = LogManager.getLogger( MetaWeblogHandler.class );
    061
    062    private Context m_context;
    063
    064    /**
    065     *  {@inheritDoc}
    066     */
    067    @Override
    068    public void initialize( final Context context )
    069    {
    070        m_context = context;
    071    }
    072
    073    /**
    074     *  Does a quick check against the current user
    075     *  and does he have permissions to do the stuff
    076     *  that he really wants to.
    077     *  <p>
    078     *  If there is no authentication enabled, returns normally.
    079     *
    080     *  @throws XmlRpcException with the correct error message, if auth fails.
    081     */
    082    private void checkPermissions( final Page page,
    083                                   final String username,
    084                                   final String password,
    085                                   final String permission ) throws XmlRpcException {
    086        try {
    087            final AuthenticationManager amm = m_context.getEngine().getManager( AuthenticationManager.class );
    088            final AuthorizationManager mgr = m_context.getEngine().getManager( AuthorizationManager.class );
    089
    090            if( amm.login( m_context.getWikiSession(), m_context.getHttpRequest(), username, password ) ) {
    091                if( !mgr.checkPermission( m_context.getWikiSession(), PermissionFactory.getPagePermission( page, permission ) ) ) {
    092                    throw new XmlRpcException( 1, "No permission" );
    093                }
    094            } else {
    095                throw new XmlRpcException( 1, "Unknown login" );
    096            }
    097        } catch( final WikiSecurityException e ) {
    098            throw new XmlRpcException( 1, e.getMessage(), e );
    099        }
    100    }
    101
    102    /**
    103     *  JSPWiki does not support categories, therefore JSPWiki always returns an empty list for categories.
    104     *
    105     *  @param blogid The id of the blog.
    106     *  @param username The username to use
    107     *  @param password The password
    108     *  @throws XmlRpcException If something goes wrong
    109     *  @return An empty hashtable.
    110     */
    111    public Hashtable< Object, Object > getCategories( final String blogid, final String username, final String password )  throws XmlRpcException {
    112        final Page page = m_context.getEngine().getManager( PageManager.class ).getPage( blogid );
    113        checkPermissions( page, username, password, "view" );
    114        return new Hashtable<>();
    115    }
    116
    117    private String getURL( final String page ) {
    118        return m_context.getEngine().getURL( ContextEnum.PAGE_VIEW.getRequestContext(), page,null );
    119    }
    120
    121    /**
    122     *  Takes a wiki page, and creates a metaWeblog struct out of it.
    123     *
    124     *  @param page The actual entry page
    125     *  @return A metaWeblog entry struct.
    126     */
    127    private Hashtable< String,Object > makeEntry( final Page page ) {
    128        final Page firstVersion = m_context.getEngine().getManager( PageManager.class ).getPage( page.getName(), 1 );
    129        final Hashtable< String, Object > ht = new Hashtable<>();
    130        ht.put( "dateCreated", firstVersion.getLastModified() );
    131        ht.put( "link", getURL(page.getName() ) );
    132        ht.put( "permaLink", getURL(page.getName() ) );
    133        ht.put( "postid", page.getName() );
    134        ht.put( "userid", page.getAuthor() );
    135
    136        final String pageText = m_context.getEngine().getManager( PageManager.class ).getText(page.getName());
    137        final int firstLine = pageText.indexOf('\n');
    138
    139        String title = "";
    140        if( firstLine > 0 ) {
    141            title = pageText.substring( 0, firstLine );
    142        }
    143
    144        if( title.trim().isEmpty() ) {
    145            title = page.getName();
    146        }
    147
    148        // Remove wiki formatting
    149        while( title.startsWith("!") ) {
    150            title = title.substring(1);
    151        }
    152
    153        ht.put("title", title);
    154        ht.put("description", pageText);
    155
    156        return ht;
    157    }
    158
    159    /**
    160     *  Returns a list of the recent posts to this weblog.
    161     *
    162     *  @param blogid The id of the blog.
    163     *  @param username The username to use
    164     *  @param password The password
    165     *  @param numberOfPosts How many posts to find
    166     *  @throws XmlRpcException If something goes wrong
    167     *  @return As per MetaweblogAPI specification
    168     */
    169    // FIXME: The implementation is suboptimal, as it goes through all of the blog entries.
    170    public Hashtable getRecentPosts( final String blogid, final String username, final String password, final int numberOfPosts ) throws XmlRpcException {
    171        final Hashtable<String, Hashtable<String, Object>> result = new Hashtable<>();
    172        LOG.info( "metaWeblog.getRecentPosts() called");
    173        final Page page = m_context.getEngine().getManager( PageManager.class ).getPage( blogid );
    174        checkPermissions( page, username, password, "view" );
    175
    176        final WeblogPlugin plugin = new WeblogPlugin();
    177        final List< Page > changed = plugin.findBlogEntries( m_context.getEngine(), blogid, new Date( 0L ), new Date() );
    178        changed.sort( new PageTimeComparator() );
    179
    180        int items = 0;
    181        for( final Iterator< Page > i = changed.iterator(); i.hasNext() && items < numberOfPosts; items++ ) {
    182            final Page p = i.next();
    183            result.put( "entry", makeEntry( p ) );
    184        }
    185
    186        return result;
    187    }
    188
    189    /**
    190     *  Adds a new post to the blog.
    191     *
    192     *  @param blogid The id of the blog.
    193     *  @param username The username to use
    194     *  @param password The password
    195     *  @param content As per Metaweblogapi contract
    196     *  @param publish This parameter is ignored for JSPWiki.
    197     *  @return Returns an empty string
    198     *  @throws XmlRpcException If something goes wrong
    199     */
    200    public String newPost( final String blogid,
    201                           final String username,
    202                           final String password,
    203                           final Hashtable< String, Object > content,
    204                           final boolean publish ) throws XmlRpcException {
    205        LOG.info("metaWeblog.newPost() called");
    206        final Engine engine = m_context.getEngine();
    207        final Page page = engine.getManager( PageManager.class ).getPage( blogid );
    208        checkPermissions( page, username, password, "createPages" );
    209
    210        try {
    211            final WeblogEntryPlugin plugin = new WeblogEntryPlugin();
    212            final String pageName = plugin.getNewEntryPage( engine, blogid );
    213            final Page entryPage = Wiki.contents().page( engine, pageName );
    214            entryPage.setAuthor( username );
    215
    216            final Context context = Wiki.context().create( engine, entryPage );
    217            final StringBuilder text = new StringBuilder();
    218            text.append( "!" ).append( content.get( "title" ) );
    219            text.append( "\n\n" );
    220            text.append( content.get("description") );
    221
    222            LOG.debug("Writing entry: "+text);
    223
    224            engine.getManager( PageManager.class ).saveText( context, text.toString() );
    225        } catch( final Exception e ) {
    226            LOG.error("Failed to create weblog entry",e);
    227            throw new XmlRpcException( 0, "Failed to create weblog entry: "+e.getMessage() );
    228        }
    229
    230        return ""; // FIXME:
    231    }
    232
    233    /**
    234     *  Creates an attachment and adds it to the blog.  The attachment
    235     *  is created into the main blog page, not the actual post page,
    236     *  because we do not know it at this point.
    237     *
    238     *  @param blogid The id of the blog.
    239     *  @param username The username to use
    240     *  @param password The password
    241     *  @param content As per the MetaweblogAPI contract
    242     *  @return As per the MetaweblogAPI contract
    243     *  @throws XmlRpcException If something goes wrong
    244     */
    245    public Hashtable< String, Object > newMediaObject( final String blogid,
    246                                                       final String username,
    247                                                       final String password,
    248                                                       final Hashtable< String, Object > content ) throws XmlRpcException {
    249        final Engine engine = m_context.getEngine();
    250        final String url;
    251
    252        LOG.info( "metaWeblog.newMediaObject() called" );
    253
    254        final Page page = engine.getManager( PageManager.class ).getPage( blogid );
    255        checkPermissions( page, username, password, "upload" );
    256
    257        final String name = (String) content.get( "name" );
    258        final byte[] data = (byte[]) content.get( "bits" );
    259
    260        final AttachmentManager attmgr = engine.getManager( AttachmentManager.class );
    261
    262        try {
    263            final Attachment att = Wiki.contents().attachment( engine, blogid, name );
    264            att.setAuthor( username );
    265            attmgr.storeAttachment( att, new ByteArrayInputStream( data ) );
    266
    267            url = engine.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), att.getName(), null );
    268        } catch( final Exception e ) {
    269            LOG.error( "Failed to upload attachment", e );
    270            throw new XmlRpcException( 0, "Failed to upload media object: "+e.getMessage() );
    271        }
    272
    273        final Hashtable< String, Object > result = new Hashtable<>();
    274        result.put("url", url);
    275
    276        return result;
    277    }
    278
    279
    280    /**
    281     *  Allows the user to edit a post.  It does not allow general editability of wiki pages, because of the limitations of the metaWeblog API.
    282     */
    283    boolean editPost( final String postid,
    284                      final String username,
    285                      final String password,
    286                      final Hashtable< String,Object > content,
    287                      final boolean publish ) throws XmlRpcException {
    288        final Engine engine = m_context.getEngine();
    289        LOG.info("metaWeblog.editPost("+postid+") called");
    290
    291        // FIXME: Is postid correct?  Should we determine it from the page name?
    292        final Page page = engine.getManager( PageManager.class ).getPage( postid );
    293        checkPermissions( page, username, password, "edit" );
    294
    295        try {
    296            final Page entryPage = page.clone();
    297            entryPage.setAuthor( username );
    298
    299            final Context context = Wiki.context().create( engine, entryPage );
    300
    301            final StringBuilder text = new StringBuilder();
    302            text.append( "!" ).append( content.get( "title" ) );
    303            text.append( "\n\n" );
    304            text.append( content.get("description") );
    305
    306            LOG.debug("Updating entry: "+text);
    307
    308            engine.getManager( PageManager.class ).saveText( context, text.toString() );
    309        } catch( final Exception e ) {
    310            LOG.error("Failed to create weblog entry",e);
    311            throw new XmlRpcException( 0, "Failed to update weblog entry: "+e.getMessage() );
    312        }
    313
    314        return true;
    315    }
    316
    317    /**
    318     *  Gets the text of any page.  The title of the page is parsed
    319     *  (if any is provided).
    320     */
    321    Hashtable< String, Object > getPost( final String postid, final String username, final String password ) throws XmlRpcException {
    322        final String wikiname = "FIXME";
    323        final Page page = m_context.getEngine().getManager( PageManager.class ).getPage( wikiname );
    324        checkPermissions( page, username, password, "view" );
    325        return makeEntry( page );
    326    }
    327
    328}