001/*
002
003    Licensed to the Apache Software Foundation (ASF) under one
004    or more contributor license agreements.  See the NOTICE file
005    distributed with this work for additional information
006    regarding copyright ownership.  The ASF licenses this file
007    to you under the Apache License, Version 2.0 (the
008    "License"); you may not use this file except in compliance
009    with the License.  You may obtain a copy of the License at
010
011       http://www.apache.org/licenses/LICENSE-2.0
012
013    Unless required by applicable law or agreed to in writing,
014    software distributed under the License is distributed on an
015    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016    KIND, either express or implied.  See the License for the
017    specific language governing permissions and limitations
018    under the License.
019 */
020package org.apache.wiki.ajax;
021
022import org.apache.commons.lang3.StringUtils;
023import org.apache.log4j.Logger;
024import org.apache.wiki.api.core.Engine;
025import org.apache.wiki.api.spi.Wiki;
026import org.apache.wiki.auth.AuthorizationManager;
027import org.apache.wiki.auth.permissions.PagePermission;
028import org.apache.wiki.util.TextUtil;
029
030import javax.servlet.ServletConfig;
031import javax.servlet.ServletException;
032import javax.servlet.http.HttpServlet;
033import javax.servlet.http.HttpServletRequest;
034import javax.servlet.http.HttpServletResponse;
035import java.io.IOException;
036import java.security.Permission;
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.List;
040import java.util.Map;
041import java.util.concurrent.ConcurrentHashMap;
042
043
044/**
045 * This provides a simple ajax servlet for handling /ajax/<ClassName> requests. HttpServlet classes need to be registered using
046 * {@link WikiAjaxDispatcherServlet#registerServlet(WikiAjaxServlet)}
047 *
048 * @since 2.10.2-svn12
049 */
050public class WikiAjaxDispatcherServlet extends HttpServlet {
051
052    private static final long serialVersionUID = 1L;
053    private static final Map< String, AjaxServletContainer > ajaxServlets = new ConcurrentHashMap<>();
054    private static final Logger log = Logger.getLogger( WikiAjaxDispatcherServlet.class.getName() );
055    private String PATH_AJAX = "/ajax/";
056    private Engine m_engine;
057
058    /**
059     * {@inheritDoc}
060     *
061     * This sets the AjaxPath to "/ajax/" as configured in "jspwiki.ajax.url.prefix".
062     * Note: Do not change this without also changing the web.xml file.
063     */
064    @Override
065    public void init( final ServletConfig config ) throws ServletException {
066        super.init( config );
067        m_engine = Wiki.engine().find( config );
068        PATH_AJAX = "/" + TextUtil.getStringProperty( m_engine.getWikiProperties(), "jspwiki.ajax.url.prefix", "ajax" ) + "/";
069        log.info( "WikiAjaxDispatcherServlet initialized." );
070    }
071
072    /**
073     * Register a {@link WikiAjaxServlet} using the servlet mapping as the alias
074     */
075    public static void registerServlet( final WikiAjaxServlet servlet ) {
076        registerServlet( servlet.getServletMapping(), servlet );
077    }
078
079    /**
080     * Register a {@link WikiAjaxServlet} with a specific alias, and default permission {@link PagePermission#VIEW}.
081     */
082    public static void registerServlet( final String alias, final WikiAjaxServlet servlet ) {
083        registerServlet( alias, servlet, PagePermission.VIEW );
084    }
085
086    /**
087     * Regster a {@link WikiAjaxServlet} given an alias, the servlet, and the permission.
088     * This creates a temporary bundle object called {@link WikiAjaxDispatcherServlet.AjaxServletContainer}
089     *
090     * @param alias the uri link to this servlet
091     * @param servlet the servlet being registered
092     * @param perm the permission required to execute the servlet.
093     */
094    public static void registerServlet( final String alias, final WikiAjaxServlet servlet, final Permission perm ) {
095        log.info( "WikiAjaxDispatcherServlet registering " + alias + "=" + servlet + " perm=" + perm );
096        ajaxServlets.put( alias, new AjaxServletContainer( alias, servlet, perm ) );
097    }
098
099    /**
100     * Calls {@link #performAction}
101     */
102    @Override
103    public void doPost( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException {
104        performAction( req, res );
105    }
106
107    /**
108     * Calls {@link #performAction}
109     */
110    @Override
111    public void doGet( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException {
112        performAction( req, res );
113    }
114
115    /**
116     * The main method which get the requestURI "/ajax/<ServletName>", gets the {@link #getServletName} and finds the servlet using
117     * {@link #findServletByName}. It then calls {@link WikiAjaxServlet#service} method.
118     *
119     * @param req the inbound request
120     * @param res the outbound response
121     * @throws IOException if WikiEngine's content encoding is valid
122     * @throws ServletException if no registered servlet can be found
123     */
124    private void performAction( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException {
125        final String path = req.getRequestURI();
126        final String servletName = getServletName( path );
127        if( servletName != null) {
128            final AjaxServletContainer container = findServletContainer( servletName );
129            if( container != null ) {
130                final WikiAjaxServlet servlet = container.servlet;
131                if ( validatePermission( req, container ) ) {
132                    req.setCharacterEncoding( m_engine.getContentEncoding().displayName() );
133                    res.setCharacterEncoding( m_engine.getContentEncoding().displayName() );
134                    final String actionName = AjaxUtil.getNextPathPart( req.getRequestURI(), servlet.getServletMapping() );
135                    log.debug( "actionName=" + actionName );
136                    final String params = req.getParameter( "params" );
137                    log.debug( "params=" + params );
138                    List< String > paramValues = new ArrayList<>();
139                    if( params != null ) {
140                        if( StringUtils.isNotBlank( params ) ) {
141                            paramValues = Arrays.asList( params.trim().split( "," ) );
142                        }
143                    }
144                    servlet.service( req, res, actionName, paramValues );
145                } else {
146                    log.warn( "Servlet container " + container + " not authorised. Permission required." );
147                }
148            } else {
149                log.error( "No registered class for servletName=" + servletName + " in path=" + path );
150                throw new ServletException( "No registered class for servletName=" + servletName );
151            }
152        }
153    }
154
155    /**
156     * Validate the permission of the {@link WikiAjaxServlet} using the {@link AuthorizationManager#checkPermission}
157     *
158     * @param req the servlet request
159     * @param container the container info of the servlet
160     * @return true if permission is valid
161     */
162    private boolean validatePermission( final HttpServletRequest req, final AjaxServletContainer container ) {
163        final Engine e = Wiki.engine().find( req.getSession().getServletContext(), null );
164        boolean valid = false;
165        if( container != null ) {
166            valid = e.getManager( AuthorizationManager.class ).checkPermission( Wiki.session().find( e, req ), container.permission );
167        }
168        return valid;
169    }
170
171    /**
172     * Get the ServletName from the requestURI "/ajax/<ServletName>", using {@link AjaxUtil#getNextPathPart}.
173     *
174     * @param path The requestURI, which must contains "/ajax/<ServletName>" in the path
175     * @return The ServletName for the requestURI, or null
176     * @throws ServletException if the path is invalid
177     */
178    public String getServletName( final String path ) throws ServletException {
179        return AjaxUtil.getNextPathPart( path, PATH_AJAX );
180    }
181
182    /**
183     * Find the {@link AjaxServletContainer} as registered in {@link #registerServlet}.
184     *
185     * @param servletAlias the name of the servlet from {@link #getServletName}
186     * @return The first servlet found, or null.
187     */
188    private AjaxServletContainer findServletContainer( final String servletAlias ) {
189        return ajaxServlets.get( servletAlias );
190    }
191
192    /**
193     * Find the {@link WikiAjaxServlet} given the servletAlias that it was registered with.
194     *
195     * @param servletAlias the value provided to {@link #registerServlet}
196     * @return the {@link WikiAjaxServlet} given the servletAlias that it was registered with.
197     */
198    public WikiAjaxServlet findServletByName( final String servletAlias ) {
199        final AjaxServletContainer container = ajaxServlets.get( servletAlias );
200        if( container != null ) {
201            return container.servlet;
202        }
203        return null;
204    }
205
206    private static class AjaxServletContainer {
207
208        final String alias;
209        final WikiAjaxServlet servlet;
210        final Permission permission;
211
212        public AjaxServletContainer( final String alias, final WikiAjaxServlet servlet, final Permission permission ) {
213            this.alias = alias;
214            this.servlet = servlet;
215            this.permission = permission;
216        }
217
218        @Override
219        public String toString() {
220            return getClass().getSimpleName() + " " + alias + "=" + servlet.getClass().getSimpleName() + " permission=" + permission;
221        }
222
223    }
224
225}