org.fornax.cartridges.sculptor.smartclient.server.ScServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.fornax.cartridges.sculptor.smartclient.server.ScServlet.java

Source

/*
 * (C) Copyright Factory4Solutions a.s. 2009
 *
 * 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 org.fornax.cartridges.sculptor.smartclient.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.antlr.runtime.tree.CommonTree;
import org.apache.commons.fileupload.FileItem;
import org.fornax.cartridges.sculptor.framework.accessapi.ConditionalCriteria;
import org.fornax.cartridges.sculptor.framework.annotation.Name;
import org.fornax.cartridges.sculptor.framework.domain.LeafProperty;
import org.fornax.cartridges.sculptor.framework.domain.PagedResult;
import org.fornax.cartridges.sculptor.framework.domain.PagingParameter;
import org.fornax.cartridges.sculptor.framework.errorhandling.ApplicationException;
import org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContext;
import org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContextStore;
import org.fornax.cartridges.sculptor.framework.errorhandling.ValidationException;
import org.fornax.cartridges.sculptor.smartclient.client.Main;
import org.fornax.cartridges.sculptor.smartclient.domain.FileUpload;
import org.fornax.cartridges.sculptor.smartclient.domain.GuiDataSource;
import org.fornax.cartridges.sculptor.smartclient.domain.ListSettings;
import org.fornax.cartridges.sculptor.smartclient.framework.MethodDescription;
import org.fornax.cartridges.sculptor.smartclient.framework.ServiceDescription;
import org.fornax.cartridges.sculptor.smartclient.framework.Translatable;
import org.fornax.cartridges.sculptor.smartclient.quartz.ServiceInvoker;
import org.fornax.cartridges.sculptor.smartclient.server.jsparser.JavaScriptDataParser;
import org.fornax.cartridges.sculptor.smartclient.server.util.Config;
import org.fornax.cartridges.sculptor.smartclient.server.util.UnifiedFormatter;
import org.fornax.cartridges.sculptor.smartclient.serviceapi.FileUploadService;
import org.fornax.cartridges.sculptor.smartclient.serviceapi.GuiDataSourceService;
import org.fornax.cartridges.sculptor.smartclient.serviceapi.ListSettingsService;
import org.fornax.cartridges.sculptor.smartclient.serviceapi.ServiceRegistryService;
import org.fornax.cartridges.sculptor.smartclient.serviceimpl.PropertySupportFacade;
import org.hibernate.validator.InvalidValue;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.HttpRequestHandler;

/**
 * Primary server side request handler. Spring servlet.
 * 
 * @author Ing. Pavel Tavoda
 */
@SuppressWarnings("unchecked")
public class ScServlet implements HttpRequestHandler, ServiceInvoker {
    private static final String GET_TRANSLATE = "getTranslate";
    private static final int GET_TRANSLATE_LENGTH = GET_TRANSLATE.length();
    private static final long serialVersionUID = 1L;
    private static final Logger log = Logger.getLogger(ScServlet.class.getName());

    private static final int DEF_DEPTH = 3;
    private static final int LOOK_AHEAD = 30;
    private static final String LIST_SETTINGS_SERVICE = "listSettingsService";
    // private static final SimpleDateFormat dateTimeFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    private static final String SET_PREFIX = "set";
    private static final String GET_PREFIX = "get";
    private static final String READ_ONLY = " (READ-ONLY)";
    private ThreadLocal<String> sessionId = new ThreadLocal<String>();
    private HashMap<String, String> ds2Ref = new HashMap<String, String>();

    @Autowired
    private UploadServlet uploadServlet;

    @Autowired
    private FileUploadService fileUploadService;

    {
        try {
            LogManager.getLogManager().readConfiguration(
                    ScServlet.class.getResourceAsStream("log-" + Config.getEnvironment() + ".properties"));
        } catch (Exception e) {
            System.err.println("Can not initialize logging system");
            e.printStackTrace();
        }

        UnifiedFormatter formatter = new UnifiedFormatter();

        // Set formatter
        String property = LogManager.getLogManager().getProperty("unifiedFormatter.loggers");
        property = property == null ? "" : property;
        String[] loggers = property.split("[,:;]");
        for (int i = 0; i < loggers.length; i++) {
            Logger rootLogger = Logger.getLogger(loggers[i]);
            Handler[] handlers = rootLogger.getHandlers();
            System.out.println(
                    "Installing unified formmater for " + rootLogger.getName() + " [" + handlers.length + "]");
            for (int j = 0; j < handlers.length; j++) {
                handlers[j].setFormatter(formatter);
            }
        }
    }

    public ScServlet() throws ServletException {
    }

    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            printRequest(request);

            sessionId.set(request.getSession().getId());
            String dataSource = request.getParameter("_dataSource");

            // List grid export
            if ("export".equals(request.getParameter("_operationType"))) {
                exportData(dataSource, response);
                return;
            }

            response.setContentType("text/plain;charset=UTF-8");
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Expires", "-1");

