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
056    /**
057     * {@inheritDoc}
058     * 
059     * This sets the AjaxPath to "/ajax/" as configured in "jspwiki.ajax.url.prefix".
060     * Note: Do not change this without also changing the web.xml file.
061     */
062    public void init(ServletConfig config)
063            throws ServletException {
064        super.init(config);
065        WikiEngine e = WikiEngine.getInstance(config);
066        PATH_AJAX = "/"+TextUtil.getStringProperty(e.getWikiProperties(), "jspwiki.ajax.url.prefix", "ajax")+"/";
067        log.info("WikiAjaxDispatcherServlet initialized.");
068    }
069
070    /**
071     * Register a {@link WikiAjaxServlet} using the servlet mapping as the alias
072     */
073    public static void registerServlet(WikiAjaxServlet servlet) {
074        registerServlet(servlet.getServletMapping(),servlet);
075    }
076    
077    /**
078     * Register a {@link WikiAjaxServlet} with a specific alias, and default permission {@link PagePermission.VIEW}.
079     */
080    public static void registerServlet(String alias, WikiAjaxServlet servlet) {
081        registerServlet(alias, servlet, PagePermission.VIEW);
082    }
083    
084    /**
085     * Regster a {@link WikiAjaxServlet} given an alias, the servlet, and the permission.
086     * Thie creates a temporary bundle object called {@link AjaxServletContainer} 
087     * @param alias the uri link to this servlet
088     * @param servlet the servlet being registered
089     * @param perm the permission required to execute the servlet.
090     */
091    public static void registerServlet(String alias, WikiAjaxServlet servlet, Permission perm) {
092        log.info("WikiAjaxDispatcherServlet registering "+alias+"="+servlet+" perm="+perm);
093        ajaxServlets.put(alias,new AjaxServletContainer(alias, servlet, perm));
094    }
095
096    /**
097     * Calls {@link this.performAction}
098     */
099    public void doPost(HttpServletRequest req, HttpServletResponse res)
100            throws IOException, ServletException {
101        performAction(req,res);
102    }
103
104    /**
105     * Calls {@link this.performAction}
106     */
107    public void doGet(HttpServletRequest req, HttpServletResponse res)
108            throws IOException, ServletException {
109        performAction(req,res);
110    }
111
112    /**
113     * The main method which get the requestURI "/ajax/<ServletName>", gets the 
114     * {@link this.getServletName} and finds the servlet using {@link this.findServletByName}. 
115     * It then calls {@link WikiAjaxServlet.service} method.
116     * @param req the inbound request
117     * @param res the outbound response
118     * @throws IOException
119     * @throws ServletException if no registered servlet can be found
120     */
121    private void performAction(HttpServletRequest req, HttpServletResponse res)
122            throws IOException, ServletException {
123        String path = req.getRequestURI();
124        String servletName = getServletName(path);
125        if (servletName!=null) {
126            AjaxServletContainer container = findServletContainer(servletName);
127            if (container != null) {
128                WikiAjaxServlet servlet = container.servlet;
129                if ( validatePermission(req,container) ) {
130                    String actionName = AjaxUtil.getNextPathPart(req.getRequestURI(), servlet.getServletMapping());
131                    log.debug("actionName="+actionName);
132                    Object params = req.getParameter("params");
133                    log.debug("params="+params);
134                    List<String> paramValues = new ArrayList<String>();
135                    if (params instanceof String) {
136                        String paramString = (String)params;
137                        if (StringUtils.isNotBlank(paramString)) {
138                            paramValues = Arrays.asList(paramString.trim().split(","));
139                        }
140                    }
141                    servlet.service(req, res, actionName, paramValues);
142                } else {
143                    log.warn("Servlet container "+container+" not authorised. Permission required.");
144                }
145            } else {
146                log.error("No registered class for servletName=" + servletName + " in path=" + path);
147                throw new ServletException("No registered class for servletName=" + servletName);
148            }
149        }
150    }
151    
152    /**
153     * Validate the permission of the {@link WikiAjaxServlet} using the {@link AuthorizationManager.checkPermission}
154     * 
155     * @param req the servlet request
156     * @param container the container info of the servlet
157     * @return true if permission is valid
158     */
159    private boolean validatePermission(HttpServletRequest req, AjaxServletContainer container) {
160        WikiEngine e = WikiEngine.getInstance(req.getSession().getServletContext(), null);
161        boolean valid = false;
162        if (container != null) {
163            valid = e.getAuthorizationManager().checkPermission(WikiSession.getWikiSession(e, req), container.permission);
164        }
165        return valid;
166    }
167
168    /**
169     * Get the ServletName from the requestURI "/ajax/<ServletName>", using {@link AjaxUtil.getNextPathPath}.
170     * 
171     * @param path The requestURI, which must contains "/ajax/<ServletName>" in the path
172     * @return The ServletName for the requestURI, or null
173     * @throws ServletException if the path is invalid
174     */
175    public String getServletName(String path) throws ServletException {
176        return AjaxUtil.getNextPathPart(path, PATH_AJAX);
177    }
178    
179    /**
180     * Find the {@link AjaxServletContainer} as registered in {@link WikiAjaxDispatcherServlet.registerServlet}.
181     * 
182     * @param servletName the name of the servlet from {@link this.getServletName}
183     * @return The first servlet found, or null.
184     */
185    private AjaxServletContainer findServletContainer(String servletAlias) {
186        return ajaxServlets.get(servletAlias);
187    }
188
189    /**
190     * Find the {@link WikiAjaxServlet} given the servletAlias that it was registered with.
191     * 
192     * @param servletAlias the value provided to {@link this.registerServlet}
193     * @return
194     */
195    public WikiAjaxServlet findServletByName(String servletAlias) {
196        AjaxServletContainer container = ajaxServlets.get(servletAlias);
197        if (container != null) {
198            return container.servlet;
199        }
200        return null;
201    }
202    
203    private static class AjaxServletContainer {
204        String alias;
205        WikiAjaxServlet servlet;
206        Permission permission;
207        
208        public AjaxServletContainer(String alias, WikiAjaxServlet servlet, Permission permission) {
209            this.alias = alias;
210            this.servlet = servlet;
211            this.permission = permission;
212        }
213        
214        public String toString() {
215            return getClass().getSimpleName()+" "+alias+"="+servlet.getClass().getSimpleName()+" permission="+permission;
216        }
217    }
218
219}