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.plugin.WeblogEntryPlugin;
040import org.apache.wiki.plugin.WeblogPlugin;
041import org.apache.wiki.util.comparators.PageTimeComparator;
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    @SuppressWarnings("unchecked")
195    public Hashtable getRecentPosts( String blogid,
196                                     String username,
197                                     String password,
198                                     int numberOfPosts)
199        throws XmlRpcException
200    {
201        Hashtable<String, Hashtable<String, Object>> result = new Hashtable<String, Hashtable<String, Object>>();
202
203        log.info( "metaWeblog.getRecentPosts() called");
204
205        WikiPage page = m_context.getEngine().getPage( blogid );
206
207        checkPermissions( page, username, password, "view" );
208
209        try
210        {
211            WeblogPlugin plugin = new WeblogPlugin();
212
213            List<WikiPage> changed = plugin.findBlogEntries(m_context.getEngine(),
214                                                            blogid,
215                                                            new Date(0L),
216                                                            new Date());
217
218            Collections.sort( changed, new PageTimeComparator() );
219
220            int items = 0;
221            for( Iterator< WikiPage > i = changed.iterator(); i.hasNext() && items < numberOfPosts; items++ )
222            {
223                WikiPage p = i.next();
224
225                result.put( "entry", makeEntry( p ) );
226            }
227
228        }
229        catch( ProviderException e )
230        {
231            log.error( "Failed to list recent posts", e );
232
233            throw new XmlRpcException( 0, e.getMessage() );
234        }
235
236        return result;
237    }
238
239    /**
240     *  Adds a new post to the blog.
241     *
242     *  @param blogid The id of the blog.
243     *  @param username The username to use
244     *  @param password The password
245     *  @param content As per Metaweblogapi contract
246     *  @param publish This parameter is ignored for JSPWiki.
247     *  @return Returns an empty string
248     *  @throws XmlRpcException If something goes wrong
249     */
250    public String newPost( String blogid,
251                           String username,
252                           String password,
253                           Hashtable content,
254                           boolean publish )
255        throws XmlRpcException
256    {
257        log.info("metaWeblog.newPost() called");
258        WikiEngine engine = m_context.getEngine();
259
260        WikiPage page = engine.getPage( blogid );
261        checkPermissions( page, username, password, "createPages" );
262
263        try
264        {
265            WeblogEntryPlugin plugin = new WeblogEntryPlugin();
266
267            String pageName = plugin.getNewEntryPage( engine, blogid );
268
269            WikiPage entryPage = new WikiPage( engine, pageName );
270            entryPage.setAuthor( username );
271
272            WikiContext context = new WikiContext( engine, entryPage );
273
274            StringBuilder text = new StringBuilder();
275            text.append( "!"+content.get("title") );
276            text.append( "\n\n" );
277            text.append( content.get("description") );
278
279            log.debug("Writing entry: "+text);
280
281            engine.saveText( context, text.toString() );
282        }
283        catch( Exception e )
284        {
285            log.error("Failed to create weblog entry",e);
286            throw new XmlRpcException( 0, "Failed to create weblog entry: "+e.getMessage() );
287        }
288
289        return ""; // FIXME:
290    }
291
292    /**
293     *  Creates an attachment and adds it to the blog.  The attachment
294     *  is created into the main blog page, not the actual post page,
295     *  because we do not know it at this point.
296     *
297     *  @param blogid The id of the blog.
298     *  @param username The username to use
299     *  @param password The password
300     *  @param content As per the MetaweblogAPI contract
301     *  @return As per the MetaweblogAPI contract
302     *  @throws XmlRpcException If something goes wrong
303     *
304     */
305    public Hashtable newMediaObject( String blogid,
306                                     String username,
307                                     String password,
308                                     Hashtable content )
309        throws XmlRpcException
310    {
311        WikiEngine engine = m_context.getEngine();
312        String url = "";
313
314        log.info("metaWeblog.newMediaObject() called");
315
316        WikiPage page = engine.getPage( blogid );
317        checkPermissions( page, username, password, "upload" );
318
319        String name = (String) content.get( "name" );
320        byte[] data = (byte[]) content.get( "bits" );
321
322        AttachmentManager attmgr = engine.getAttachmentManager();
323
324        try
325        {
326            Attachment att = new Attachment( engine, blogid, name );
327            att.setAuthor( username );
328            attmgr.storeAttachment( att, new ByteArrayInputStream( data ) );
329
330            url = engine.getURL( WikiContext.ATTACH, att.getName(), null, true );
331        }
332        catch( Exception e )
333        {
334            log.error( "Failed to upload attachment", e );
335            throw new XmlRpcException( 0, "Failed to upload media object: "+e.getMessage() );
336        }
337
338        Hashtable<String, Object> result = new Hashtable<String, Object>();
339        result.put("url", url);
340
341        return result;
342    }
343
344
345    /**
346     *  Allows the user to edit a post.  It does not allow general
347     *   editability of wiki pages, because of the limitations of the
348     *  metaWeblog API.
349     */
350    boolean editPost( String postid,
351                      String username,
352                      String password,
353                      Hashtable content,
354                      boolean publish )
355        throws XmlRpcException
356    {
357        WikiEngine engine = m_context.getEngine();
358        log.info("metaWeblog.editPost("+postid+") called");
359
360        // FIXME: Is postid correct?  Should we determine it from the page name?
361        WikiPage page = engine.getPage( postid );
362        checkPermissions( page, username, password, "edit" );
363
364        try
365        {
366            WikiPage entryPage = (WikiPage)page.clone();
367            entryPage.setAuthor( username );
368
369            WikiContext context = new WikiContext( engine, entryPage );
370
371            StringBuilder text = new StringBuilder();
372            text.append( "!"+content.get("title") );
373            text.append( "\n\n" );
374            text.append( content.get("description") );
375
376            log.debug("Updating entry: "+text);
377
378            engine.saveText( context, text.toString() );
379        }
380        catch( Exception e )
381        {
382            log.error("Failed to create weblog entry",e);
383            throw new XmlRpcException( 0, "Failed to update weblog entry: "+e.getMessage() );
384        }
385
386        return true;
387    }
388
389    /**
390     *  Gets the text of any page.  The title of the page is parsed
391     *  (if any is provided).
392     */
393    Hashtable getPost( String postid,
394                       String username,
395                       String password )
396        throws XmlRpcException
397    {
398        String wikiname = "FIXME";
399
400        WikiPage page = m_context.getEngine().getPage( wikiname );
401
402        checkPermissions( page, username, password, "view" );
403
404        return makeEntry( page );
405    }
406}