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