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}