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