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