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