/* * Copyright 2005 Joe Walker * * 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.directwebremoting.jsonrpc.io; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.directwebremoting.extend.Call; import org.directwebremoting.extend.ConverterManager; import org.directwebremoting.extend.Creator; import org.directwebremoting.extend.CreatorManager; import org.directwebremoting.json.parse.JsonParseException; import org.directwebremoting.json.parse.impl.StatefulJsonDecoder; import org.directwebremoting.util.JavascriptUtil; import static javax.servlet.http.HttpServletResponse.*; import static org.directwebremoting.jsonrpc.JsonRpcConstants.*; /** * A JsonDecoder that creates a JsonRpcCalls structure. * @author Joe Walker [joe at getahead dot ltd dot uk] */ public class JsonRpcCallsJsonDecoder extends StatefulJsonDecoder { /** * Force creators to give us the beans we need */ public JsonRpcCallsJsonDecoder(ConverterManager converterManager, CreatorManager creatorManager) { this.converterManager = converterManager; this.creatorManager = creatorManager; } /* (non-Javadoc) * @see org.directwebremoting.json.parse.impl.StatefulJsonDecoder#addMemberToArray(java.lang.Object, java.lang.Object) */ @Override protected void addMemberToArray(Object parent, Object member) throws JsonParseException { if (!inParams) { log.error("JSON parse has addMemberToArray where inParms=false"); calls.addParseError("Array not expected"); return; } @SuppressWarnings("unchecked") List<Object> array = (List<Object>) parent; array.add(member); } /* (non-Javadoc) * @see org.directwebremoting.json.parse.impl.StatefulJsonDecoder#addMemberToObject(java.lang.Object, java.lang.String, java.lang.Object) */ @SuppressWarnings("unchecked") @Override protected void addMemberToObject(Object parent, String propertyName, Object member) throws JsonParseException { if (inParams) { if (parent == calls) { if (!"params".equals(propertyName)) { log.error("JSON parse has addMemberToObject where parent=request and inParms=true for propertyName=" + propertyName); calls.addParseError("Expected property 'params' to be only object child"); return; } if (!(member instanceof List)) { log.error("JSON parse has addMemberToObject where member.class == " + member.getClass().getName() + " (expecting List) and propertyName=" + propertyName); calls.addParseError("Expected property 'params' to be a list"); return; } params = (List<Object>) member; inParams = false; } else { Map<String, Object> map = (Map<String, Object>) parent; map.put(propertyName, member); } } else { if (parent != calls) { log.error("JSON parse has addMemberToObject where parent != request and !inParms for propertyName=" + propertyName); calls.addParseError("Only params (and their children) can have children"); return; } if ("params".equals(propertyName)) { log.error("JSON parse has addMemberToObject where !inParams and propertyName=" + propertyName); calls.addParseError("Adding params property when not in params mode"); return; } if ("id".equals(propertyName)) { // id is allowed to be a string|number|boolean and we need to // recreate it exactly ... if (member instanceof String) { calls.setBatchId(JavascriptUtil.escapeJavaScript((String) member)); } else { calls.setBatchId(member.toString()); } } else { if (!(member instanceof String)) { log.error("JSON parse has addMemberToObject where member.class == " + member.getClass().getName() + " (expecting String) and propertyName=" + propertyName); calls.addParseError("Expected string type"); return; } String data = (String) member; if ("jsonrpc".equals(propertyName)) { calls.setVersion(data); } else if ("method".equals(propertyName)) { String[] parts = data.split("\\."); if (parts.length != 2) { log.warn("Got method='" + data + "', but this does not split into 2 parts (parts=" + Arrays.asList(parts) + ")."); calls.addParseError("method parameter did not split into 2 parts"); return; } scriptName = parts[0]; methodName = parts[1]; } } } } /* (non-Javadoc) * @see org.directwebremoting.json.parse.impl.StatefulJsonDecoder#createArray(java.lang.Object, java.lang.String) */ @Override protected Object createArray(Object parent, String propertyName) throws JsonParseException { if (parent == null) { log.error("JSON parse has createArray where parent=null"); calls.addParseError("json-rpc request must have a root object"); } if (parent == calls) { if (!"params".equals(propertyName)) { log.error("JSON parse has createArray where parent=request but propertyName=" + propertyName + " (expecting 'params')"); calls.addParseError("Only params object in a request can be an array"); } inParams = true; } return new ArrayList<Object>(); } /* (non-Javadoc) * @see org.directwebremoting.json.parse.impl.StatefulJsonDecoder#createObject(java.lang.Object, java.lang.String) */ @Override protected Object createObject(Object parent, String propertyName) throws JsonParseException { if (parent == null) { // This must be done at the end of the parse, if we don't have // everything now, we never will convertParams(); return calls; } if (parent == calls) { log.error("JSON parse has createObject where parent=request and propertyName=" + propertyName); calls.addParseError("There should be no child objects in a request object"); } return new HashMap<String, Object>(); } /** * We might not have enough information to convert the parameters because * the method parameter might not have been transfered, before the params * so we need to check for both */ protected void convertParams() { Creator creator = creatorManager.getCreator(scriptName, false); if (creator == null) { log.warn("No creator found: " + scriptName); throw new JsonRpcCallException(calls, "Object not valid", ERROR_CODE_INVALID, SC_BAD_REQUEST); } // Fill out the Calls structure try { Class<?> type = creator.getType(); // Get the types of the parameters List<Class<?>> paramTypes = new ArrayList<Class<?>>(); for (Object param : params) { paramTypes.add(param.getClass()); } Class<?>[] typeArray = paramTypes.toArray(new Class[paramTypes.size()]); Method method = type.getMethod(methodName, typeArray); Call call = new Call(null, scriptName, methodName); calls.addCall(call); call.setMethod(method); call.setParameters(params.toArray()); } catch (SecurityException ex) { log.warn("Method not allowed: " + scriptName + "." + methodName, ex); throw new JsonRpcCallException(calls, "Method not allowed", ERROR_CODE_INVALID, SC_BAD_REQUEST); } catch (NoSuchMethodException ex) { log.warn("Method not allowed: " + scriptName + "." + methodName, ex); throw new JsonRpcCallException(calls, "Method not found", ERROR_CODE_INVALID, SC_BAD_REQUEST); } } /** * How we convert parameters */ protected final ConverterManager converterManager; /** * How we create new beans */ protected final CreatorManager creatorManager; /** * */ private boolean inParams = false; /** * */ private final JsonRpcCalls calls = new JsonRpcCalls(); private String methodName; private String scriptName; /** * */ private List<Object> params = null; /** * The log stream */ private static final Log log = LogFactory.getLog(JsonRpcCallsJsonDecoder.class); }