com.wavemaker.runtime.server.ServerUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.wavemaker.runtime.server.ServerUtils.java

Source

/*
 *  Copyright (C) 2012-2013 CloudJee, Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.wavemaker.runtime.server;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.springframework.aop.support.AopUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.thoughtworks.paranamer.AdaptiveParanamer;
import com.thoughtworks.paranamer.ParameterNamesNotFoundException;
import com.wavemaker.common.MessageResource;
import com.wavemaker.common.WMException;
import com.wavemaker.common.WMRuntimeException;
import com.wavemaker.common.util.ClassLoaderUtils;
import com.wavemaker.common.util.ClassUtils;
import com.wavemaker.common.util.StringUtils;
import com.wavemaker.common.util.SystemUtils;
import com.wavemaker.json.JSONState;
import com.wavemaker.runtime.service.ParsedServiceArguments;
import com.wavemaker.runtime.service.ServiceWire;
import com.wavemaker.runtime.service.TypedServiceReturn;
import com.wavemaker.runtime.service.annotations.ExposeToClient;
import com.wavemaker.runtime.service.annotations.HideFromClient;
import com.wavemaker.runtime.service.events.ServiceEventNotifier;

/**
 * Utility methods for the server components.
 * 
 * @author Matt Small
 */
public class ServerUtils {

    /** Logger for this class and subclasses */
    protected final static Logger logger = Logger.getLogger(ServerUtils.class);

    static final Pattern extensionPattern;
    static {
        extensionPattern = Pattern.compile(
                "^(.*)\\.(" + ServerConstants.DOWNLOAD_EXTENSION + "|" + ServerConstants.UPLOAD_EXTENSION + "|"
                        + ServerConstants.FLASH_UPLOAD_EXTENSION + "|" + ServerConstants.JSON_EXTENSION + ")$");
    }

    private ServerUtils() {
    }

    public static String getFileName(HttpServletRequest request) {

        String uri = request.getRequestURI();

        if (-1 != uri.lastIndexOf('/')) {
            uri = uri.substring(uri.lastIndexOf('/') + 1);
        }

        return uri;
    }

    /**
     * Returns the service name, if the URL points to a valid service, or null if not.
     */
    public static String getServiceName(HttpServletRequest request) {

        String fileName = getFileName(request);
        Matcher matcher = extensionPattern.matcher(fileName);

        if (matcher.matches()) {
            return matcher.group(1);
        } else {
            return null;
        }
    }

    public static String getDirectory(HttpServletRequest request) {

        String uri = request.getRequestURI();

        if (-1 != uri.lastIndexOf('/')) {
            uri = uri.substring(0, uri.lastIndexOf('/'));
        }
        if (0 == "".compareTo(uri)) {
            uri = "/";
        }

        return uri;
    }

    public static String readInput(HttpServletRequest request) throws IOException {

        InputStream is = request.getInputStream();
        if (is == null) {
            throw new WMRuntimeException("no input stream found in request");
        }

        String input = IOUtils.toString(is, ServerConstants.DEFAULT_ENCODING);

        is.close();

        return input;
    }

    /**
     * Try to determine parameter names for a given method. This will check {@link ParamName} attributes and debugging
     * symbols; if no name can be found, a default "arg-<position>" name will be used.
     * 
     * This will also continue working of method has been loaded by a non-default classloader.
     * 
     * @param method The method to introspect.
     * @return The names of the parameters in an ordered list.
     */
    public static List<String> getParameterNames(Method method) {

        int numParams = method.getParameterTypes().length;
        List<String> ret = new ArrayList<String>(numParams);
        Annotation[][] paramAnnotations = method.getParameterAnnotations();
        Class<?> paramNameClass = ClassLoaderUtils.loadClass(ParamName.class.getName(),
                method.getDeclaringClass().getClassLoader());

        String[] methodParameterNames;

        try {
            AdaptiveParanamer ap = new AdaptiveParanamer();
            methodParameterNames = ap.lookupParameterNames(method);
            ap = null;
        } catch (ParameterNamesNotFoundException e) {
            logger.info("No parameter names found for method " + method.getName());
            methodParameterNames = new String[numParams];
        }

        for (int i = 0; i < numParams; i++) {
            String paramName = null;

            if (paramName == null) {
                for (Annotation ann : paramAnnotations[i]) {
                    if (paramNameClass.isAssignableFrom(ann.annotationType())) {
                        try {
                            Method nameMethod = paramNameClass.getMethod("name");
                            paramName = (String) nameMethod.invoke(ann);
                        } catch (SecurityException e) {
                            throw new WMRuntimeException(e);
                        } catch (NoSuchMethodException e) {
                            throw new WMRuntimeException(e);
                        } catch (IllegalAccessException e) {
                            throw new WMRuntimeException(e);
                        } catch (InvocationTargetException e) {
                            throw new WMRuntimeException(e);
                        }

                        break;
                    }
                }
            }

            if (paramName == null && methodParameterNames != null) {
                paramName = methodParameterNames[i];
            }

            if (paramName == null) {
                logger.warn("no parameter name information for parameter " + i + ", method: " + method.getName());
                paramName = "arg-" + (i + 1);
            }

            ret.add(paramName);
        }

        return ret;
    }

