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     */
019    package org.apache.wiki.xmlrpc;
020    
021    import java.io.UnsupportedEncodingException;
022    import java.util.Calendar;
023    import java.util.Collection;
024    import java.util.Date;
025    import java.util.Hashtable;
026    import java.util.Iterator;
027    import java.util.Vector;
028    
029    import org.apache.log4j.Logger;
030    import org.apache.wiki.LinkCollector;
031    import org.apache.wiki.WikiContext;
032    import org.apache.wiki.WikiEngine;
033    import org.apache.wiki.WikiPage;
034    import org.apache.wiki.attachment.Attachment;
035    import org.apache.wiki.auth.permissions.PagePermission;
036    import org.apache.wiki.auth.permissions.PermissionFactory;
037    import org.apache.wiki.util.TextUtil;
038    import org.apache.xmlrpc.XmlRpcException;
039    
040    /**
041     *  Provides handlers for all RPC routines.
042     *
043     *  @since 1.6.6
044     */
045    // We could use WikiEngine directly, but because of introspection it would
046    // show just too many methods to be safe.
047    public class RPCHandler
048        extends AbstractRPCHandler
049    {
050        private static Logger log = Logger.getLogger( RPCHandler.class ); 
051    
052        /**
053         *  {@inheritDoc}
054         */
055        public void initialize( WikiContext ctx )
056        {
057            super.initialize( ctx );
058        }
059    
060        /**
061         *  Converts Java string into RPC string.
062         */
063        private String toRPCString( String src )
064        {
065            return TextUtil.urlEncodeUTF8( src );
066        }
067    
068        /**
069         *  Converts RPC string (UTF-8, url encoded) into Java string.
070         */
071        private String fromRPCString( String src )
072        {
073            return TextUtil.urlDecodeUTF8( src );
074        }
075    
076        /**
077         *  Transforms a Java string into UTF-8.
078         */
079        private byte[] toRPCBase64( String src )
080        {
081            try
082            {
083                return src.getBytes("UTF-8");
084            }
085            catch( UnsupportedEncodingException e )
086            {
087                //
088                //  You shouldn't be running JSPWiki on a platform that does not
089                //  use UTF-8.  We revert to platform default, so that the other
090                //  end might have a chance of getting something.
091                //
092                log.fatal("Platform does not support UTF-8, reverting to platform default");
093                return src.getBytes();
094            }
095        }
096    
097        public String getApplicationName()
098        {
099            checkPermission( PagePermission.VIEW );
100            return toRPCString(m_engine.getApplicationName());
101        }
102    
103        public Vector getAllPages()
104        {
105            checkPermission( PagePermission.VIEW );
106            Collection pages = m_engine.getRecentChanges();
107            Vector<String> result = new Vector<String>();
108    
109            for( Iterator i = pages.iterator(); i.hasNext(); )
110            {
111                WikiPage p = (WikiPage) i.next();
112                if( !(p instanceof Attachment) )
113                {
114                    result.add( toRPCString(p.getName()) );
115                }
116            }
117    
118            return result;
119        }
120    
121        /**
122         *  Encodes a single wiki page info into a Hashtable.
123         */
124        protected Hashtable<String,Object> encodeWikiPage( WikiPage page )
125        {
126            Hashtable<String, Object> ht = new Hashtable<String, Object>();
127    
128            ht.put( "name", toRPCString(page.getName()) );
129    
130            Date d = page.getLastModified();
131    
132            //
133            //  Here we reset the DST and TIMEZONE offsets of the
134            //  calendar.  Unfortunately, I haven't thought of a better
135            //  way to ensure that we're getting the proper date
136            //  from the XML-RPC thingy, except to manually adjust the date.
137            //
138    
139            Calendar cal = Calendar.getInstance();
140            cal.setTime( d );
141            cal.add( Calendar.MILLISECOND, 
142                     - (cal.get( Calendar.ZONE_OFFSET ) + 
143                        (cal.getTimeZone().inDaylightTime( d ) ? cal.get( Calendar.DST_OFFSET ) : 0 )) );
144    
145            ht.put( "lastModified", cal.getTime() );
146            ht.put( "version", page.getVersion() );
147    
148            if( page.getAuthor() != null )
149            {
150                ht.put( "author", toRPCString(page.getAuthor()) );
151            }
152    
153            return ht;
154        }
155    
156        public Vector getRecentChanges( Date since )
157        {
158            checkPermission( PagePermission.VIEW );
159            Collection pages = m_engine.getRecentChanges();
160            Vector<Hashtable<String, Object>> result = new Vector<Hashtable<String, Object>>();
161    
162            Calendar cal = Calendar.getInstance();
163            cal.setTime( since );
164    
165            //
166            //  Convert UTC to our time.
167            //
168            cal.add( Calendar.MILLISECOND,
169                     (cal.get( Calendar.ZONE_OFFSET ) +
170                      (cal.getTimeZone().inDaylightTime(since) ? cal.get( Calendar.DST_OFFSET ) : 0 ) ) );
171            since = cal.getTime();
172    
173            for( Iterator i = pages.iterator(); i.hasNext(); )
174            {
175                WikiPage page = (WikiPage)i.next();
176    
177                if( page.getLastModified().after( since ) && !(page instanceof Attachment) )
178                {
179                    result.add( encodeWikiPage( page ) );
180                }
181            }
182    
183            return result;
184        }
185    
186        /**
187         *  Simple helper method, turns the incoming page name into
188         *  normal Java string, then checks page condition.
189         *
190         *  @param pagename Page Name as an RPC string (URL-encoded UTF-8)
191         *  @return Real page name, as Java string.
192         *  @throws XmlRpcException, if there is something wrong with the page.
193         */
194        private String parsePageCheckCondition( String pagename )
195            throws XmlRpcException
196        {
197            pagename = fromRPCString( pagename );
198    
199            if( !m_engine.pageExists(pagename) )
200            {
201                throw new XmlRpcException( ERR_NOPAGE, "No such page '"+pagename+"' found, o master." );
202            }
203    
204            WikiPage p = m_engine.getPage( pagename );
205            
206            checkPermission( PermissionFactory.getPagePermission( p, PagePermission.VIEW_ACTION ) );
207            
208            return pagename;
209        }
210    
211        public Hashtable getPageInfo( String pagename )
212            throws XmlRpcException
213        {
214            pagename = parsePageCheckCondition( pagename );
215            return encodeWikiPage( m_engine.getPage(pagename) );
216        }
217    
218        public Hashtable getPageInfoVersion( String pagename, int version )
219            throws XmlRpcException
220        {
221            pagename = parsePageCheckCondition( pagename );
222    
223            return encodeWikiPage( m_engine.getPage( pagename, version ) );
224        }
225    
226        public byte[] getPage( String pagename )
227            throws XmlRpcException
228        {
229            pagename = parsePageCheckCondition( pagename );
230    
231            String text = m_engine.getPureText( pagename, -1 );
232    
233            return toRPCBase64( text );
234        }
235    
236        public byte[] getPageVersion( String pagename, int version )
237            throws XmlRpcException
238        {
239            pagename = parsePageCheckCondition( pagename );
240    
241            return toRPCBase64( m_engine.getPureText( pagename, version ) );
242        }
243    
244        public byte[] getPageHTML( String pagename )
245            throws XmlRpcException    
246        {
247            pagename = parsePageCheckCondition( pagename );
248    
249            return toRPCBase64( m_engine.getHTML( pagename ) );
250        }
251    
252        public byte[] getPageHTMLVersion( String pagename, int version )
253            throws XmlRpcException
254        {
255            pagename = parsePageCheckCondition( pagename );
256    
257            return toRPCBase64( m_engine.getHTML( pagename, version ) );
258        }
259    
260        public Vector listLinks( String pagename )
261            throws XmlRpcException
262        {
263            pagename = parsePageCheckCondition( pagename );
264    
265            WikiPage page = m_engine.getPage( pagename );
266            String pagedata = m_engine.getPureText( page );
267    
268            LinkCollector localCollector = new LinkCollector();
269            LinkCollector extCollector   = new LinkCollector();
270            LinkCollector attCollector   = new LinkCollector();
271    
272            WikiContext context = new WikiContext( m_engine, page );
273            context.setVariable( WikiEngine.PROP_REFSTYLE, "absolute" );
274    
275            m_engine.textToHTML( context,
276                                 pagedata,
277                                 localCollector,
278                                 extCollector,
279                                 attCollector );
280    
281            Vector<Hashtable<String, String>> result = new Vector<Hashtable<String, String>>();
282    
283            //
284            //  Add local links.
285            //
286            for( Iterator< String > i = localCollector.getLinks().iterator(); i.hasNext(); )
287            {
288                String link = i.next();
289                Hashtable< String, String > ht = new Hashtable<String, String>();
290                ht.put( "page", toRPCString( link ) );
291                ht.put( "type", LINK_LOCAL );
292    
293                //
294                //  FIXME: This is a kludge.  The link format should really be queried
295                //  from the TranslatorReader itself.  Also, the link format should probably
296                //  have information on whether the page exists or not.
297                //
298    
299                //
300                //  FIXME: The current link collector interface is not very good, since
301                //  it causes this.
302                //
303    
304                if( m_engine.pageExists(link) )
305                {
306                    ht.put( "href", context.getURL(WikiContext.VIEW,link) );
307                }
308                else
309                {
310                    ht.put( "href", context.getURL(WikiContext.EDIT,link) );
311                }
312    
313                result.add( ht );
314            }
315    
316            //
317            // Add links to inline attachments
318            //
319            for( Iterator< String > i = attCollector.getLinks().iterator(); i.hasNext(); )
320            {
321                String link = i.next();
322    
323                Hashtable< String, String > ht = new Hashtable< String, String >();
324    
325                ht.put( "page", toRPCString( link ) );
326                ht.put( "type", LINK_LOCAL );
327                ht.put( "href", context.getURL( WikiContext.ATTACH, link ) );
328    
329                result.add( ht );
330            }
331    
332            //
333            // External links don't need to be changed into XML-RPC strings,
334            // simply because URLs are by definition ASCII.
335            //
336    
337            for( Iterator< String > i = extCollector.getLinks().iterator(); i.hasNext(); )
338            {
339                String link = i.next();
340    
341                Hashtable< String, String > ht = new Hashtable< String, String >();
342    
343                ht.put( "page", link );
344                ht.put( "type", LINK_EXTERNAL );
345                ht.put( "href", link );
346    
347                result.add( ht );
348            }
349    
350            return result;
351        }
352    }