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