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