    /**
     * Get the method parameter from the parameters map; as a side effect, remove that entry.
     * 
     * @param params The map - this is side-effected, and the method entry is removed.
     * @return The method to invoke.
     */
    public static String getMethod(Map<String, Object[]> params) {

        String method = null;
        if (params.containsKey(ServerConstants.METHOD)) {
            Object methodO = params.get(ServerConstants.METHOD);
            if (methodO instanceof String[]) {
                if (1 == ((String[]) methodO).length) {
                    method = ((String[]) methodO)[0];
                }
            }
        }
        if (method == null) {
            throw new WMRuntimeException(MessageResource.SERVER_NOMETHODORID, params);
        }
        params.remove(ServerConstants.METHOD);

        return method;
    }

    /**
     * Merge parameters from fileMap (if it exists) and parametersMap. All parameters are returned in Object[], even
     * those from fileMap.
     * 
     * @param request The original request.
     * @return A merged map of all parameters.
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object[]> mergeParams(HttpServletRequest request) {

        Map<String, Object[]> params = new HashMap<String, Object[]>();
        // Set<Map.Entry<?, ?>> entries;
        Set<Map.Entry<String, MultipartFile>> entries;

        if (request instanceof MultipartHttpServletRequest) {
            MultipartHttpServletRequest mrequest = (MultipartHttpServletRequest) request;
            entries = mrequest.getFileMap().entrySet();
            for (Map.Entry<?, ?> e : entries) {
                params.put((String) e.getKey(), new Object[] { e.getValue() });
            }
        }

        entries = request.getParameterMap().entrySet();
        for (Map.Entry<?, ?> e : entries) {
            String key = (String) e.getKey();
            Object[] value = (Object[]) e.getValue();
            if (params.get(key) == null) {
                params.put(key, (Object[]) e.getValue());
            } else {
                Object[] newArray = new Object[value.length + params.get(key).length];
                System.arraycopy(params.get(key), 0, newArray, 0, params.get(key).length);
                System.arraycopy(value, 0, newArray, params.get(key).length, value.length);
                params.put(key, newArray);
            }
        }

        return params;
    }

    public static TypedServiceReturn invokeMethodWithEvents(ServiceEventNotifier serviceEventNotifier,
            ServiceWire sw, String method, ParsedServiceArguments args, JSONState jsonState,
            boolean throwExceptions) throws WMException {
        return invokeMethodWithEvents(serviceEventNotifier, sw, method, args, jsonState, throwExceptions, null);
    }

    public static TypedServiceReturn invokeMethodWithEvents(ServiceEventNotifier serviceEventNotifier,
            ServiceWire sw, String method, ParsedServiceArguments args, JSONState jsonState,
            boolean throwExceptions, ServiceResponse serviceResponse) throws WMException {

        TypedServiceReturn ret = null;

        try {
            NDC.push("invoke " + sw.getServiceId() + "." + method);

            Throwable exception = null;

            // log the method arguments after conversion
            if (logger.isDebugEnabled()) {
                StringBuilder logMessage = new StringBuilder();
                logMessage.append("Invoking method \"" + method + "\" with translated parameters: [");

                for (Object arg : args.getArguments()) {
                    logMessage.append(arg);
                    if (arg != null) {
                        logMessage.append(" (" + arg.getClass() + ")");
                    }
                    logMessage.append(", ");
                }
                logMessage.append("]");
                logger.debug(logMessage.toString());
            }

            args.setArguments(serviceEventNotifier.executePreOperation(sw, method, args.getArguments()));
            try {
                ret = sw.getServiceType().invokeMethod(sw, method, args, jsonState, serviceResponse);
            } catch (Throwable t) {
                if (throwExceptions) {
                    throw new WMRuntimeException(t);
                }

                exception = SystemUtils.unwrapInternalException(t);
            }

            try {
                ret = serviceEventNotifier.executePostOperation(sw, method, ret, exception);
            } catch (Throwable t) {
                if (t instanceof WMException) {
                    throw (WMException) t;
                } else if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    // some exception messages are not useful when taken outside
                    // of the context of the exception type (ClassCastException,
                    // ClassNotFoundException, etc) so include the type in the
                    // msg
                    String msg = StringUtils.fromLastOccurrence(t.getClass().getName(), ".");
                    if (t.getMessage() != null) {
                        msg += ": " + t.getMessage();
                    }
                    throw new WMRuntimeException(msg, t);
                }
            }
        } finally {
            NDC.pop();
        }

        return ret;
    }

    public static Object invokeMethod(Object serviceObject, Method method, Object[] args)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Class<?>[] paramTypes = method.getParameterTypes();
        Object[] argsToSend = null;
        if (args != null && paramTypes != null && args.length > 0) {
            argsToSend = new Object[args.length];
            int i = 0;
            for (Class<?> p : paramTypes) {
                Object arg = args[i];
                if (arg != null && !p.isAssignableFrom(arg.getClass())
                        && String.class.isAssignableFrom(arg.getClass())) {
                    arg = convert(arg.toString(), p);
                }
                argsToSend[i] = arg;
                i++;
            }

        }
        return method.invoke(serviceObject, argsToSend);
    }

    private static Object convert(String value, Class clazz) {
        PropertyEditor editor = PropertyEditorManager.findEditor(clazz);
        editor.setAsText(value);
        return editor.getValue();
    }

    public static void main(String[] args) {
        System.out.println(convert("223342.3", Double.class));
    }

    /**
      * Detect proxy class, and find underlying class. <br>
      * An inglorious hack: This simple version works for CGLIB proxy; as used by Springframework.security. No guarantee
      * (or even expectation) that other AOP proxies will be detected. Java (or CGLIB) may have more deterministic ways
      * to find the underlying class.
      * <p>
      * Used by FileUploadController and FileDownloadController, which to findMethod() on SpringBeans obtained at
      * runtime.
      * 
      * @param sClass the class of an object that may be wrapped by a proxy object.
      * @return the underlying Class of the object that was wrapped
      */
    public static Class<?> getRealClass(Object o) {

        Class<?> ret;
        if (AopUtils.isAopProxy(o)) {
            ret = AopUtils.getTargetClass(o);
        } else {
            ret = o.getClass();
        }
        return ret;
    }

