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.Context;
023import org.apache.wiki.api.core.ContextEnum;
024import org.apache.wiki.api.core.Engine;
025import org.apache.wiki.api.spi.Wiki;
026import org.apache.xmlrpc.ContextXmlRpcHandler;
027import org.apache.xmlrpc.Invoker;
028import org.apache.xmlrpc.XmlRpcContext;
029import org.apache.xmlrpc.XmlRpcHandlerMapping;
030import org.apache.xmlrpc.XmlRpcServer;
031
032import javax.servlet.ServletConfig;
033import javax.servlet.ServletException;
034import javax.servlet.http.HttpServlet;
035import javax.servlet.http.HttpServletRequest;
036import javax.servlet.http.HttpServletResponse;
037import java.io.IOException;
038import java.io.OutputStream;
039import java.io.OutputStreamWriter;
040import java.io.PrintWriter;
041import java.util.Vector;
042
043/**
044 *  Handles all incoming servlet requests for XML-RPC calls.
045 *  <P>
046 *  Uses two initialization parameters:
047 *  <UL>
048 *  <LI><B>handler</B> : the class which is used to handle the RPC calls.
049 *  <LI><B>prefix</B> : The command prefix for that particular handler.
050 *  </UL>
051 *
052 *  @since 1.6.6
053 */
054public class RPCServlet extends HttpServlet {
055    private static final long serialVersionUID = 3976735878410416180L;
056
057    /** This is what is appended to each command, if the handler has not been specified. */
058    // FIXME: Should this be $default?
059    public static final String XMLRPC_PREFIX = "wiki";
060
061    private Engine m_engine;
062    private XmlRpcServer m_xmlrpcServer = new XmlRpcServer();
063
064    private static final Logger log = Logger.getLogger( RPCServlet.class );
065
066    public void initHandler( final String prefix, final String handlerName ) throws ClassNotFoundException {
067        /*
068        Class handlerClass = Class.forName( handlerName );
069        WikiRPCHandler rpchandler = (WikiRPCHandler) handlerClass.newInstance();
070        rpchandler.initialize( m_engine );
071        m_xmlrpcServer.addHandler( prefix, rpchandler );
072        */
073        final Class< ? > handlerClass = Class.forName( handlerName );
074        m_xmlrpcServer.addHandler( prefix, new LocalHandler(handlerClass) );
075    }
076
077    /**
078     *  Initializes the servlet.
079     */
080    @Override
081    public void init( final ServletConfig config ) throws ServletException {
082        m_engine = Wiki.engine().find( config );
083
084        String handlerName = config.getInitParameter( "handler" );
085        String prefix      = config.getInitParameter( "prefix" );
086
087        if( handlerName == null ) {
088            handlerName = "org.apache.wiki.xmlrpc.RPCHandler";
089        }
090        if( prefix == null ) {
091            prefix = XMLRPC_PREFIX;
092        }
093
094        try {
095            initHandler( prefix, handlerName );
096
097            // FIXME: The metaweblog API should be possible to turn off.
098            initHandler( "metaWeblog", "org.apache.wiki.xmlrpc.MetaWeblogHandler" );
099        } catch( final Exception e ) {
100            log.fatal("Unable to start RPC interface: ", e);
101            throw new ServletException( "No RPC interface", e );
102        }
103    }
104
105    /**
106     *  Handle HTTP POST.  This is an XML-RPC call, and we'll just forward the query to an XmlRpcServer.
107     */
108    @Override
109    public void doPost( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException {
110        log.debug("Received POST to RPCServlet");
111
112        try {
113            final Context ctx = Wiki.context().create( m_engine, request, ContextEnum.PAGE_NONE.getRequestContext() );
114            final XmlRpcContext xmlrpcContext = new WikiXmlRpcContext( m_xmlrpcServer.getHandlerMapping(), ctx );
115            final byte[] result = m_xmlrpcServer.execute( request.getInputStream(), xmlrpcContext );
116
117            //
118            //  I think it's safe to write the output as UTF-8: The XML-RPC standard never creates other than USASCII
119            //  (which is UTF-8 compatible), and our special UTF-8 hack just creates UTF-8.  So in all cases our butt
120            //  should be covered.
121            //
122            response.setContentType( "text/xml; charset=utf-8" );
123            response.setContentLength( result.length );
124
125            final OutputStream out = response.getOutputStream();
126            out.write( result );
127            out.flush();
128
129            // log.debug("Result = "+new String(result) );
130        } catch( final IOException e ) {
131            throw new ServletException("Failed to build RPC result", e);
132        }
133    }
134
135    /**
136     *  Handles HTTP GET.  However, we do not respond to GET requests, other than to show an explanatory text.
137     */
138    @Override
139    public void doGet( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException {
140        log.debug("Received HTTP GET to RPCServlet");
141
142        try {
143            final String msg = "We do not support HTTP GET here.  Sorry.";
144            response.setContentType( "text/plain" );
145            response.setContentLength( msg.length() );
146
147            final PrintWriter writer = new PrintWriter( new OutputStreamWriter( response.getOutputStream() ) );
148
149            writer.println( msg );
150            writer.flush();
151        } catch( final IOException e ) {
152            throw new ServletException("Failed to build RPC result", e);
153        }
154    }
155
156    private static class LocalHandler implements ContextXmlRpcHandler {
157        private Class< ? > m_clazz;
158
159        public LocalHandler( final Class< ? > clazz )
160        {
161            m_clazz = clazz;
162        }
163
164        @Override
165        public Object execute( final String method, final Vector params, final XmlRpcContext context ) throws Exception {
166            final WikiRPCHandler rpchandler = (WikiRPCHandler) m_clazz.newInstance();
167            rpchandler.initialize( ((WikiXmlRpcContext)context).getWikiContext() );
168
169            final Invoker invoker = new Invoker( rpchandler );
170            return invoker.execute( method, params );
171        }
172    }
173
174    private static class WikiXmlRpcContext implements XmlRpcContext {
175
176        private XmlRpcHandlerMapping m_mapping;
177        private Context m_context;
178
179        public WikiXmlRpcContext( final XmlRpcHandlerMapping map, final Context ctx ) {
180            m_mapping = map;
181            m_context = ctx;
182        }
183
184        @Override
185        public XmlRpcHandlerMapping getHandlerMapping()
186        {
187            return m_mapping;
188        }
189
190        @Override
191        public String getPassword() {
192            return null;
193        }
194
195        @Override
196        public String getUserName() {
197            return null;
198        }
199
200        public Context getWikiContext()
201        {
202            return m_context;
203        }
204    }
205
206}