001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.jsonwebservice;
016    
017    import com.liferay.portal.kernel.bean.BeanLocator;
018    import com.liferay.portal.kernel.bean.PortalBeanLocatorUtil;
019    import com.liferay.portal.kernel.bean.PortletBeanLocatorUtil;
020    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceAction;
021    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceActionMapping;
022    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceActionsManager;
023    import com.liferay.portal.kernel.log.Log;
024    import com.liferay.portal.kernel.log.LogFactoryUtil;
025    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
026    import com.liferay.portal.kernel.servlet.HttpMethods;
027    import com.liferay.portal.kernel.util.ArrayUtil;
028    import com.liferay.portal.kernel.util.BinarySearch;
029    import com.liferay.portal.kernel.util.CamelCaseUtil;
030    import com.liferay.portal.kernel.util.CharPool;
031    import com.liferay.portal.kernel.util.ContextPathUtil;
032    import com.liferay.portal.kernel.util.GetterUtil;
033    import com.liferay.portal.kernel.util.MethodParameter;
034    import com.liferay.portal.kernel.util.SortedArrayList;
035    import com.liferay.portal.kernel.util.StringPool;
036    import com.liferay.portal.spring.context.PortalContextLoaderListener;
037    import com.liferay.portal.util.PortalUtil;
038    import com.liferay.portal.util.PropsValues;
039    
040    import java.lang.reflect.Method;
041    
042    import java.util.ArrayList;
043    import java.util.Iterator;
044    import java.util.List;
045    import java.util.Map;
046    import java.util.Set;
047    import java.util.TreeSet;
048    
049    import javax.servlet.ServletContext;
050    import javax.servlet.http.HttpServletRequest;
051    import javax.servlet.http.HttpSession;
052    
053    /**
054     * @author Igor Spasic
055     */
056    @DoPrivileged
057    public class JSONWebServiceActionsManagerImpl
058            implements JSONWebServiceActionsManager {
059    
060            @Override
061            public Set<String> getContextPaths() {
062                    Set<String> contextPaths = new TreeSet<String>();
063    
064                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
065                                    _jsonWebServiceActionConfigs) {
066    
067                            String contextPath = jsonWebServiceActionConfig.getContextPath();
068    
069                            contextPaths.add(contextPath);
070                    }
071    
072                    return contextPaths;
073            }
074    
075            @Override
076            public JSONWebServiceAction getJSONWebServiceAction(
077                    HttpServletRequest request) {
078    
079                    String path = GetterUtil.getString(request.getPathInfo());
080    
081                    String method = GetterUtil.getString(request.getMethod());
082    
083                    String parameterPath = null;
084    
085                    JSONRPCRequest jsonRPCRequest = null;
086    
087                    int parameterPathIndex = _getParameterPathIndex(path);
088    
089                    if (parameterPathIndex != -1) {
090                            parameterPath = path.substring(parameterPathIndex);
091    
092                            path = path.substring(0, parameterPathIndex);
093                    }
094                    else {
095                            if (method.equals(HttpMethods.POST) &&
096                                    !PortalUtil.isMultipartRequest(request)) {
097    
098                                    jsonRPCRequest = JSONRPCRequest.detectJSONRPCRequest(request);
099    
100                                    if (jsonRPCRequest != null) {
101                                            path += StringPool.SLASH + jsonRPCRequest.getMethod();
102    
103                                            method = null;
104                                    }
105                            }
106                    }
107    
108                    JSONWebServiceActionParameters jsonWebServiceActionParameters =
109                            new JSONWebServiceActionParameters();
110    
111                    jsonWebServiceActionParameters.collectAll(
112                            request, parameterPath, jsonRPCRequest, null);
113    
114                    String[] paths = _resolvePaths(request, path);
115    
116                    String contextPath = paths[0];
117    
118                    path = paths[1];
119    
120                    if (_log.isDebugEnabled()) {
121                            _log.debug(
122                                    "Request JSON web service action with path " + path +
123                                            " and method " + method + " for /" + contextPath);
124                    }
125    
126                    int jsonWebServiceActionConfigIndex =
127                            _getJSONWebServiceActionConfigIndex(
128                                    contextPath, path, method,
129                                    jsonWebServiceActionParameters.getParameterNames());
130    
131                    if (jsonWebServiceActionConfigIndex == -1) {
132                            throw new RuntimeException(
133                                    "No JSON web service action associated with path " + path +
134                                            " and method " + method + " for /" + contextPath);
135                    }
136    
137                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
138                            _jsonWebServiceActionConfigs.get(jsonWebServiceActionConfigIndex);
139    
140                    return new JSONWebServiceActionImpl(
141                            jsonWebServiceActionConfig, jsonWebServiceActionParameters);
142            }
143    
144            @Override
145            public JSONWebServiceAction getJSONWebServiceAction(
146                    HttpServletRequest request, String path, String method,
147                    Map<String, Object> parameterMap) {
148    
149                    JSONWebServiceActionParameters jsonWebServiceActionParameters =
150                            new JSONWebServiceActionParameters();
151    
152                    jsonWebServiceActionParameters.collectAll(
153                            request, null, null, parameterMap);
154    
155                    String[] parameterNames =
156                            jsonWebServiceActionParameters.getParameterNames();
157    
158                    String[] paths = _resolvePaths(request, path);
159    
160                    String contextPath = paths[0];
161    
162                    path = paths[1];
163    
164                    if (_log.isDebugEnabled()) {
165                            _log.debug(
166                                    "Request JSON web service action with path " + path +
167                                            " and method " + method + " for /" + contextPath);
168                    }
169    
170                    int jsonWebServiceActionConfigIndex =
171                            _getJSONWebServiceActionConfigIndex(
172                                    contextPath, path, method, parameterNames);
173    
174                    if (jsonWebServiceActionConfigIndex == -1) {
175                            throw new RuntimeException(
176                                    "No JSON web service action with path " + path +
177                                            " and method " + method + " for /" + contextPath);
178                    }
179    
180                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
181                            _jsonWebServiceActionConfigs.get(jsonWebServiceActionConfigIndex);
182    
183                    return new JSONWebServiceActionImpl(
184                            jsonWebServiceActionConfig, jsonWebServiceActionParameters);
185            }
186    
187            @Override
188            public JSONWebServiceActionMapping getJSONWebServiceActionMapping(
189                    String signature) {
190    
191                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
192                                    _jsonWebServiceActionConfigs) {
193    
194                            if (signature.equals(jsonWebServiceActionConfig.getSignature())) {
195                                    return jsonWebServiceActionConfig;
196                            }
197                    }
198    
199                    return null;
200            }
201    
202            @Override
203            public List<JSONWebServiceActionMapping> getJSONWebServiceActionMappings(
204                    String contextPath) {
205    
206                    List<JSONWebServiceActionMapping> jsonWebServiceActionMappings =
207                            new ArrayList<JSONWebServiceActionMapping>(
208                                    _jsonWebServiceActionConfigs.size());
209    
210                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
211                                    _jsonWebServiceActionConfigs) {
212    
213                            String jsonWebServiceContextPath =
214                                    jsonWebServiceActionConfig.getContextPath();
215    
216                            if (contextPath.equals(jsonWebServiceContextPath)) {
217                                    jsonWebServiceActionMappings.add(jsonWebServiceActionConfig);
218                            }
219                    }
220    
221                    return jsonWebServiceActionMappings;
222            }
223    
224            @Override
225            public int getJSONWebServiceActionsCount(String contextPath) {
226                    int count = 0;
227    
228                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
229                                    _jsonWebServiceActionConfigs) {
230    
231                            if (contextPath.equals(
232                                            jsonWebServiceActionConfig.getContextPath())) {
233    
234                                    count++;
235                            }
236                    }
237    
238                    return count;
239            }
240    
241            @Override
242            public void registerJSONWebServiceAction(
243                    String contextPath, Class<?> actionClass, Method actionMethod,
244                    String path, String method) {
245    
246                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
247                            new JSONWebServiceActionConfig(
248                                    contextPath, actionClass, actionMethod, path, method);
249    
250                    if (_jsonWebServiceActionConfigs.contains(jsonWebServiceActionConfig)) {
251                            if (_log.isDebugEnabled()) {
252                                    _log.debug(
253                                            "A JSON web service action is already registered at " +
254                                                    path);
255                            }
256    
257                            return;
258                    }
259    
260                    _jsonWebServiceActionConfigs.add(jsonWebServiceActionConfig);
261            }
262    
263            @Override
264            public void registerJSONWebServiceAction(
265                    String contextPath, Object actionObject, Class<?> actionClass,
266                    Method actionMethod, String path, String method) {
267    
268                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
269                            new JSONWebServiceActionConfig(
270                                    contextPath, actionObject, actionClass, actionMethod, path,
271                                    method);
272    
273                    if (_jsonWebServiceActionConfigs.contains(jsonWebServiceActionConfig)) {
274                            if (_log.isWarnEnabled()) {
275                                    _log.warn(
276                                            "A JSON web service action is already registered at " +
277                                                    path);
278                            }
279    
280                            return;
281                    }
282    
283                    _jsonWebServiceActionConfigs.add(jsonWebServiceActionConfig);
284            }
285    
286            @Override
287            public int registerServletContext(ServletContext servletContext) {
288                    String contextPath = ContextPathUtil.getContextPath(servletContext);
289    
290                    int count = registerServletContext(contextPath);
291    
292                    if (count < 0) {
293                            count = registerServletContext(
294                                    servletContext.getServletContextName());
295                    }
296    
297                    return count;
298            }
299    
300            @Override
301            public int registerServletContext(String contextPath) {
302                    BeanLocator beanLocator = null;
303    
304                    if (contextPath.equals(
305                                    PortalContextLoaderListener.getPortalServletContextPath()) ||
306                            contextPath.isEmpty()) {
307    
308                            beanLocator = PortalBeanLocatorUtil.getBeanLocator();
309                    }
310                    else {
311                            String contextName = contextPath;
312    
313                            if (contextName.startsWith(StringPool.SLASH)) {
314                                    contextName = contextName.substring(1);
315                            }
316    
317                            beanLocator = PortletBeanLocatorUtil.getBeanLocator(contextName);
318                    }
319    
320                    if (beanLocator == null) {
321                            if (_log.isInfoEnabled()) {
322                                    _log.info("Bean locator not available for " + contextPath);
323                            }
324    
325                            return -1;
326                    }
327    
328                    JSONWebServiceRegistrator jsonWebServiceRegistrator =
329                            new JSONWebServiceRegistrator();
330    
331                    jsonWebServiceRegistrator.processAllBeans(contextPath, beanLocator);
332    
333                    int count = getJSONWebServiceActionsCount(contextPath);
334    
335                    if (_log.isInfoEnabled()) {
336                            _log.info("Configured " + count + " actions for " + contextPath);
337                    }
338    
339                    return count;
340            }
341    
342            @Override
343            public int unregisterJSONWebServiceActions(Object actionObject) {
344                    int count = 0;
345    
346                    Iterator<JSONWebServiceActionConfig> iterator =
347                            _jsonWebServiceActionConfigs.iterator();
348    
349                    while (iterator.hasNext()) {
350                            JSONWebServiceActionConfig jsonWebServiceActionConfig =
351                                    iterator.next();
352    
353                            if (actionObject.equals(
354                                            jsonWebServiceActionConfig.getActionObject())) {
355    
356                                    iterator.remove();
357    
358                                    count++;
359                            }
360                    }
361    
362                    return count;
363            }
364    
365            @Override
366            public int unregisterJSONWebServiceActions(String contextPath) {
367                    int count = 0;
368    
369                    Iterator<JSONWebServiceActionConfig> iterator =
370                            _jsonWebServiceActionConfigs.iterator();
371    
372                    while (iterator.hasNext()) {
373                            JSONWebServiceActionConfig jsonWebServiceActionConfig =
374                                    iterator.next();
375    
376                            if (contextPath.equals(
377                                            jsonWebServiceActionConfig.getContextPath())) {
378    
379                                    iterator.remove();
380    
381                                    count++;
382                            }
383                    }
384    
385                    return count;
386            }
387    
388            @Override
389            public int unregisterServletContext(ServletContext servletContext) {
390                    String contextPath = ContextPathUtil.getContextPath(servletContext);
391    
392                    return unregisterJSONWebServiceActions(contextPath);
393            }
394    
395            private int _countMatchedElements(
396                    String[] parameterNames, MethodParameter[] methodParameters) {
397    
398                    int matched = 0;
399    
400                    for (MethodParameter methodParameter : methodParameters) {
401                            String methodParameterName = methodParameter.getName();
402    
403                            methodParameterName = CamelCaseUtil.normalizeCamelCase(
404                                    methodParameterName);
405    
406                            if (ArrayUtil.contains(parameterNames, methodParameterName)) {
407                                    matched++;
408                            }
409                    }
410    
411                    return matched;
412            }
413    
414            private int _getJSONWebServiceActionConfigIndex(
415                    String contextPath, String path, String method,
416                    String[] parameterNames) {
417    
418                    int hint = -1;
419    
420                    int dotIndex = path.indexOf(CharPool.PERIOD);
421    
422                    if (dotIndex != -1) {
423                            hint = GetterUtil.getInteger(path.substring(dotIndex + 1));
424    
425                            path = path.substring(0, dotIndex);
426                    }
427    
428                    path = contextPath + path;
429    
430                    int firstIndex = _pathBinarySearch.findFirst(path);
431    
432                    if (firstIndex < 0) {
433                            if (_log.isDebugEnabled()) {
434                                    _log.debug(
435                                            "Unable to find JSON web service actions with path " +
436                                                    path + " for /" + contextPath);
437                            }
438    
439                            return -1;
440                    }
441    
442                    int lastIndex = _pathBinarySearch.findLast(path, firstIndex);
443    
444                    if (lastIndex < 0) {
445                            lastIndex = firstIndex;
446                    }
447    
448                    int index = -1;
449    
450                    int max = -1;
451    
452                    if (_log.isDebugEnabled()) {
453                            int total = lastIndex - firstIndex + 1;
454    
455                            _log.debug(
456                                    "Found " + total + " JSON web service actions with path " +
457                                            path + " in for /" + contextPath);
458                    }
459    
460                    for (int i = firstIndex; i <= lastIndex; i++) {
461                            JSONWebServiceActionConfig jsonWebServiceActionConfig =
462                                    _jsonWebServiceActionConfigs.get(i);
463    
464                            String jsonWebServiceActionConfigMethod =
465                                    jsonWebServiceActionConfig.getMethod();
466    
467                            if (PropsValues.JSONWS_WEB_SERVICE_STRICT_HTTP_METHOD &&
468                                    (method != null)) {
469    
470                                    if ((jsonWebServiceActionConfigMethod != null) &&
471                                            !jsonWebServiceActionConfigMethod.equals(method)) {
472    
473                                            continue;
474                                    }
475                            }
476    
477                            MethodParameter[] jsonWebServiceActionConfigMethodParameters =
478                                    jsonWebServiceActionConfig.getMethodParameters();
479    
480                            int methodParametersCount =
481                                    jsonWebServiceActionConfigMethodParameters.length;
482    
483                            if ((hint != -1) && (methodParametersCount != hint)) {
484                                    continue;
485                            }
486    
487                            int count = _countMatchedElements(
488                                    parameterNames, jsonWebServiceActionConfigMethodParameters);
489    
490                            if (count > max) {
491                                    if ((hint != -1) || (count >= methodParametersCount)) {
492                                            max = count;
493    
494                                            index = i;
495                                    }
496                            }
497                    }
498    
499                    if (_log.isDebugEnabled()) {
500                            if (index == -1) {
501                                    _log.debug(
502                                            "Unable to match parameters to a JSON web service " +
503                                                    "action with path " + path + " for /" +
504                                                            contextPath);
505                            }
506                            else {
507                                    _log.debug(
508                                            "Matched parameters to a JSON web service action with " +
509                                                    "path " + path + " for /" + contextPath);
510                            }
511                    }
512    
513                    return index;
514            }
515    
516            private int _getParameterPathIndex(String path) {
517                    int index = path.indexOf(CharPool.SLASH, 1);
518    
519                    if (index != -1) {
520                            index = path.indexOf(CharPool.SLASH, index + 1);
521                    }
522    
523                    return index;
524            }
525    
526            private String[] _resolvePaths(HttpServletRequest request, String path) {
527                    String contextPath = null;
528    
529                    int index = path.indexOf(CharPool.FORWARD_SLASH, 1);
530    
531                    if (index != -1) {
532                            index = path.lastIndexOf(CharPool.PERIOD, index);
533    
534                            if (index != -1) {
535                                    contextPath = path.substring(0, index);
536    
537                                    path = CharPool.FORWARD_SLASH + path.substring(index + 1);
538                            }
539                    }
540    
541                    if (contextPath == null) {
542                            HttpSession session = request.getSession();
543    
544                            ServletContext servletContext = session.getServletContext();
545    
546                            contextPath = ContextPathUtil.getContextPath(servletContext);
547                    }
548    
549                    return new String[] {contextPath, path};
550            }
551    
552            private static Log _log = LogFactoryUtil.getLog(
553                    JSONWebServiceActionsManagerImpl.class);
554    
555            private SortedArrayList<JSONWebServiceActionConfig>
556                    _jsonWebServiceActionConfigs =
557                            new SortedArrayList<JSONWebServiceActionConfig>();
558            private BinarySearch<String> _pathBinarySearch = new PathBinarySearch();
559    
560            private class PathBinarySearch extends BinarySearch<String> {
561    
562                    @Override
563                    protected int compare(int index, String element) {
564                            JSONWebServiceActionConfig jsonWebServiceActionConfig =
565                                    _jsonWebServiceActionConfigs.get(index);
566    
567                            String fullPath = jsonWebServiceActionConfig.getFullPath();
568    
569                            return fullPath.compareTo(element);
570                    }
571    
572                    @Override
573                    protected int getLastIndex() {
574                            return _jsonWebServiceActionConfigs.size() - 1;
575                    }
576    
577            }
578    
579    }