    /**
     * Get a list of methods to be exposed to the client. This will obey restrictions from {@link ExposeToClient} and
     * {@link HideFromClient}.
     * 
     * @param klass The class to examine.
     * @return A list of methods to expose to the client in the specified class.
     */
    @SuppressWarnings("unchecked")
    public static List<Method> getClientExposedMethods(Class<?> klass) {

        List<Method> allMethods = ClassUtils.getPublicMethods(klass);
        List<Method> ret = new ArrayList<Method>(allMethods.size());
        ClassLoader cl = klass.getClassLoader();

        Class<Annotation> hideFromClient = (Class<Annotation>) ClassLoaderUtils
                .loadClass(HideFromClient.class.getCanonicalName(), cl);
        Class<Annotation> exposeToClient = (Class<Annotation>) ClassLoaderUtils
                .loadClass(ExposeToClient.class.getCanonicalName(), cl);

        if (klass.getAnnotation(hideFromClient) != null) {
            for (Method meth : allMethods) {
                if (meth.getAnnotation(exposeToClient) != null) {
                    ret.add(meth);
                }
            }
        } else {
            for (Method meth : allMethods) {
                if (meth.getAnnotation(hideFromClient) == null) {
                    ret.add(meth);
                }
            }
        }

        return ret;
    }

    /**
     * Calculate the server time offset against UTC
     * 
     * @return the server time offset in mili-seconds
     */
    public static int getServerTimeOffset() {
        Calendar now = Calendar.getInstance();
        return now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET);
    }
}