001    /**
002     * Copyright (c) 2000-present 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.jsonwebservice.JSONWebServiceNaming;
024    import com.liferay.portal.kernel.jsonwebservice.NoSuchJSONWebServiceException;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
028    import com.liferay.portal.kernel.servlet.HttpMethods;
029    import com.liferay.portal.kernel.util.CharPool;
030    import com.liferay.portal.kernel.util.GetterUtil;
031    import com.liferay.portal.kernel.util.MethodParameter;
032    import com.liferay.portal.kernel.util.StringBundler;
033    import com.liferay.portal.kernel.util.StringPool;
034    import com.liferay.portal.kernel.util.StringUtil;
035    import com.liferay.portal.kernel.util.Validator;
036    import com.liferay.portal.service.ServiceContextThreadLocal;
037    import com.liferay.portal.spring.context.PortalContextLoaderListener;
038    import com.liferay.portal.util.PortalUtil;
039    import com.liferay.portal.util.PropsValues;
040    
041    import java.lang.reflect.Method;
042    
043    import java.util.ArrayList;
044    import java.util.Collections;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.Set;
048    import java.util.TreeSet;
049    import java.util.concurrent.ConcurrentHashMap;
050    import java.util.concurrent.ConcurrentMap;
051    import java.util.concurrent.CopyOnWriteArrayList;
052    
053    import javax.servlet.ServletContext;
054    import javax.servlet.http.HttpServletRequest;
055    
056    /**
057     * @author Igor Spasic
058     * @author Raymond Aug??
059     */
060    @DoPrivileged
061    public class JSONWebServiceActionsManagerImpl
062            implements JSONWebServiceActionsManager {
063    
064            @Override
065            public Set<String> getContextNames() {
066                    return new TreeSet<>(
067                            _contextNameIndexedJSONWebServiceActionConfigs.keySet());
068            }
069    
070            @Override
071            public JSONWebServiceAction getJSONWebServiceAction(
072                            HttpServletRequest request)
073                    throws NoSuchJSONWebServiceException {
074    
075                    String path = GetterUtil.getString(request.getPathInfo());
076    
077                    String method = GetterUtil.getString(request.getMethod());
078    
079                    String parameterPath = null;
080    
081                    JSONRPCRequest jsonRPCRequest = null;
082    
083                    int parameterPathIndex = _getParameterPathIndex(path);
084    
085                    if (parameterPathIndex != -1) {
086                            parameterPath = path.substring(parameterPathIndex);
087    
088                            path = path.substring(0, parameterPathIndex);
089                    }
090                    else {
091                            if (method.equals(HttpMethods.POST) &&
092                                    !PortalUtil.isMultipartRequest(request)) {
093    
094                                    jsonRPCRequest = JSONRPCRequest.detectJSONRPCRequest(request);
095    
096                                    if (jsonRPCRequest != null) {
097                                            path += StringPool.SLASH + jsonRPCRequest.getMethod();
098    
099                                            method = null;
100                                    }
101                            }
102                    }
103    
104                    JSONWebServiceActionParameters jsonWebServiceActionParameters =
105                            new JSONWebServiceActionParameters();
106    
107                    jsonWebServiceActionParameters.collectAll(
108                            request, parameterPath, jsonRPCRequest, null);
109    
110                    if (jsonWebServiceActionParameters.getServiceContext() != null) {
111                            ServiceContextThreadLocal.pushServiceContext(
112                                    jsonWebServiceActionParameters.getServiceContext());
113                    }
114    
115                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
116                            _findJSONWebServiceAction(
117                                    request, path, method, jsonWebServiceActionParameters);
118    
119                    return new JSONWebServiceActionImpl(
120                            jsonWebServiceActionConfig, jsonWebServiceActionParameters,
121                            _jsonWebServiceNaming);
122            }
123    
124            @Override
125            public JSONWebServiceAction getJSONWebServiceAction(
126                            HttpServletRequest request, String path, String method,
127                            Map<String, Object> parameterMap)
128                    throws NoSuchJSONWebServiceException {
129    
130                    JSONWebServiceActionParameters jsonWebServiceActionParameters =
131                            new JSONWebServiceActionParameters();
132    
133                    jsonWebServiceActionParameters.collectAll(
134                            request, null, null, parameterMap);
135    
136                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
137                            _findJSONWebServiceAction(
138                                    request, path, method, jsonWebServiceActionParameters);
139    
140                    return new JSONWebServiceActionImpl(
141                            jsonWebServiceActionConfig, jsonWebServiceActionParameters,
142                            _jsonWebServiceNaming);
143            }
144    
145            @Override
146            public JSONWebServiceActionMapping getJSONWebServiceActionMapping(
147                    String signature) {
148    
149                    return _signatureIndexedJSONWebServiceActionConfigs.get(signature);
150            }
151    
152            @Override
153            public List<JSONWebServiceActionMapping> getJSONWebServiceActionMappings(
154                    String contextName) {
155    
156                    List<JSONWebServiceActionConfig> jsonWebServiceActionConfigs =
157                            _contextNameIndexedJSONWebServiceActionConfigs.get(contextName);
158    
159                    if (jsonWebServiceActionConfigs == null) {
160                            return Collections.emptyList();
161                    }
162    
163                    return new ArrayList<JSONWebServiceActionMapping>(
164                            jsonWebServiceActionConfigs);
165            }
166    
167            @Override
168            public int getJSONWebServiceActionsCount(String contextName) {
169                    List<JSONWebServiceActionConfig> jsonWebServiceActionConfigs =
170                            _contextNameIndexedJSONWebServiceActionConfigs.get(contextName);
171    
172                    if (jsonWebServiceActionConfigs == null) {
173                            return 0;
174                    }
175    
176                    return jsonWebServiceActionConfigs.size();
177            }
178    
179            @Override
180            public JSONWebServiceNaming getJSONWebServiceNaming() {
181                    return _jsonWebServiceNaming;
182            }
183    
184            @Override
185            public synchronized void registerJSONWebServiceAction(
186                    String contextName, String contextPath, Class<?> actionClass,
187                    Method actionMethod, String path, String method) {
188    
189                    try {
190                            if (!_addJSONWebServiceActionConfig(
191                                            new JSONWebServiceActionConfig(
192                                                    contextName, contextPath, actionClass, actionMethod,
193                                                    path, method))) {
194    
195                                    if (_log.isDebugEnabled()) {
196                                            _log.debug(
197                                                    "A JSON web service action is already registered at " +
198                                                            path);
199                                    }
200                            }
201                    }
202                    catch (Exception e) {
203                            if (_log.isWarnEnabled()) {
204                                    StringBundler sb = new StringBundler(14);
205    
206                                    sb.append("Unable to register service method {actionClass=");
207                                    sb.append(actionClass);
208                                    sb.append(", actionMethod=");
209                                    sb.append(actionMethod);
210                                    sb.append(", contextName=");
211                                    sb.append(contextName);
212                                    sb.append(", contextPath=");
213                                    sb.append(contextPath);
214                                    sb.append(", method=");
215                                    sb.append(method);
216                                    sb.append(", path=");
217                                    sb.append(path);
218                                    sb.append("} due to ");
219                                    sb.append(e.getMessage());
220    
221                                    _log.warn(sb.toString());
222                            }
223                    }
224            }
225    
226            @Override
227            public synchronized void registerJSONWebServiceAction(
228                    String contextName, String contextPath, Object actionObject,
229                    Class<?> actionClass, Method actionMethod, String path, String method) {
230    
231                    try {
232                            if (!_addJSONWebServiceActionConfig(
233                                            new JSONWebServiceActionConfig(
234                                                    contextName, contextPath, actionObject, actionClass,
235                                                    actionMethod, path, method))) {
236    
237                                    if (_log.isWarnEnabled()) {
238                                            _log.warn(
239                                                    "A JSON web service action is already registered at " +
240                                                            path);
241                                    }
242                            }
243                    }
244                    catch (Exception e) {
245                            StringBundler sb = new StringBundler(17);
246    
247                            sb.append("Something went wrong attempting to register service ");
248                            sb.append("method {contextName=");
249                            sb.append(contextName);
250                            sb.append(",contextPath=");
251                            sb.append(contextPath);
252                            sb.append(",actionObject=");
253                            sb.append(actionObject);
254                            sb.append(",actionClass=");
255                            sb.append(actionClass);
256                            sb.append(",actionMethod=");
257                            sb.append(actionMethod);
258                            sb.append(",path=");
259                            sb.append(path);
260                            sb.append(",method=");
261                            sb.append(method);
262                            sb.append("} due to ");
263                            sb.append(e.getMessage());
264    
265                            _log.warn(sb.toString());
266                    }
267            }
268    
269            @Override
270            public int registerService(String contextPath, Object service) {
271                    return registerService(StringPool.BLANK, contextPath, service);
272            }
273    
274            @Override
275            public int registerService(
276                    String contextName, String contextPath, Object service) {
277    
278                    JSONWebServiceRegistrator jsonWebServiceRegistrator =
279                            new JSONWebServiceRegistrator();
280    
281                    jsonWebServiceRegistrator.processBean(
282                            contextName, contextPath, service);
283    
284                    int count = getJSONWebServiceActionsCount(contextPath);
285    
286                    if (_log.isInfoEnabled()) {
287                            _log.info("Configured " + count + " actions for " + contextPath);
288                    }
289    
290                    return count;
291            }
292    
293            @Override
294            public int registerServletContext(ServletContext servletContext) {
295                    BeanLocator beanLocator = null;
296    
297                    String contextName = servletContext.getServletContextName();
298                    String contextPath = servletContext.getContextPath();
299    
300                    if (contextPath.equals(
301                                    PortalContextLoaderListener.getPortalServletContextPath()) ||
302                            contextPath.isEmpty()) {
303    
304                            beanLocator = PortalBeanLocatorUtil.getBeanLocator();
305                    }
306                    else {
307                            beanLocator = PortletBeanLocatorUtil.getBeanLocator(contextName);
308                    }
309    
310                    if (beanLocator == null) {
311                            if (_log.isInfoEnabled()) {
312                                    _log.info("Bean locator not available for " + contextPath);
313                            }
314    
315                            return -1;
316                    }
317    
318                    JSONWebServiceRegistrator jsonWebServiceRegistrator =
319                            new JSONWebServiceRegistrator();
320    
321                    jsonWebServiceRegistrator.processAllBeans(
322                            contextName, contextPath, beanLocator);
323    
324                    int count = getJSONWebServiceActionsCount(contextPath);
325    
326                    if (_log.isInfoEnabled()) {
327                            _log.info("Configured " + count + " actions for " + contextPath);
328                    }
329    
330                    return count;
331            }
332    
333            @Override
334            public synchronized int unregisterJSONWebServiceActions(
335                    Object actionObject) {
336    
337                    int count = 0;
338    
339                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
340                                    _signatureIndexedJSONWebServiceActionConfigs.values()) {
341    
342                            if (actionObject.equals(
343                                            jsonWebServiceActionConfig.getActionObject()) &&
344                                    _removeJSONWebServiceActionConfig(jsonWebServiceActionConfig)) {
345    
346                                    count++;
347                            }
348                    }
349    
350                    return count;
351            }
352    
353            @Override
354            public synchronized int unregisterJSONWebServiceActions(
355                    String contextPath) {
356    
357                    int count = 0;
358    
359                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
360                                    _signatureIndexedJSONWebServiceActionConfigs.values()) {
361    
362                            if (contextPath.equals(
363                                            jsonWebServiceActionConfig.getContextPath()) &&
364                                    _removeJSONWebServiceActionConfig(jsonWebServiceActionConfig)) {
365    
366                                    count++;
367                            }
368                    }
369    
370                    return count;
371            }
372    
373            @Override
374            public int unregisterServletContext(ServletContext servletContext) {
375                    String contextPath = servletContext.getContextPath();
376    
377                    return unregisterJSONWebServiceActions(contextPath);
378            }
379    
380            private boolean _addJSONWebServiceActionConfig(
381                    JSONWebServiceActionConfig jsonWebServiceActionConfig) {
382    
383                    JSONWebServiceActionConfig oldJSONWebServiceActionConfig =
384                            _signatureIndexedJSONWebServiceActionConfigs.putIfAbsent(
385                                    jsonWebServiceActionConfig.getSignature(),
386                                    jsonWebServiceActionConfig);
387    
388                    if (oldJSONWebServiceActionConfig != null) {
389                            return false;
390                    }
391    
392                    String contextName = jsonWebServiceActionConfig.getContextName();
393    
394                    List<JSONWebServiceActionConfig> jsonWebServiceActionConfigs =
395                            _contextNameIndexedJSONWebServiceActionConfigs.get(contextName);
396    
397                    if (jsonWebServiceActionConfigs == null) {
398                            jsonWebServiceActionConfigs = new CopyOnWriteArrayList<>();
399    
400                            _contextNameIndexedJSONWebServiceActionConfigs.put(
401                                    contextName, jsonWebServiceActionConfigs);
402                    }
403    
404                    jsonWebServiceActionConfigs.add(jsonWebServiceActionConfig);
405    
406                    jsonWebServiceActionConfigs =
407                            _pathIndexedJSONWebServiceActionConfigs.get(
408                                    jsonWebServiceActionConfig.getPath());
409    
410                    if (jsonWebServiceActionConfigs == null) {
411                            jsonWebServiceActionConfigs = new CopyOnWriteArrayList<>();
412    
413                            _pathIndexedJSONWebServiceActionConfigs.put(
414                                    jsonWebServiceActionConfig.getPath(),
415                                    jsonWebServiceActionConfigs);
416                    }
417    
418                    jsonWebServiceActionConfigs.add(jsonWebServiceActionConfig);
419    
420                    return true;
421            }
422    
423            private int _countMatchedParameters(
424                    String[] parameterNames, MethodParameter[] methodParameters) {
425    
426                    int matched = 0;
427    
428                    for (MethodParameter methodParameter : methodParameters) {
429                            String methodParameterName = methodParameter.getName();
430    
431                            methodParameterName = StringUtil.toLowerCase(methodParameterName);
432    
433                            for (String parameterName : parameterNames) {
434                                    if (StringUtil.equalsIgnoreCase(
435                                                    parameterName, methodParameterName)) {
436    
437                                            matched++;
438                                    }
439                            }
440                    }
441    
442                    return matched;
443            }
444    
445            private JSONWebServiceActionConfig _findJSONWebServiceAction(
446                            HttpServletRequest request, String path, String method,
447                            JSONWebServiceActionParameters jsonWebServiceActionParameters)
448                    throws NoSuchJSONWebServiceException {
449    
450                    String[] paths = _resolvePaths(request, path);
451    
452                    String contextName = paths[0];
453    
454                    path = paths[1];
455    
456                    if (_log.isDebugEnabled()) {
457                            _log.debug(
458                                    "Request JSON web service action with path " + path +
459                                            " and method " + method + " for " + contextName);
460                    }
461    
462                    String[] parameterNames =
463                            jsonWebServiceActionParameters.getParameterNames();
464    
465                    JSONWebServiceActionConfig jsonWebServiceActionConfig =
466                            _getJSONWebServiceActionConfig(
467                                    contextName, path, method, parameterNames);
468    
469                    if (jsonWebServiceActionConfig == null) {
470                            if (jsonWebServiceActionParameters.includeDefaultParameters()) {
471                                    parameterNames =
472                                            jsonWebServiceActionParameters.getParameterNames();
473    
474                                    jsonWebServiceActionConfig = _getJSONWebServiceActionConfig(
475                                            contextName, path, method, parameterNames);
476                            }
477                    }
478    
479                    if (jsonWebServiceActionConfig == null) {
480                            throw new NoSuchJSONWebServiceException(
481                                    "No JSON web service action with path " + path +
482                                            " and method " + method + " for " + contextName);
483                    }
484    
485                    return jsonWebServiceActionConfig;
486            }
487    
488            private JSONWebServiceActionConfig _getJSONWebServiceActionConfig(
489                    String contextName, String path, String method,
490                    String[] parameterNames) {
491    
492                    int hint = -1;
493    
494                    int offset = 0;
495    
496                    if (Validator.isNotNull(contextName)) {
497                            String pathPrefix = StringPool.SLASH.concat(contextName).concat(
498                                    StringPool.PERIOD);
499    
500                            if (path.startsWith(pathPrefix)) {
501                                    offset = pathPrefix.length();
502                            }
503                    }
504    
505                    int dotIndex = path.indexOf(CharPool.PERIOD, offset);
506    
507                    if (dotIndex != -1) {
508                            hint = GetterUtil.getInteger(path.substring(dotIndex + 1), -1);
509    
510                            if (hint != -1) {
511                                    path = path.substring(0, dotIndex);
512                            }
513                    }
514    
515                    List<JSONWebServiceActionConfig> jsonWebServiceActionConfigs =
516                            _pathIndexedJSONWebServiceActionConfigs.get(path);
517    
518                    if ((jsonWebServiceActionConfigs == null) ||
519                            jsonWebServiceActionConfigs.isEmpty()) {
520    
521                            if (_log.isDebugEnabled()) {
522                                    _log.debug(
523                                            "Unable to find JSON web service actions with path " +
524                                                    path + " for " + contextName);
525                            }
526    
527                            return null;
528                    }
529    
530                    if (_log.isDebugEnabled()) {
531                            _log.debug(
532                                    "Found " + jsonWebServiceActionConfigs.size() +
533                                            " JSON web service actions with path " + path + " for " +
534                                                    contextName);
535                    }
536    
537                    jsonWebServiceActionConfigs = new ArrayList<>(
538                            jsonWebServiceActionConfigs);
539    
540                    Collections.sort(jsonWebServiceActionConfigs);
541    
542                    int max = -1;
543    
544                    JSONWebServiceActionConfig matchedJSONWebServiceActionConfig = null;
545    
546                    for (JSONWebServiceActionConfig jsonWebServiceActionConfig :
547                                    jsonWebServiceActionConfigs) {
548    
549                            String jsonWebServiceActionConfigMethod =
550                                    jsonWebServiceActionConfig.getMethod();
551    
552                            if (PropsValues.JSONWS_WEB_SERVICE_STRICT_HTTP_METHOD &&
553                                    (method != null)) {
554    
555                                    if ((jsonWebServiceActionConfigMethod != null) &&
556                                            !jsonWebServiceActionConfigMethod.equals(method)) {
557    
558                                            continue;
559                                    }
560                            }
561    
562                            MethodParameter[] jsonWebServiceActionConfigMethodParameters =
563                                    jsonWebServiceActionConfig.getMethodParameters();
564    
565                            int methodParametersCount =
566                                    jsonWebServiceActionConfigMethodParameters.length;
567    
568                            if ((hint != -1) && (methodParametersCount != hint)) {
569                                    continue;
570                            }
571    
572                            int count = _countMatchedParameters(
573                                    parameterNames, jsonWebServiceActionConfigMethodParameters);
574    
575                            if (count > max) {
576                                    if ((hint != -1) || (count >= methodParametersCount)) {
577                                            max = count;
578    
579                                            matchedJSONWebServiceActionConfig =
580                                                    jsonWebServiceActionConfig;
581                                    }
582                            }
583                    }
584    
585                    if (_log.isDebugEnabled()) {
586                            if (matchedJSONWebServiceActionConfig == null) {
587                                    _log.debug(
588                                            "Unable to match parameters to a JSON web service " +
589                                                    "action with path " + path + " for " + contextName);
590                            }
591                            else {
592                                    _log.debug(
593                                            "Matched parameters to a JSON web service action with " +
594                                                    "path " + path + " for " + contextName);
595                            }
596                    }
597    
598                    return matchedJSONWebServiceActionConfig;
599            }
600    
601            private int _getParameterPathIndex(String path) {
602                    int index = path.indexOf(CharPool.SLASH, 1);
603    
604                    if (index != -1) {
605                            index = path.indexOf(CharPool.SLASH, index + 1);
606                    }
607    
608                    return index;
609            }
610    
611            private boolean _removeJSONWebServiceActionConfig(
612                    JSONWebServiceActionConfig jsonWebServiceActionConfig) {
613    
614                    if (!_signatureIndexedJSONWebServiceActionConfigs.remove(
615                                    jsonWebServiceActionConfig.getSignature(),
616                                    jsonWebServiceActionConfig)) {
617    
618                            return false;
619                    }
620    
621                    String contextName = jsonWebServiceActionConfig.getContextName();
622    
623                    List<JSONWebServiceActionConfig> jsonWebServiceActionConfigs =
624                            _contextNameIndexedJSONWebServiceActionConfigs.get(contextName);
625    
626                    jsonWebServiceActionConfigs.remove(jsonWebServiceActionConfig);
627    
628                    if (jsonWebServiceActionConfigs.isEmpty()) {
629                            _contextNameIndexedJSONWebServiceActionConfigs.remove(contextName);
630                    }
631    
632                    jsonWebServiceActionConfigs =
633                            _pathIndexedJSONWebServiceActionConfigs.get(
634                                    jsonWebServiceActionConfig.getPath());
635    
636                    jsonWebServiceActionConfigs.remove(jsonWebServiceActionConfig);
637    
638                    if (jsonWebServiceActionConfigs.isEmpty()) {
639                            _pathIndexedJSONWebServiceActionConfigs.remove(
640                                    jsonWebServiceActionConfig.getPath());
641                    }
642    
643                    return true;
644            }
645    
646            private String[] _resolvePaths(HttpServletRequest request, String path) {
647                    String contextName = null;
648    
649                    int index = path.indexOf(CharPool.FORWARD_SLASH, 1);
650    
651                    if (index != -1) {
652                            index = path.lastIndexOf(CharPool.PERIOD, index);
653    
654                            if (index != -1) {
655                                    contextName = path.substring(1, index);
656                            }
657                    }
658    
659                    if (contextName == null) {
660                            ServletContext servletContext = request.getServletContext();
661    
662                            contextName = servletContext.getServletContextName();
663    
664                            if (Validator.isNotNull(contextName)) {
665                                    path = StringPool.SLASH.concat(contextName).concat(
666                                            StringPool.PERIOD).concat(path.substring(1));
667                            }
668                    }
669    
670                    return new String[] {contextName, path};
671            }
672    
673            private static final Log _log = LogFactoryUtil.getLog(
674                    JSONWebServiceActionsManagerImpl.class);
675    
676            private final Map<String, List<JSONWebServiceActionConfig>>
677                    _contextNameIndexedJSONWebServiceActionConfigs =
678                            new ConcurrentHashMap<>();
679            private final JSONWebServiceNaming _jsonWebServiceNaming =
680                    new JSONWebServiceNaming();
681            private final Map<String, List<JSONWebServiceActionConfig>>
682                    _pathIndexedJSONWebServiceActionConfigs = new ConcurrentHashMap<>();
683            private final ConcurrentMap<String, JSONWebServiceActionConfig>
684                    _signatureIndexedJSONWebServiceActionConfigs =
685                            new ConcurrentHashMap<>();
686    
687    }