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}