            if ("getMenu".equals(dataSource)) {
                response.getWriter().write(listServices());
            } else if ("userInfo".equals(dataSource)) {
                ServiceContext ctx = ServiceContextStore.get();
                PrintWriter out = response.getWriter();
                out.print(ctx.getUserId());
                for (String role : ctx.getRoles()) {
                    out.print(",");
                    out.print(role);
                }
                out.flush();
            } else if ("createDataSource".equals(dataSource)) {
                HashMap<String, String> classToServiceName = new HashMap<String, String>();
                for (ServiceDescription serviceDesc : getServiceDescription()) {
                    log.log(Level.INFO, "Service translation from {0} to {1}",
                            new Object[] { serviceDesc.getExpectedClass().getName(), serviceDesc.getName() });
                    classToServiceName.put(serviceDesc.getExpectedClass().getName(), serviceDesc.getName());
                }
                GuiDataSourceService dataSourceService = getGuiDataSourceService();
                dataSourceService.setServiceMapping(ServiceContextStore.get(), classToServiceName);

                StringBuilder output = new StringBuilder();
                output.append("dataSources###");
                List<GuiDataSource> allDataSources = dataSourceService.findAll(ServiceContextStore.get());
                for (GuiDataSource dataSourceDef : allDataSources) {
                    ds2Ref.put(dataSourceDef.getBaseClass(), dataSourceDef.getTitleField());
                    dataSourceDef.setBaseClass(null);
                    log.log(Level.INFO, "Entity def for {0}", dataSourceDef.getXxxID());
                    dataSourceDef.setDataURL(getServiceUrl(request));

                    output.append("$wnd.isc.RestDataSource.create(")
                            .append(mapObjToOutput(dataSourceDef, 6, new Stack<Object>(), true, false))
                            .append(");\n");
                }

                ListSettingsService listInstance = (ListSettingsService) findService(LIST_SETTINGS_SERVICE)
                        .getInstance();
                List<ListSettings> guiList = listInstance.findUserSettings(ServiceContextStore.get());
                for (ListSettings listSettings : guiList) {
                    output.append("\n###").append(listSettings.getListID()).append("###")
                            .append(listSettings.getSettings());
                }

                log.log(Level.INFO, "Create datasource {0}", output.toString());
                response.getWriter().append(output);

            } else if (findService(dataSource) != null) {
                ServiceDescription serviceDesc = findService(dataSource);
                String operationType = request.getParameter("_operationType");
                String methodName = request.getParameter("_operationId");

                if (methodName != null && !methodName.endsWith("_fetch")) {
                    log.log(Level.FINER, "Executing method '" + dataSource + "." + methodName + "'");
                    MethodDescription methodDescription = serviceDesc.getOtherMethods().get(methodName);
                    if (methodDescription != null) {
                        Object valObj = null;
                        HashMap<String, Object> jsData = unifyRequest(request);
                        Object[] params = prepareMethodParam(jsData, true, methodDescription.parameterNames,
                                methodDescription.method.getParameterTypes());
                        // Resolve "ids" for iterations
                        int iterate = -1;
                        if (jsData.get("ids") != null) {
                            Class<?>[] paramTypes = methodDescription.method.getParameterTypes();
                            for (int i = 0; i < paramTypes.length; i++) {
                                if (Long.class.equals(paramTypes[i]) || paramTypes[i].equals(Long.TYPE)
                                        && methodDescription.parameterNames[i].equals("id") && params[i] == null) {
                                    iterate = i;
                                    break;
                                } else if (Collection.class.isAssignableFrom(paramTypes[i])
                                        && methodDescription.parameterNames[i].equals("ids") && params[i] == null) {
                                    List<Long> longList = new ArrayList<Long>();
                                    String ids = (String) jsData.get("ids");
                                    String[] idSplit = ids.split(",");
                                    for (int k = 0; k < idSplit.length; k++) {
                                        if (idSplit[i].length() > 0) {
                                            longList.add(Long.parseLong(idSplit[i]));
                                        }
                                    }
                                    params[i] = longList;
                                    break;
                                }
                            }
                        }

                        if (iterate != -1) {
                            if (log.isLoggable(Level.FINER)) {
                                for (int j = 0; j < params.length; j++) {
                                    log.log(Level.FINER, "     Parameter[{0}]={1}", new Object[] { j, params[j] });
                                }
                            }
                            String ids = (String) jsData.get("ids");
                            String[] idSplit = ids.split(",");
                            ArrayList<Object> listResult = new ArrayList<Object>();
                            valObj = listResult;
                            for (int i = 0; i < idSplit.length; i++) {
                                if (idSplit[i].length() > 0) {
                                    params[iterate] = Long.parseLong(idSplit[i]);
                                    Object subValObj = methodDescription.method.invoke(serviceDesc.getInstance(),
                                            params);
                                    if (subValObj != null && subValObj instanceof Collection) {
                                        listResult.addAll((Collection) subValObj);
                                    } else if (subValObj != null) {
                                        listResult.add(subValObj);
                                    }
                                }
                            }
                            log.info("Return value: " + valObj);
                        } else if (params != null) {
                            if (log.isLoggable(Level.FINER)) {
                                for (int j = 0; j < params.length; j++) {
                                    log.log(Level.FINER, "     Parameter[{0}]={1}", new Object[] { j, params[j] });
                                }
                            }
                            valObj = methodDescription.method.invoke(serviceDesc.getInstance(), params);
                            log.info("Return value: " + valObj);
                        }
                        Collection<Object> collResult;
                        PagedResult<?> pagedResult = null;
                        if (valObj == null) {
                            collResult = new ArrayList<Object>();
                            for (int i = 0; i < params.length; i++) {
                                if (params[i] != null
                                        && params[i].getClass().equals(serviceDesc.getExpectedClass())) {
                                    collResult.add(params[i]);
                                    break;
                                }
                            }
                        } else if (valObj instanceof Collection) {
                            collResult = (Collection) valObj;
                        } else if (valObj instanceof PagedResult) {
                            collResult = null;
                            pagedResult = (PagedResult) valObj;
                        } else {
                            ArrayList<Object> serviceResultArray = new ArrayList<Object>();
                            serviceResultArray.add(valObj);
                            collResult = serviceResultArray;
                        }
                        if (collResult != null) {
                            sendResponse(response.getWriter(), 0, collResult.size(), collResult);
                        } else if (pagedResult != null) {
                            sendResponse(response.getWriter(), pagedResult);
                        }
                    } else {
                        throw new IllegalArgumentException(
                                "No " + methodName + " operation available on " + dataSource);
                    }
                } else if (operationType == null) {
                    throw makeApplicationException("Unsupported operation", "ERR9001", "NULL");
                } else if (operationType.equals("fetch") && request.getParameter("id") != null) {
                    Long idVal = Long.parseLong(request.getParameter("id"));
                    if (serviceDesc.getFindById() != null) {
                        Object serviceResult = serviceDesc.getFindById().invoke(serviceDesc.getInstance(),
                                ServiceContextStore.get(), idVal);
                        ArrayList<Object> serviceResultArray = new ArrayList<Object>();
                        serviceResultArray.add(serviceResult);

                        sendResponse(response.getWriter(), 0, 1, serviceResultArray);
                    } else {
                        throw new IllegalArgumentException("No fetch operation available");
                    }
                } else if (operationType.equals("fetch")) {
                    List<?> serviceResult;
                    PagedResult<?> pagedServiceResult = null;

                    String sRow = request.getParameter("_startRow");
                    int startRow = sRow == null ? 0 : Integer.parseInt(sRow);
                    String eRow = request.getParameter("_endRow");
                    int endRow = eRow == null ? 0 : Integer.parseInt(eRow);

                    ArrayList<String> queryParams = new ArrayList();
                    Enumeration pNames = request.getParameterNames();
                    while (pNames.hasMoreElements()) {
                        String paramName = (String) pNames.nextElement();
                        if (!paramName.startsWith("_")) {
                            queryParams.add(paramName);
                        } else if ("_sortBy".equals(paramName)) {
                            queryParams.add("sortBy");
                        }
                    }

                    if (queryParams.size() > 0) {
                        Collection<MethodDescription> methods = serviceDesc.getOtherMethods().values();
                        MethodDescription matchedMethod = null;
                        int matchLength = 1000;
                        for (MethodDescription method : methods) {
                            String[] methodParamNames = method.parameterNames;
                            if (methodParamNames != null && methodParamNames.length >= queryParams.size()
                                    && (List.class.isAssignableFrom(method.method.getReturnType())
                                            || PagedResult.class.isAssignableFrom(method.method.getReturnType()))) {
                                boolean matchParam = false;
                                for (String queryParam : queryParams) {
                                    matchParam = false;
                                    for (String methodParamName : methodParamNames) {
                                        if (queryParam.equals(methodParamName)) {
                                            matchParam = true;
                                            break;
                                        }
                                    }
                                    if (!matchParam) {
                                        break;
                                    }
                                }
                                if (matchParam && method.parameterNames.length < matchLength) {
                                    matchedMethod = method;
                                    matchLength = method.parameterNames.length;
                                }
                            }
                        }

                        Object[] params = null;
                        if (matchedMethod != null) {
                            HashMap<String, Object> jsData = unifyRequest(request);
                            params = prepareMethodParam(jsData, true, matchedMethod.parameterNames,
                                    matchedMethod.method.getParameterTypes());
                        } else {
                            List<ConditionalCriteria> conditions = new ArrayList();
                            for (MethodDescription method : methods) {
                                if (method.getMethodName().equals("findByCondition")) {
                                    Class<?>[] parameterTypes = method.method.getParameterTypes();
                                    if (List.class.isAssignableFrom(parameterTypes[0])) {
                                        params = new Object[] { conditions };
                                        matchedMethod = method;
                                        break;
                                    } else if (parameterTypes.length == 3
                                            && ServiceContext.class.isAssignableFrom(parameterTypes[0])
                                            && List.class.isAssignableFrom(parameterTypes[1])
                                            && PagingParameter.class.isAssignableFrom(parameterTypes[2])) {
                                        endRow = endRow < startRow + LOOK_AHEAD ? startRow + LOOK_AHEAD : endRow;
                                        PagingParameter pagingParam = PagingParameter.rowAccess(startRow, endRow,
                                                LOOK_AHEAD);
                                        params = new Object[] { ServiceContextStore.get(), conditions,
                                                pagingParam };
                                        matchedMethod = method;
                                        break;
                                    } else if (parameterTypes.length == 2
                                            && ServiceContext.class.isAssignableFrom(parameterTypes[0])
                                            && List.class.isAssignableFrom(parameterTypes[1])) {
                                        params = new Object[] { ServiceContextStore.get(), conditions };
                                        matchedMethod = method;
                                        break;
                                    }
                                }
                            }
                            if (matchedMethod != null) {
                                for (String queryParam : queryParams) {
                                    LeafProperty<?> queryProp = new LeafProperty(queryParam, ScServlet.class);
                                    String matchStyle = request.getParameter("_textMatchStyle");
                                    String queryValue = request.getParameter(queryParam);
                                    if (queryParam.equals("sortBy")) {
                                        queryValue = normalizeAssoc(serviceDesc, request.getParameter("_sortBy"));
                                        if (queryValue.startsWith("+")) {
                                            LeafProperty<?> sortProp = new LeafProperty(queryValue.substring(1),
                                                    ScServlet.class);
                                            conditions.add(ConditionalCriteria.orderAsc(sortProp));
                                        } else if (queryValue.startsWith("-")) {
                                            LeafProperty<?> sortProp = new LeafProperty(queryValue.substring(1),
                                                    ScServlet.class);
                                            conditions.add(ConditionalCriteria.orderDesc(sortProp));
                                        } else {
                                            LeafProperty<?> sortProp = new LeafProperty(queryValue,
                                                    ScServlet.class);
                                            conditions.add(ConditionalCriteria.orderAsc(sortProp));
                                        }
                                    } else if (queryValue.indexOf('%') != -1) {
                                        conditions.add(ConditionalCriteria.ignoreCaseLike(queryProp,
                                                request.getParameter(queryParam) + "%"));
                                    } else {
                                        String[] queryParamSplit = queryParam.split("\\.");
                                        Class watchClass = serviceDesc.getExpectedClass();
                                        Object otherEqVal = null;
                                        boolean isString = false;
                                        for (String queryParamPart : queryParamSplit) {
                                            try {
                                                Method method = watchClass.getMethod(
                                                        makeGetMethodName(queryParamPart), (Class[]) null);
                                                watchClass = method.getReturnType();
                                                if (Collection.class.isAssignableFrom(watchClass)) {
                                                    // TODO look deeper into class of collection
                                                    break;
                                                } else if (Enum.class.isAssignableFrom(watchClass)) {
                                                    otherEqVal = Enum.valueOf(watchClass, queryValue);
                                                    break;
                                                } else if (String.class.equals(watchClass)) {
                                                    isString = true;
                                                    break;
                                                } else if (Long.class.equals(watchClass)) {
                                                    isString = true;
                                                    otherEqVal = Long.parseLong(queryValue);
                                                    break;
                                                } else if (Integer.class.equals(watchClass)) {
                                                    isString = true;
                                                    otherEqVal = Integer.parseInt(queryValue);
                                                    break;
                                                } else if (Double.class.equals(watchClass)) {
                                                    isString = true;
                                                    otherEqVal = Double.parseDouble(queryValue);
                                                    break;
                                                } else if (Date.class.equals(watchClass)) {
                                                    otherEqVal = dateFormat.parse(queryValue);
                                                    break;
                                                } else if (findServiceByClassName(watchClass.getName()) != null
                                                        && "null".equals(queryValue)) {
                                                    otherEqVal = "null";
                                                    break;
                                                } else if (findServiceByClassName(watchClass.getName()) != null
                                                        && findServiceByClassName(watchClass.getName())
                                                                .getFindById() != null) {
                                                    ServiceDescription srvc = findServiceByClassName(
                                                            watchClass.getName());
                                                    otherEqVal = srvc.getFindById().invoke(srvc.getInstance(),
                                                            ServiceContextStore.get(), Long.parseLong(queryValue));
                                                }
                                            } catch (NoSuchMethodException nsme) {
                                                // Ignore error, isString will stay false
                                                break;
                                            }
                                        }
                                        boolean isLike = "substring".equals(matchStyle)
                                                || "startsWith".equals(matchStyle);
                                        if ("null".equals(otherEqVal)) {
                                            conditions.add(ConditionalCriteria.isNull(queryProp));
                                        } else if (otherEqVal instanceof Date) {
                                            DateMidnight start = (new DateTime(otherEqVal)).toDateMidnight();
                                            DateMidnight stop = start.plusDays(1);
                                            conditions.add(ConditionalCriteria.between(queryProp, start.toDate(),
                                                    stop.toDate()));
                                        } else if (isString && otherEqVal != null && isLike) {
                                            conditions.add(ConditionalCriteria.like(queryProp, otherEqVal));
                                        } else if (isString && "substring".equals(matchStyle)) {
                                            conditions.add(ConditionalCriteria.ignoreCaseLike(queryProp,
                                                    "%" + request.getParameter(queryParam) + "%"));
                                        } else if (isString && "startsWith".equals(matchStyle)) {
                                            conditions.add(ConditionalCriteria.ignoreCaseLike(queryProp,
                                                    request.getParameter(queryParam) + "%"));
                                        } else if (otherEqVal != null) {
                                            conditions.add(ConditionalCriteria.equal(queryProp, otherEqVal));
                                        } else {
                                            conditions.add(ConditionalCriteria.equal(queryProp, queryValue));
                                        }
                                    }
                                }
                            }
                        }

                        if (matchedMethod != null && params != null) {
                            for (int j = 0; j < params.length; j++) {
                                log.log(Level.FINER, "     Parameter[{0}]={1}", new Object[] { j, params[j] });
                            }
                            if (matchedMethod.method.getReturnType().equals(PagedResult.class)) {
                                serviceResult = null;
                                pagedServiceResult = (PagedResult) matchedMethod.method
                                        .invoke(serviceDesc.getInstance(), params);
                            } else {
                                serviceResult = (List<?>) matchedMethod.method.invoke(serviceDesc.getInstance(),
                                        params);
                            }
                        } else {
                            throw makeApplicationException("You can''t filter with such condition.", "ERR9015",
                                    (Serializable[]) null);
                        }
                    } else if (queryParams.size() == 0 && serviceDesc.getFindAll() != null) {
                        Class<?>[] paramTypes = serviceDesc.getFindAll().getParameterTypes();
                        if (paramTypes.length == 2 && paramTypes[0].equals(ServiceContext.class)
                                && paramTypes[1].equals(PagingParameter.class)
                                && serviceDesc.getFindAll().getReturnType().equals(PagedResult.class)) {
                            endRow = endRow < startRow + LOOK_AHEAD ? startRow + LOOK_AHEAD : endRow;
                            PagingParameter pagingParam = PagingParameter.rowAccess(startRow, endRow, LOOK_AHEAD);
                            pagedServiceResult = (PagedResult<?>) serviceDesc.getFindAll()
                                    .invoke(serviceDesc.getInstance(), ServiceContextStore.get(), pagingParam);
                            serviceResult = null;
                        } else if (paramTypes.length == 1 && paramTypes[0].equals(ServiceContext.class)) {
                            serviceResult = (List<? extends Object>) serviceDesc.getFindAll()
                                    .invoke(serviceDesc.getInstance(), ServiceContextStore.get());
                        } else {
                            serviceResult = null;
                        }
                    } else {
                        throw new ApplicationException("", "No fetch operation available");
                    }

                    if (pagedServiceResult != null) {
                        sendResponse(response.getWriter(), pagedServiceResult);
                    } else {
                        int resultSize = serviceResult == null ? 0 : serviceResult.size();
                        endRow = (endRow == 0 ? resultSize : endRow);
                        sendResponse(response.getWriter(), startRow, endRow < resultSize ? endRow : resultSize,
                                serviceResult);
                    }
                } else if (operationType.equals("update") && request.getParameter("id") != null) {
                    Object val = serviceDesc.getFindById().invoke(serviceDesc.getInstance(),
                            ServiceContextStore.get(), Long.parseLong(request.getParameter("id")));
                    HashMap<String, Object> reqData = unifyRequest(request);
                    mapRequestToObj(reqData, serviceDesc.getExpectedClass(), val);
                    serviceDesc.getOtherMethods().get("save").method.invoke(serviceDesc.getInstance(),
                            ServiceContextStore.get(), val);
                    ArrayList<Object> list = new ArrayList<Object>();
                    list.add(val);
                    sendResponse(response.getWriter(), 0, 1, list);
                } else if ((operationType.equals("add") || operationType.equals("update"))
                        && request.getParameter("id") == null) {
                    HashMap<String, Object> reqData = unifyRequest(request);
                    Object val = makeNewInstance(serviceDesc.getExpectedClass(), reqData);
                    if (val != null) {
                        mapRequestToObj(reqData, serviceDesc.getExpectedClass(), val);
                        serviceDesc.getOtherMethods().get("save").method.invoke(serviceDesc.getInstance(),
                                ServiceContextStore.get(), val);
                        ArrayList<Object> list = new ArrayList<Object>();
                        list.add(val);
                        sendResponse(response.getWriter(), 0, 1, list);
                    } else {
                        throw makeApplicationException("Can't create new instance", "ERR9003",
                                serviceDesc.getExpectedClass().getName());
                    }
                } else {
                    throw makeApplicationException("Unsupported operation", "ERR9001", operationType);
                }
            } else {
                throw makeApplicationException("Wrong datasource name", "ERR9002", dataSource);
            }
        } catch (Throwable ex) {
            // Find most relevant exception in embedded exceptions
            ApplicationException appException = null;
            Throwable relevantException = ex;
            while (ex != null) {
                relevantException = ex;
                if (ex instanceof ApplicationException) {
                    appException = (ApplicationException) ex;
                    break;
                }
                if (ex instanceof ValidationException) {
                    break;
                }

                ex = ex.getCause();
            }

            // Prepare message
            String msg = null;
            if (appException != null) {
                msg = translate(appException.getMessage());
                Serializable[] msgParams = appException.getMessageParameters();
                if (msgParams != null) {
                    Object[] params = new Object[msgParams.length];
                    for (int i = 0; i < msgParams.length; i++) {
                        if (msgParams[i] instanceof Translatable) {
                            params[i] = translate(((Translatable) msgParams[i]).getContent());
                        } else {
                            params[i] = msgParams[i];
                        }
                    }
                    msg = MessageFormat.format(msg, params);
                }
            } else if (relevantException instanceof ValidationException) {
                StringBuilder b = new StringBuilder();
                for (InvalidValue iv : ((ValidationException) relevantException).getInvalidValues()) {
                    b.append("<b>").append(translate(iv.getPropertyName()) + ":</b> " + iv.getMessage())
                            .append("<br/>");
                }
                msg = b.toString();
            } else {
                msg = translate("ERR9000");
                msg = MessageFormat.format(msg,
                        new Object[] { relevantException.getClass().getName(), relevantException.getMessage() });
            }

            // Print stack trace
            log.log(Level.WARNING, "Relevant exception", relevantException);

            if (msg != null) {
                log.log(Level.WARNING, "SENDING BACK ERROR '" + msg + "'");
                response.getWriter()
                        .write("{response:{ status:-1, data:\""
                                + msg.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"")
                                + "\", startRow:0, endRow:0, totalRows:0}}");
            }
            response.flushBuffer();
            // response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            throw new ServletException(msg);
        }
        response.flushBuffer();
    }

    private static final String VAL_SUFFIX = "_VAL";

    private String normalizeAssoc(ServiceDescription serviceDesc, String assocName) {
        String retVal = assocName;
        if (assocName.endsWith(VAL_SUFFIX)) {
            String prefix = "";
            String realAssocName = assocName.substring(0, assocName.length() - VAL_SUFFIX.length());
            if (assocName.startsWith("+") || assocName.startsWith("-")) {
                prefix = realAssocName.substring(0, 1);
                realAssocName = realAssocName.substring(1);
            }
            try {
                Method method = serviceDesc.getExpectedClass().getMethod(makeGetMethodName(realAssocName),
                        (Class[]) null);
                Class watchClass = method.getReturnType();
                String refField = ds2Ref.get(watchClass.getName());
                retVal = prefix + realAssocName + "." + refField;
            } catch (NoSuchMethodException nsme) {
                // Ignore error, isString will stay false
            }
        }
        return retVal;
    }

    private String makeGetMethodName(String attributeName) {
        return GET_PREFIX + attributeName.substring(0, 1).toUpperCase() + attributeName.substring(1);
    }

    private void exportData(String dataSource, HttpServletResponse response) throws Exception {
        ServiceDescription serviceDesc = findService(dataSource);
        List<? extends Object> serviceResult = null;
        if (serviceDesc.getFindAll() != null) {
            Class<?>[] parameterTypes = serviceDesc.getFindAll().getParameterTypes();
            Object[] realParameters = new Object[parameterTypes.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                if (parameterTypes[i].equals(ServiceContext.class)) {
                    realParameters[i] = ServiceContextStore.get();
                } else if (parameterTypes[i].equals(PagingParameter.class)) {
                    realParameters[i] = PagingParameter.rowAccess(0, 1000);
                } else {
                    throw new IllegalArgumentException("findAll parameter #" + i + " for '" + dataSource
                            + "' has unknown type '" + parameterTypes[i].getName() + "'");
                }
            }
            if (serviceDesc.getFindAll().getReturnType().equals(PagedResult.class)) {
                PagedResult pagedServiceResult = (PagedResult) serviceDesc.getFindAll()
                        .invoke(serviceDesc.getInstance(), realParameters);
                serviceResult = pagedServiceResult.getValues();
            } else {
                serviceResult = (List<? extends Object>) serviceDesc.getFindAll().invoke(serviceDesc.getInstance(),
                        realParameters);
            }
        } else {
            throw new IllegalArgumentException("No fetch (findAll) operation available");
        }

        // application/vnd.oasis.opendocument.spreadsheet
        // application/vnd.ms-excel
        response.setContentType("application/vnd.oasis.opendocument.spreadsheet;charset=UTF-8");
        response.setHeader("Content-Disposition", "filename=" + dataSource + ".csv");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Expires", "-1");

        boolean first = true;
        List<Method> methods = new ArrayList<Method>();
        for (Object dataRow : serviceResult) {
            // prepare header
            if (first) {
                first = false;
                StringBuilder header = new StringBuilder();

                // Resolve getId method
                Method idMethod;
                try {
                    idMethod = dataRow.getClass().getMethod("getId", (Class<?>[]) null);
                } catch (Throwable th) {
                    idMethod = null;
                }
                if (idMethod != null) {
                    methods.add(idMethod);
                    header.append("Id " + READ_ONLY + ",");
                }

                // Iterate through methods
                for (Class curClass = dataRow.getClass(); curClass != Object.class; curClass = curClass
                        .getSuperclass()) {
                    Method[] declaredMethods = curClass.getDeclaredMethods();
                    for (Method m : declaredMethods) {
                        if (m.getName().startsWith(GET_PREFIX) && Modifier.isPublic(m.getModifiers())
                                && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0
                                && !"getKey".equals(m.getName()) && !"getCreatedDate".equals(m.getName())
                                && !"getCreatedBy".equals(m.getName()) && !"getLastUpdated".equals(m.getName())
                                && !"getLastUpdatedBy".equals(m.getName()) && !"getVersion".equals(m.getName())
                                && !"getUuid".equals(m.getName()) && !"getId".equals(m.getName())) {
                            Method setMethod;
                            try {
                                setMethod = curClass.getMethod(
                                        SET_PREFIX + m.getName().substring(GET_PREFIX.length()),
                                        new Class[] { m.getReturnType() });
                            } catch (Throwable ex) {
                                setMethod = null;
                            }
                            String readOnly = "";
                            if (setMethod == null) {
                                readOnly = READ_ONLY;
                            }
                            header.append(m.getName().substring(GET_PREFIX.length())).append(readOnly).append(",");
                            methods.add(m);
                        }
                    }
                }
                response.getWriter().println(header.length() > 0 ? header.substring(0, header.length() - 1) : "");
            }

            // Export data
            StringBuilder csvRow = new StringBuilder();
            for (Method m : methods) {
                Object value = m.invoke(dataRow, (Object[]) null);
                if (value == null) {
                    csvRow.append("null,");
                } else if (value instanceof Map) {
                    csvRow.append("HUH-MAPA");
                    csvRow.append(",");
                } else if (value instanceof Collection) {
                    csvRow.append("HUH-COLLECTION");
                    csvRow.append(",");
                } else if (value instanceof List) {
                    csvRow.append("HUH-LIST");
                    csvRow.append(",");
                } else if (value instanceof Object[]) {
                    csvRow.append("HUH-ARRAY");
                    csvRow.append(",");
                } else if (value instanceof Date) {
                    csvRow.append(dateFormat.format((Date) value));
                    csvRow.append(",");
                } else if (value instanceof Boolean) {
                    csvRow.append(((Boolean) value) ? "TRUE" : "FALSE");
                    csvRow.append(",");
                } else if (value instanceof Number) {
                    csvRow.append(value);
                    csvRow.append(",");
                } else if (value instanceof CharSequence) {
                    csvRow.append(Q).append(((CharSequence) value).toString().replaceAll(Q, Q + Q)).append(Q);
                    csvRow.append(",");
                } else if (value instanceof Enum) {
                    String enumName = ((Enum) value).name();
                    csvRow.append(Q + enumName + Q);
                    csvRow.append(",");
                } else {
                    csvRow.append("SOMETHING ELSE " + value.getClass());
                    csvRow.append(",");
                }
            }
            response.getWriter().println(csvRow.length() > 0 ? csvRow.substring(0, csvRow.length() - 1) : "");
        }
        return;
    }

    /* ###########################################################################
     * # Map request back to object
     * ########################################################################### */
    private void mapRequestToObj(HashMap<String, Object> data, Class expectedClass, Object obj) throws Exception {
        if (obj == null) {
            throw new ApplicationException("mapRequestToObj called on NULL obj", "ERR9001");
        }

        try {
            Method versionMethod = expectedClass.getMethod("getVersion", (Class<?>[]) null);
            Long objVersion = (Long) versionMethod.invoke(obj, (Object[]) null);
            String clientVersion = (String) data.get("version");
            if (objVersion != null && clientVersion != null) {
                try {
                    long clientVersionLong = Long.parseLong(clientVersion);
                    if (!objVersion.equals(clientVersionLong)) {
                        throw makeApplicationException("Can't save object", "ERR9016", (Serializable[]) null);
                    }
                } catch (NumberFormatException nfe) {
                    // Version from client isn't number - ignore
                }
            }
        } catch (NoSuchMethodException nme) {
            // No version control
        }

        Method[] methods = expectedClass.getMethods();
        for (Method m : methods) {
            Class<?>[] paramTypes = m.getParameterTypes();

            Class persistentClass = null;
            if (paramTypes.length == 1) {
                if (paramTypes[0].getAnnotation(Entity.class) != null) {
                    persistentClass = paramTypes[0];
                } else if (paramTypes[0].getAnnotation(Embeddable.class) != null) {
                    persistentClass = paramTypes[0];
                }
            }
            ServiceDescription srvParam = paramTypes.length == 1 ? findServiceByClassName(paramTypes[0].getName())
                    : null;
            if ((m.getName().startsWith(SET_PREFIX) && paramTypes.length == 1
                    && (paramTypes[0].isAssignableFrom(String.class) || paramTypes[0].equals(Integer.class)
                            || paramTypes[0].equals(Integer.TYPE) || paramTypes[0].equals(Long.class)
                            || paramTypes[0].equals(Long.TYPE) || paramTypes[0].equals(Float.class)
                            || paramTypes[0].equals(Float.TYPE) || paramTypes[0].equals(Boolean.class)
                            || paramTypes[0].equals(Boolean.TYPE) || paramTypes[0].equals(Double.class)
                            || paramTypes[0].equals(Double.TYPE) || paramTypes[0].equals(Date.class)
                            || Enum.class.isAssignableFrom(paramTypes[0])
                            || (srvParam != null && srvParam.getFindById() != null) || persistentClass != null))
                    || (m.getName().startsWith(GET_PREFIX) && paramTypes.length == 0
                            && (Set.class.isAssignableFrom(m.getReturnType())
                                    || List.class.isAssignableFrom(m.getReturnType())))) {
                String fldName;
                if (m.getName().startsWith(GET_TRANSLATE)) {
                    fldName = m.getName().substring(GET_TRANSLATE_LENGTH, GET_TRANSLATE_LENGTH + 1).toLowerCase()
                            + m.getName().substring(GET_TRANSLATE_LENGTH + 1);
                } else {
                    fldName = m.getName().substring(3, 4).toLowerCase() + m.getName().substring(4);
                }
                Object value = data.get(fldName);
                if (value == null) {
                    fldName = m.getName().substring(3);
                    value = data.get(fldName);
                }
                if (value != null) {
                    Object typedVal;
                    String val = null;
                    if (value instanceof String) {
                        val = (String) value;
                    }
                    log.log(Level.FINER, "        value = " + value);
                    if (m.getName().startsWith(GET_PREFIX) && paramTypes.length == 0
                            && (Set.class.isAssignableFrom(m.getReturnType())
                                    || List.class.isAssignableFrom(m.getReturnType()))) {
                        log.log(Level.FINER, "GET");

                        String attrName = m.getName().substring(3, 4).toLowerCase() + m.getName().substring(4);
                        Type[] actualTypeArguments = null;
                        Class iterClass = expectedClass;
                        while (iterClass != null) {
                            try {
                                Field field = iterClass.getDeclaredField(attrName);
                                ParameterizedType genericType = (ParameterizedType) field.getGenericType();
                                actualTypeArguments = genericType.getActualTypeArguments();
                                break;
                            } catch (NoSuchFieldException nsfe) {
                                // do nothing iterate again
                            }
                            iterClass = iterClass.getSuperclass();
                            iterClass = iterClass.equals(Object.class) ? null : iterClass;
                        }

                        if (actualTypeArguments != null && actualTypeArguments.length == 1
                                && actualTypeArguments[0] instanceof Class) {
                            Class assocClass = (Class) actualTypeArguments[0];
                            ServiceDescription assocService = findServiceByClassName(assocClass.getName());
                            Collection dbValueSet = (Collection) m.invoke(obj, (Object[]) null);
                            if (value == null || !(value instanceof HashMap)) {
                                log.log(Level.FINE, "No data for db property {0}", attrName);
                            } else if (assocService != null) {
                                HashMap<String, Object> guiValueMap = (HashMap<String, Object>) value;

                                ArrayList<Object> removeIt = new ArrayList<Object>();
                                Iterator dbIterator = dbValueSet.iterator();
                                while (dbIterator.hasNext()) {
                                    Object dbVal = dbIterator.next();
                                    String dbValId = getIdFromObj(dbVal);

                                    if (dbValId != null) {
                                        boolean wasMatchingGuiVal = false;
                                        ArrayList<String> removeKeys = new ArrayList<String>();
                                        for (String key : guiValueMap.keySet()) {
                                            Object object = guiValueMap.get(key);
                                            if (object instanceof HashMap) {
                                                Object guiValue = ((HashMap<String, Object>) object).get("id");
                                                if (guiValue.equals(dbValId)) {
                                                    removeKeys.add(key);
                                                    wasMatchingGuiVal = true;
                                                    mapRequestToObj((HashMap<String, Object>) guiValue, assocClass,
                                                            dbVal);
                                                    break;
                                                }
                                            } else if (object instanceof String) {
                                                // Association
                                                if (dbValId.equals(object)) {
                                                    removeKeys.add(key);
                                                    wasMatchingGuiVal = true;
                                                }
                                            } else {
                                                log.log(Level.WARNING, "Wrong object type from GUI under key {0}",
                                                        key);
                                            }
                                        }
                                        // Remove processed elements
                                        // Direct remove is firing concurrent modification exception
                                        for (String removeKey : removeKeys) {
                                            guiValueMap.remove(removeKey);
                                        }

                                        if (!wasMatchingGuiVal) {
                                            // Is not in list comming from GUI - delete
                                            removeIt.add(dbVal);
                                        }
                                    } else {
                                        log.log(Level.WARNING, "No ID in object {0}", dbVal);
                                    }
                                }
                                dbValueSet.removeAll(removeIt);

                                // Rest are new records
                                for (String key : guiValueMap.keySet()) {
                                    Object object = guiValueMap.get(key);
                                    if (object instanceof HashMap) {
                                        Object subObj = makeNewInstance(assocClass,
                                                (HashMap<String, Object>) object);
                                        mapRequestToObj((HashMap<String, Object>) object, assocClass, subObj);
                                        dbValueSet.add(subObj);
                                    } else if (object instanceof String) {
                                        // Association
                                        try {
                                            Long id = new Long((String) object);
                                            Object assocObj = assocService.getFindById().invoke(
                                                    assocService.getInstance(), ServiceContextStore.get(), id);
                                            if (assocObj != null) {
                                                dbValueSet.add(assocObj);
                                            } else {
                                                log.log(Level.WARNING,
                                                        "Object with ID {0} not availabla via service {1}",
                                                        new Object[] { id, assocService.getName() });
                                            }
                                        } catch (Exception ex) {
                                            log.log(Level.WARNING, "No ID parsable from value {0} under key {1}",
                                                    new Object[] { object, key });
                                        }
                                    } else {
                                        log.log(Level.WARNING, "Wrong sub type {0}", attrName);
                                    }
                                }
                            } else if (assocClass != null) {
                                HashMap<String, Object> guiValueMap = (HashMap<String, Object>) value;

                                ArrayList<Object> removeIt = new ArrayList<Object>();
                                Iterator dbIterator = dbValueSet.iterator();
                                while (dbIterator.hasNext()) {
                                    Object dbVal = dbIterator.next();
                                    String dbValId = getIdFromObj(dbVal);

                                    if (dbValId != null) {
                                        Object matchingGuiVal = null;
                                        for (String key : guiValueMap.keySet()) {
                                            Object object = guiValueMap.get(key);
                                            if (object instanceof HashMap) {
                                                HashMap<String, Object> guiVal = (HashMap<String, Object>) object;
                                                if (dbValId.equals(guiVal.get("id"))) {
                                                    guiValueMap.remove(key);
                                                    matchingGuiVal = guiVal;
                                                    break;
                                                }
                                            } else {
                                                log.log(Level.WARNING, "Wrong object type from GUI under key {0}",
                                                        key);
                                            }
                                        }
                                        if (matchingGuiVal != null) {
                                            // Coming from GUI - update
                                            mapRequestToObj((HashMap<String, Object>) matchingGuiVal, assocClass,
                                                    dbVal);
                                        } else {
                                            // Not in GUI - delete
                                            removeIt.add(dbVal);
                                        }
                                    } else {
                                        log.log(Level.WARNING, "No ID in object {0}", dbVal);
                                    }
                                }
                                dbValueSet.removeAll(removeIt);

                                // Rest are new records
                                for (String key : guiValueMap.keySet()) {
                                    Object object = guiValueMap.get(key);
                                    if (object instanceof HashMap) {
                                        Object subObj = makeNewInstance(assocClass,
                                                (HashMap<String, Object>) object);
                                        mapRequestToObj((HashMap<String, Object>) object, assocClass, subObj);
                                        dbValueSet.add(subObj);
                                    } else {
                                        log.log(Level.WARNING, "Wrong sub type {0}", attrName);
                                    }
                                }
                            }
                        } else {
                            log.log(Level.WARNING, "No DB mapping or not of collection type: {0}", attrName);
                        }
                        typedVal = null;
                    } else if (paramTypes[0].isAssignableFrom(String.class)) {
                        typedVal = val;
                    } else if (paramTypes[0].equals(Integer.class) || paramTypes[0].equals(Integer.TYPE)) {
                        typedVal = Integer.parseInt(val);
                    } else if (paramTypes[0].equals(Long.class) || paramTypes[0].equals(Long.TYPE)) {
                        typedVal = Long.parseLong(val);
                    } else if (paramTypes[0].equals(Double.class) || paramTypes[0].equals(Double.TYPE)) {
                        typedVal = Double.parseDouble(val);
                    } else if (paramTypes[0].equals(Float.class) || paramTypes[0].equals(Float.TYPE)) {
                        typedVal = Float.parseFloat(val);
                    } else if (paramTypes[0].equals(Boolean.class) || paramTypes[0].equals(Boolean.TYPE)) {
                        typedVal = "true".equalsIgnoreCase(val) || "t".equalsIgnoreCase(val)
                                || "y".equalsIgnoreCase(val);
                    } else if (paramTypes[0].isAssignableFrom(Date.class)) {
                        typedVal = dateFormat.parse(val);
                    } else if (Enum.class.isAssignableFrom(paramTypes[0])) {
                        try {
                            Method fromValueMethod = paramTypes[0].getMethod("fromValue", String.class);
                            typedVal = fromValueMethod.invoke(null, val);
                        } catch (Exception ex) {
                            typedVal = null;
                        }

                        try {
                            if (typedVal == null) {
                                Method valueOfMethod = paramTypes[0].getMethod("valueOf", String.class);
                                typedVal = valueOfMethod.invoke(null, val);
                            }
                        } catch (Exception ex) {
                            typedVal = null;
                        }
                    } else if (persistentClass != null && persistentClass.equals(FileUpload.class)) {
                        FileItem fileItem = uploadServlet.getFileItem(sessionId.get(), fldName, val);
                        if (fileItem != null) {
                            typedVal = fileUploadService.uploadFile(ServiceContextStore.get(), fileItem.getName(),
                                    fileItem.getContentType(), fileItem.getInputStream());
                        } else {
                            typedVal = null;
                        }
                    } else if (srvParam != null && srvParam.getFindById() != null) {
                        if (value instanceof HashMap) {
                            HashMap<String, Object> embeddedObj = (HashMap<String, Object>) value;
                            typedVal = srvParam.getFindById().invoke(srvParam.getInstance(),
                                    ServiceContextStore.get(), new Long((String) embeddedObj.get("id")));
                            mapRequestToObj(embeddedObj, srvParam.getExpectedClass(), typedVal);
                        } else {
                            try {
                                Long parsedId = new Long(val);
                                typedVal = srvParam.getFindById().invoke(srvParam.getInstance(),
                                        ServiceContextStore.get(), parsedId);
                            } catch (NumberFormatException nfe) {
                                // wrong value
                                typedVal = null;
                            }
                        }
                    } else if (persistentClass != null) {
                        String getMethodName = "g" + m.getName().substring(1);
                        try {
                            Method getMethod = obj.getClass().getMethod(getMethodName, (Class[]) null);
                            typedVal = getMethod.invoke(obj, (Object[]) null);
                        } catch (NoSuchMethodException nsme) {
                            typedVal = null;
                        }
                        if (typedVal == null) {
                            typedVal = makeNewInstance(persistentClass, (HashMap<String, Object>) value);
                        }
                        mapRequestToObj((HashMap<String, Object>) value, typedVal.getClass(), typedVal);
                    } else {
                        log.log(Level.WARNING, "Can't convert value for: {0}.{1} ({2})", new Object[] {
                                expectedClass.getName(), m.getName(),
                                (paramTypes.length == 1 ? paramTypes[0].getName() : paramTypes.toString()) });
                        typedVal = null;
                    }
                    if (typedVal != null) {
                        m.invoke(obj, typedVal);
                    }
                }
            } else if (m.getName().startsWith(SET_PREFIX)) {
                log.log(Level.WARNING, "Unusable setter method: {0}.{1} ({2})",
                        new Object[] { expectedClass.getName(), m.getName(),
                                (paramTypes.length == 1 ? paramTypes[0].getName() : paramTypes.toString()) });
            }
        }
    }

    private HashMap<String, Object> unifyRequest(HttpServletRequest request) {
        HashMap<String, Object> hashData = new HashMap<String, Object>();

        Enumeration parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String paramName = (String) parameterNames.nextElement();
            if ("_oldValues".equals(paramName)) {
                continue;
            }

            String reqVal = request.getParameter(paramName);
            if ("_sortBy".equals(paramName)) {
                hashData.put("sortBy", reqVal);
            } else {
                hashData.put(paramName, parseJson(reqVal));
            }
        }
        return hashData;
    }

    private Object parseJson(String value) {
        Object result = value;
        if (value.startsWith("{") || value.startsWith("[")) {
            try {
                CommonTree tree = JavaScriptDataParser.parseString(value);
                result = mapJsDataToHash(tree);
            } catch (Exception e) {
                log.log(Level.WARNING, "Can not parse value to object " + value);
                result = value;
            }
        } else if ("null".equals(value)) {
            result = null;
        }
        return result;
    }

    private HashMap<String, Object> mapJsDataToHash(CommonTree from) throws Exception {
        HashMap<String, Object> retVal = new HashMap<String, Object>();
        if (from == null || from.getChildCount() == 0) {
            // Do nothing
        } else if (from.getType() == JavaScriptDataParser.OBJECT) {
            for (int i = 0; i < from.getChildCount(); i++) {
                Object tokenName = from.getChild(i);
                CommonTree token = (CommonTree) tokenName;
                if (token.getType() == JavaScriptDataParser.ELEMENT) {
                    CommonTree nameToken = (CommonTree) token.getFirstChildWithType(JavaScriptDataParser.Name); // e.g. name:"vendelin"
                    String name;
                    if (nameToken == null) {
                        // try to find token in case that the attribute is wrapped with double quote e.g. "name":"vendelin"
                        nameToken = (CommonTree) token.getFirstChildWithType(JavaScriptDataParser.String);
                        name = nameToken.getText();
                        name = name.substring(1, name.length() - 1);
                    } else {
                        name = nameToken.getText();
                    }
                    CommonTree value = (CommonTree) token.getChild(1);
                    retVal.put(name, mapElementValToObject(value));
                } else {
                    throw new Exception("Wrong AST type " + JavaScriptDataParser.tokenNames[token.getType()]
                            + " I need ELEMENT");
                }
            }
        } else {
            throw new Exception(
                    "Wrong AST type " + JavaScriptDataParser.tokenNames[from.getType()] + " send to mapper");
        }

        return retVal;
    }

    private Object mapElementValToObject(CommonTree value) throws Exception {
        Object retVal;
        if (value.getType() == JavaScriptDataParser.STRING) {
            String result = value.getChild(0).getText();
            retVal = result.substring(1, result.length() - 1);
        } else if (value.getType() == JavaScriptDataParser.INTEGER || value.getType() == JavaScriptDataParser.DOUBLE
                || value.getType() == JavaScriptDataParser.BOOLEAN) {
            retVal = value.getChild(0).getText();
        } else if (value.getType() == JavaScriptDataParser.DATE) {
            // in format 'new Date(132132123123)'
            String jsDateString = value.getChild(0).getText();
            int leftBracket = jsDateString.indexOf("(");
            int rightBracket = jsDateString.indexOf(")");
            if (leftBracket != -1 && rightBracket != -1 && leftBracket < rightBracket) {
                try {
                    long time = Long.parseLong(jsDateString.substring(leftBracket + 1, rightBracket));
                    retVal = new Date(time);
                } catch (NumberFormatException nfe) {
                    retVal = null;
                }
            } else {
                retVal = null;
            }
        } else if (value.getType() == JavaScriptDataParser.NULL) {
            retVal = null;
        } else if (value.getType() == JavaScriptDataParser.ARRAY) {
            ArrayList<Object> arrayVal = new ArrayList();
            for (int i = 0; i < value.getChildCount(); i++) {
                Object arrayElem = value.getChild(i);
                arrayVal.add(mapElementValToObject((CommonTree) ((CommonTree) arrayElem).getChild(0)));
            }
            retVal = arrayVal;
        } else if (value.getType() == JavaScriptDataParser.OBJECT) {
            if (value.getChildCount() != 0) {
                retVal = mapJsDataToHash(value);
            } else {
                retVal = null;
            }
        } else {
            throw new Exception(
                    "Wrong AST type " + JavaScriptDataParser.tokenNames[value.getType()] + " can't map");
        }
        return retVal;
    }

    /* ###########################################################################
     * # Send response
     * ########################################################################### */
    private void sendResponse(PrintWriter output, PagedResult<?> pResult) throws IllegalArgumentException {
        List<?> inputData = pResult.getValues() == null ? new ArrayList<Object>() : pResult.getValues();
        int showEndRow = pResult.getEndRow() < 1 ? 0 : pResult.getEndRow();

        int totalRows;
        if (pResult.isTotalCounted()) {
            totalRows = pResult.getTotalRows();
        } else if (pResult.isAddionalResultCounted()) {
            totalRows = pResult.getEndRow() + pResult.getAdditionalResultRows();
        } else {
            totalRows = pResult.getStartRow() + pResult.getValues().size() + 1;
        }

        log.log(Level.FINE, "Sending JSON response from {0} to {1} of size {2}",
                new Object[] { pResult.getStartRow(), showEndRow, inputData.size() });
        output.write("{response: { status:0, startRow:" + pResult.getStartRow() + ", endRow:" + showEndRow
                + ", totalRows:" + totalRows + ", data:[");

        String delim = "";
        for (Object obj : inputData) {
            output.write(delim);

            // Prepare string representation and write to out
            String objString = mapObjToOutput(obj, DEF_DEPTH, new Stack(), false, false);
            log.log(Level.FINER, "    >>>>>>>>>>> {0}", objString);
            output.write(objString);
            delim = ", ";
        }
        output.write(" ] } }");
    }

    private void sendResponse(PrintWriter output, int startRow, int endRow, Collection<? extends Object> inputData)
            throws IllegalArgumentException {
        inputData = inputData == null ? new ArrayList<Object>() : inputData;
        int showEndRow = endRow < 1 ? 0 : endRow - 1;
        log.log(Level.FINE, "Sending JSON response from {0} to {1} of size {2}",
                new Object[] { startRow, showEndRow, inputData.size() });
        output.write("{response: { status:0, startRow:" + startRow + ", endRow:" + showEndRow + ", totalRows:"
                + inputData.size() + ", data:[");

        // If inputData is Set convert to array
        Object[] setData = null;
        if (inputData instanceof Set) {
            setData = inputData.toArray();
        } else if (!(inputData instanceof List)) {
            throw new IllegalArgumentException("Datatype " + inputData.getClass().getName()
                    + " is not supported (only implementations of java.util.Set and java.util.List)");
        }
        for (int i = startRow; i < endRow; i++) {
            // Get object from collection
            Object obj;
            if (inputData instanceof Set) {
                obj = setData[i];
            } else {
                obj = ((List<? extends Object>) inputData).get(i);
            }

            // Write out separator except first
            if (i != startRow) {
                output.write(", ");
            }

            // Prepare string representation and write out
            String objString = mapObjToOutput(obj, DEF_DEPTH, new Stack(), false, false);
            log.log(Level.FINER, "    >>>>>>>>>>> {0}", objString);
            output.write(objString);
        }
        output.write(" ] } }");
    }

    private static final String Q = "\"";

    private String mapObjToOutput(Object obj, int maxDepth, Stack<Object> serStack, boolean useGwtArray,
            boolean translateValue) {
        if (serStack.size() == 0) {
            log.log(Level.FINER, "Serializing START {0}", obj);
        }

        // Avoid recursion
        if (serStack.size() == maxDepth && !(obj instanceof Date || obj instanceof Number || obj instanceof Boolean
                || obj instanceof CharSequence || obj instanceof Enum)) {
            String objId = getIdFromObj(obj);
            return objId == null ? Q + Q : objId;
        }
        if (serStack.contains(obj)) {
            return getIdFromObj(obj);
            // return Q+"ref: "+obj.getClass().getName()+"@"+obj.hashCode()+Q;
        }
        serStack.push(obj);

        String startArray = useGwtArray ? "$wnd.Array.create([" : "[";
        String endArray = useGwtArray ? "])" : "]";

        StringBuilder recordData = new StringBuilder();
        if (obj == null) {
            recordData.append("null");
        } else if (obj instanceof Map) {
            recordData.append("{");
            Map objMap = (Map) obj;
            String delim = "";
            for (Object objKey : objMap.keySet()) {
                recordData.append(delim).append(objKey).append(":")
                        .append(mapObjToOutput(objMap.get(objKey), maxDepth, serStack, useGwtArray, false));
                delim = " , ";
            }
            recordData.append("}");
        } else if (obj instanceof Collection) {
            recordData.append(startArray);
            Collection objSet = (Collection) obj;
            String delim = "";
            for (Object objVal : objSet) {
                recordData.append(delim).append(mapObjToOutput(objVal, maxDepth, serStack, useGwtArray, false));
                delim = " , ";
            }
            recordData.append(endArray);
        } else if (obj instanceof List) {
            recordData.append(startArray);
            List objList = (List) obj;
            String delim = "";
            for (Object objVal : objList) {
                recordData.append(delim).append(mapObjToOutput(objVal, maxDepth, serStack, useGwtArray, false));
                delim = " , ";
            }
            recordData.append(endArray);
        } else if (obj instanceof Object[]) {
            recordData.append(startArray);
            Object[] objArr = (Object[]) obj;
            String delim = "";
            for (Object objVal : objArr) {
                recordData.append(delim).append(mapObjToOutput(objVal, maxDepth, serStack, useGwtArray, false));
                delim = " , ";
            }
            recordData.append(endArray);
        } else if (obj instanceof Date) {
            Date objDate = (Date) obj;
            // recordData.append(Q+dateTimeFormat.format(objDate)+Q);
            recordData.append("new Date(" + objDate.getTime() + ")");
        } else if (obj instanceof Boolean) {
            recordData.append(obj);
        } else if (obj instanceof Number) {
            recordData.append(obj);
        } else if (obj instanceof CharSequence) {
            String strObj = obj.toString();
            if (strObj.startsWith(Main.JAVASCRIPT_PREFIX) && useGwtArray) {
                recordData.append(" ").append(strObj.substring(Main.JAVASCRIPT_PREFIX.length()));
            } else if (strObj.startsWith("function") && useGwtArray) {
                recordData.append(" ").append(strObj);
            } else {
                strObj = translateValue ? translate(strObj) : strObj;
                String escapeString = strObj.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"")
                        .replaceAll("\\r", "\\\\r").replaceAll("\\n", "\\\\n");
                recordData.append(Q + escapeString + Q);
            }
        } else if (obj instanceof Enum) {
            String val = ((Enum) obj).name();
            if (useGwtArray) {
                try {
                    Method getValMethod = obj.getClass().getMethod("getValue", (Class[]) null);
                    val = (String) getValMethod.invoke(obj, (Object[]) null);
                } catch (Exception e) {
                    // no method getValue
                }
            }
            recordData.append(Q + val + Q);
        } else {
            String className = obj.getClass().getName();
            ServiceDescription serviceForClass = findServiceByClassName(className);
            log.log(Level.FINER, "Serializing class {0}", className);
            if (serStack.size() > 2 && serviceForClass != null) {
                recordData.append(getIdFromObj(obj));
            } else {
                // Use reflection
                recordData.append("{");
                String delim = "";
                String jsonPostfix = null;
                Method[] methods = obj.getClass().getMethods();
                for (Method m : methods) {
                    boolean translateThisValue = false;
                    String mName;
                    if (m.getName().startsWith(GET_TRANSLATE)) {
                        translateThisValue = true;
                        mName = m.getName().substring(GET_TRANSLATE_LENGTH);
                    } else if (m.getName().startsWith("is")) {
                        mName = m.getName().substring(2);
                    } else {
                        mName = m.getName().substring(3);
                    }

                    if (mName.length() > 1 && Character.isLowerCase(mName.charAt(1))) {
                        mName = mName.substring(0, 1).toLowerCase() + mName.substring(1);
                    }

                    if (m.getName().startsWith("getJsonPostfix") && m.getParameterTypes().length == 0
                            && String.class.equals(m.getReturnType())) {
                        try {
                            jsonPostfix = (String) m.invoke(obj, new Object[] {});
                        } catch (Throwable e) {
                            log.log(Level.FINE, "Mapping error", e);
                        }
                    } else if (!m.getDeclaringClass().getName().startsWith("org.hibernate")
                            && m.getDeclaringClass() != Object.class && m.getDeclaringClass() != Class.class
                            && (m.getName().startsWith("get") || m.getName().startsWith("is"))
                            && m.getParameterTypes().length == 0 && m.getReturnType() != null
                            && !isHiddenField(m.getDeclaringClass().getName(), mName)) {
                        log.log(Level.FINEST, "Reflection invoking name={0} declaringClass={1} on {2}[{3}]",
                                new Object[] { m.getName(), m.getDeclaringClass(), obj, obj.getClass() });
                        try {
                            Object result = m.invoke(obj, new Object[] {});
                            if (result != null) {
                                mName = mName.startsWith("xxx") ? mName.substring(3) : mName;
                                String resultClassName = AopUtils.getTargetClass(result).getName();
                                String idVal = getIdFromObj(result);
                                String valStr;
                                if (findServiceByClassName(resultClassName) != null && idVal != null) {
                                    recordData.append(delim).append(mName).append(":").append(idVal);
                                    String refField = ds2Ref.get(resultClassName);
                                    if (refField != null) {
                                        Object realVal = getValFromObj(refField, result);
                                        valStr = realVal == null ? Q + Q : Q + realVal + Q;
                                    } else {
                                        valStr = Q + "UNKNOWN" + Q;
                                    }
                                    mName = mName + "_VAL";
                                    delim = ", ";
                                } else {
                                    valStr = mapObjToOutput(result, maxDepth, serStack, useGwtArray,
                                            translateThisValue);
                                }
                                recordData.append(delim).append(mName).append(":").append(valStr);
                                delim = ", ";
                            }
                        } catch (Throwable e) {
                            log.log(Level.FINE, "Mapping error", e);
                        }
                    }
                }

                if (jsonPostfix != null) {
                    recordData.append(delim).append(jsonPostfix).append("}");
                } else {
                    recordData.append("}");
                }
            }
        }
        serStack.pop();
        return recordData.toString();
    }

    private String getIdFromObj(Object obj) {
        String retVal = null;

        Method getIdMethod = null;
        try {
            getIdMethod = obj.getClass().getMethod("getId", new Class<?>[] {});
        } catch (Exception e) {
            // Ignore, use reflection
        }

        if (getIdMethod != null) {
            try {
                retVal = "" + getIdMethod.invoke(obj, new Object[] {});
            } catch (Exception e) {
                log.log(Level.FINE, "Getting ID error", e);
            }
        }
        return retVal;
    }

    private Object getValFromObj(String propertyName, Object obj) {
        Object retVal = null;

        String methodName = GET_PREFIX + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
        Method getValMethod = null;
        try {
            getValMethod = obj.getClass().getMethod(methodName, new Class<?>[] {});
        } catch (Exception e) {
            // Ignore, use reflection
        }

        if (getValMethod != null) {
            try {
                retVal = getValMethod.invoke(obj, new Object[] {});
            } catch (Exception e) {
                log.log(Level.FINE, "Getting VAL error", e);
            }
        }
        return retVal;
    }

    private String translate(String name) {
        String key = getLanguage() + "." + name;
        String tran;
        try {
            tran = propertyService.getProperty(key);

            if (tran == null && name != null) {
                String propName = name;
                propName = propName.replaceAll("(\\p{Upper}+)", " $1");
                if (propName.startsWith(" ")) {
                    propName = propName.substring(1);
                }
                tran = propName.substring(0, 1).toUpperCase() + propName.substring(1);
                propertyService.getProperty(key, tran);
            }
        } catch (Exception e) {
            tran = key;
            log.log(Level.WARNING, "Can not transalate message " + name, e);
        }

        return tran;
    }

    public String listServices() {
        StringBuilder result = new StringBuilder("(");
        boolean wasService = false;

        HashMap<String, List<ServiceDescription>> categories = new HashMap<String, List<ServiceDescription>>();
        for (ServiceDescription serviceDesc : getServiceDescription()) {
            String category = serviceDesc.getCategory();
            List<ServiceDescription> categoryList = categories.get(category);
            if (categoryList == null) {
                categoryList = new ArrayList<ServiceDescription>();
                categories.put(category, categoryList);
            }
            categoryList.add(serviceDesc);
        }

        Set<String> categorySet = categories.keySet();
        for (String category : categorySet) {
            List<ServiceDescription> serviceCategoryList = categories.get(category);
            String delim = (wasService ? "], " : "[") + "[ " + Q + translate(category) + Q + ", ";
            for (ServiceDescription serviceDesc : serviceCategoryList) {
                String beanName = serviceDesc.getName();
                String[] accRoles = serviceDesc.getVisibleForRoles();
                boolean accessDenied = true;
                if (accRoles != null && accRoles.length > 0) {
                    for (String accRole : accRoles) {
                        if (ServiceContextStore.get().isUserInRole(accRole)) {
                            accessDenied = false;
                            break;
                        }
                    }
                } else {
                    accessDenied = false;
                }

                if (!accessDenied) {
                    log.log(Level.INFO, "Service {0} accesible", beanName);

                    // Bean name
                    wasService = true;
                    result.append(delim);
                    result.append(Q).append(beanName).append(Q);
                    delim = ", ";

                    // Service description
                    result.append(delim);
                    if (serviceDesc.getOtherMethods().size() > 0) {
                        result.append("(")
                                .append(mapObjToOutput(filterMethods(serviceDesc.getOtherMethods().values(),
                                        ServiceContextStore.get()), 4, new Stack<Object>(), false, false))
                                .append(")");
                    } else {
                        result.append("([])");
                    }

                    // Bean translated name
                    result.append(delim);
                    result.append(Q).append(translate(
                            beanName.endsWith("Service") ? beanName.substring(0, beanName.length() - 7) : beanName))
                            .append(Q);
                } else {
                    log.log(Level.INFO, "Service {0} access denied", beanName);
                }
            }
        }

        result.append("] ] )");
        log.log(Level.FINE, "Menu: {0}", result);
        return result.toString();
    }

    private List<MethodDescription> filterMethods(Collection<MethodDescription> methods,
            ServiceContext serviceContext) {
        ArrayList<MethodDescription> result = new ArrayList<MethodDescription>();
        for (MethodDescription m : methods) {
            boolean isOK = true;
            if (m.getIfRole() != null && m.getIfRole().length > 0) {
                isOK = false;
                for (String role : m.getIfRole()) {
                    if (serviceContext.isUserInRole(role)) {
                        isOK = true;
                        break;
                    }
                }
            }

            if (isOK) {
                result.add(m);
            }
        }
        return result;
    }

    long serviceTimestamp = 0;
    long serviceTimeout = 1000 * 60; // 60 sec

    private List<ServiceDescription> getServiceDescription() {
        return serviceRegistry.getServices(ServiceContextStore.get()); // caching is on service side
    }

    public ServiceDescription findService(String serviceName) {
        ServiceDescription service = null;
        for (ServiceDescription serviceDescription : getServiceDescription()) {
            if (serviceDescription.getName().equals(serviceName)) {
                service = serviceDescription;
                break;
            }
        }
        return service;
    }

    public ServiceDescription findServiceByClassName(String className) {
        ServiceDescription retVal = null;
        for (ServiceDescription serviceDesc : getServiceDescription()) {
            if (className.equals(serviceDesc.getExpectedClass().getName())) {
                retVal = serviceDesc;
                break;
            }
        }
        return retVal;
    }

    /* ###########################################################################
     * # Print request helper
     * ########################################################################### */
    private void printRequest(HttpServletRequest request) throws IOException {
        Enumeration<String> paramList = request.getParameterNames();
        StringBuilder out = new StringBuilder();
        out.append("<<<<<<<<<<<< Request ");
        while (paramList.hasMoreElements()) {
            String param = paramList.nextElement();
            out.append(" :: " + param + "=" + request.getParameter(param));
        }
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));
        char[] dataBuff = new char[1024];
        int readBytes = bufferedReader.read(dataBuff);
        while (readBytes != -1) {
            out.append("\nData: " + new String(dataBuff, 0, readBytes));
            readBytes = bufferedReader.read(dataBuff);
        }
        log.log(Level.INFO, "{0}", out);
    }

    private Object makeNewInstance(Class expectedClass, HashMap<String, Object> jsData)
            throws ApplicationException {
        Object val = null;
        try {
            Constructor[] constructors = expectedClass.getConstructors();
            for (int k = 0; k < constructors.length; k++) {
                Constructor constructor = constructors[k];
                Annotation[][] constrAnnot = constructor.getParameterAnnotations();
                String[] paramNames = new String[constrAnnot.length];
                int annotCount = 0;
                for (int i = 0; i < constrAnnot.length; i++) {
                    Annotation[] annotations = constrAnnot[i];
                    for (int j = 0; j < annotations.length; j++) {
                        Annotation annotation = annotations[j];
                        if (annotation instanceof Name) {
                            paramNames[i] = ((Name) annotation).value();
                            paramNames[i] = paramNames[i].startsWith("xxx") ? paramNames[i].substring(3)
                                    : paramNames[i];
                            annotCount++;
                            break;
                        }
                    }
                }
                if (annotCount != constrAnnot.length) {
                    continue;
                }
                Object[] params = prepareMethodParam(jsData, false, paramNames, constructor.getParameterTypes());
                if (params != null) {
                    val = constructor.newInstance(params);
                    break;
                }
            }

            if (val == null) {
                throw new Exception("Can't create instance of class " + expectedClass.getName());
            }
        } catch (Exception ex) {
            throw new ApplicationException(ex.getMessage(), "ERR9001", ex);
        }

        return val;
    }

    public Object[] prepareMethodParam(HashMap<String, ? extends Object> jsData, boolean applyNull,
            String[] paramNames, Class[] parameterTypes) throws ApplicationException {
        boolean hasMapping = false;
        Object[] realParams = new Object[paramNames.length];
        try {
            hasMapping = true;
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> paramType = parameterTypes[i];
                //Object paramObject=jsData.get(paramNames[i]) == null ? jsData : jsData.get(paramNames[i]);
                Object paramObject = jsData.get(paramNames[i]);

                if (paramType.equals(ServiceContext.class)) {
                    realParams[i] = ServiceContextStore.get();
                } else if (paramType.equals(PagingParameter.class)) {
                    String sRow = (String) jsData.get("_startRow");
                    int startRow = sRow == null ? 0 : Integer.parseInt(sRow);
                    String eRow = (String) jsData.get("_endRow");
                    int endRow = eRow == null ? 0 : Integer.parseInt(eRow);

                    realParams[i] = PagingParameter.rowAccess(startRow, endRow, LOOK_AHEAD);
                } else if (paramType.equals(String.class)) {
                    realParams[i] = (String) paramObject;
                } else if (paramType.equals(Integer.class) || paramType.equals(Integer.TYPE)) {
                    realParams[i] = paramObject == null ? null
                            : Integer.parseInt(((String) paramObject).replaceAll("\"", ""));
                } else if (paramType.equals(Long.class) || paramType.equals(Long.TYPE)) {
                    realParams[i] = paramObject == null ? null
                            : Long.parseLong(((String) paramObject).replaceAll("\"", ""));
                } else if (paramType.equals(Double.class) || paramType.equals(Double.TYPE)) {
                    realParams[i] = paramObject == null ? null
                            : Double.parseDouble(((String) paramObject).replaceAll("\"", ""));
                } else if (paramType.equals(Date.class)) {
                    realParams[i] = paramObject == null ? null : dateFormat.parse((String) paramObject);
                } else if (paramType.equals(Boolean.class) || paramType.equals(Boolean.TYPE)) {
                    realParams[i] = ("true".equals(paramObject));
                } else if (paramObject instanceof HashMap || (paramObject == null && jsData instanceof HashMap)) {
                    HashMap<String, Object> objData = (HashMap<String, Object>) (paramObject == null ? jsData
                            : paramObject);
                    ServiceDescription service = findServiceByClassName(paramType.getName());
                    if (objData.get("id") != null && service != null) {
                        Long id = Long.parseLong((String) objData.get("id"));
                        realParams[i] = service.getFindById().invoke(service.getInstance(),
                                ServiceContextStore.get(), id);
                    } else {
                        realParams[i] = makeNewInstance(paramType, objData);
                    }
                    mapRequestToObj(objData, paramType, realParams[i]);
                } else if (findServiceByClassName(paramType.getName()) != null) {
                    ServiceDescription service = findServiceByClassName(paramType.getName());
                    Long recId = Long.parseLong((String) paramObject);
                    realParams[i] = service.getFindById().invoke(service.getInstance(), ServiceContextStore.get(),
                            recId);
                } else if (Enum.class.isAssignableFrom(paramType)) {
                    try {
                        Method fromValueMethod = paramType.getMethod("fromValue", String.class);
                        realParams[i] = fromValueMethod.invoke(null, paramObject);
                    } catch (Exception ex) {
                    }

                    try {
                        if (realParams[i] == null) {
                            Method valueOfMethod = paramType.getMethod("valueOf", String.class);
                            realParams[i] = valueOfMethod.invoke(null, paramObject);
                        }
                    } catch (Exception ex) {
                    }

                    if (realParams[i] == null) {
                        hasMapping = false;
                        break;
                    }
                } else if (applyNull) {
                    realParams[i] = null;
                } else {
                    hasMapping = false;
                    break;
                }
            }
        } catch (ApplicationException appEx) {
            throw appEx;
        } catch (Exception ex) {
            throw new ApplicationException(ex.getMessage(), "ERR9001", ex);
        }

        return hasMapping ? realParams : null;
    }

    private String[] hiddenFields = { "uuid", "key" };

    private boolean isHiddenField(String owningClass, String fieldName) {
        String fullName = owningClass + "." + fieldName;
        for (String hiddenField : hiddenFields) {
            if (hiddenField.equals(fieldName) || hiddenField.equals(fullName)) {
                return true;
            }
        }
        return false;
    }

    private String getLanguage() {
        String lang = (String) ServiceContextStore.get().getProperty("lang");
        if (lang == null) {
            lang = "en";
        }
        return lang;
    }

    private ApplicationException makeApplicationException(String errorCode, String message,
            Serializable... parameters) {
        ApplicationException ex = new ApplicationException(errorCode, message);
        ex.setMessageParameters(parameters);

        return ex;
    }

    static String serviceUrl = null;

    private String getServiceUrl(HttpServletRequest request) {
        if (serviceUrl == null) {
            serviceUrl = request.getContextPath() + "/service";
        }
        return serviceUrl;
    }

    /* ###########################################################################
     * # Methods for IoC
     * ########################################################################### */
    ServiceRegistryService serviceRegistry = null;

    public ServiceRegistryService getServiceRegistry() {
        return serviceRegistry;
    }

    public void setServiceRegistry(ServiceRegistryService serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    GuiDataSourceService guiDataSourceService = null;

    public GuiDataSourceService getGuiDataSourceService() {
        return guiDataSourceService;
    }

    public void setGuiDataSourceService(GuiDataSourceService guiDataSourceService) {
        this.guiDataSourceService = guiDataSourceService;
    }

    private PropertySupportFacade propertyService;

    public void setPropertySupport(PropertySupportFacade propertySupport) {
        this.propertyService = propertySupport;
    }

    public PropertySupportFacade getPropertySupport() {
        return propertyService;
    }

    @Autowired
    public void setScheduler(Scheduler scheduler) {
        try {
            scheduler.getContext().put("serviceInvoker", this);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    public Object invokeService(String serviceName, String methodName, Object[] params) throws Exception {
        Object valObj = null;
        ServiceDescription serviceDesc = findService(serviceName);
        if (serviceDesc != null) {
            MethodDescription methodDescription = serviceDesc.getOtherMethods().get(methodName);
            if (methodDescription != null) {
                valObj = methodDescription.method.invoke(serviceDesc.getInstance(), params);
            }
        }

        return valObj;
    }

    public Object invokeService(String serviceName, String methodName, HashMap<String, String> params,
            boolean parseParams) throws Exception {
        Object valObj = null;
        ServiceDescription serviceDesc = findService(serviceName);
        if (serviceDesc != null) {
            MethodDescription methodDescription = serviceDesc.getOtherMethods().get(methodName);
            HashMap<String, ? super String> jsData;
            if (parseParams) {
                HashMap<String, Object> newJsData = new HashMap<String, Object>();
                for (String key : params.keySet()) {
                    newJsData.put(key, parseJson(params.get(key)));
                }
                jsData = newJsData;
            } else {
                jsData = params;
            }
            Object[] methodParam = prepareMethodParam(jsData, true, methodDescription.parameterNames,
                    methodDescription.method.getParameterTypes());
            valObj = methodDescription.method.invoke(serviceDesc.getInstance(), methodParam);
        }
        return valObj;
    }
}