Java tutorial
/* * Copyright 2004-2010 the Seasar Foundation and the Others. * * 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.slim3.gwt.server.rpc; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.HashMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slim3.controller.HotReloadingClassLoader; import org.slim3.util.CipherFactory; import org.slim3.util.ClassUtil; import org.slim3.util.RequestLocator; import org.slim3.util.ResponseLocator; import org.slim3.util.ServletContextLocator; import org.slim3.util.StringUtil; import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream; import com.google.gwt.user.server.rpc.RPC; import com.google.gwt.user.server.rpc.RPCRequest; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.google.gwt.user.server.rpc.SerializationPolicy; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader; import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator; /** * The remote service servlet for Slim3. * * @author higa * @since 1.0.0 * */ public class GWTServiceServlet extends RemoteServiceServlet { private static final long serialVersionUID = 1L; private static final HashMap<String, Class<?>> TYPE_NAMES; static { TYPE_NAMES = new HashMap<String, Class<?>>(); TYPE_NAMES.put("Z", boolean.class); TYPE_NAMES.put("B", byte.class); TYPE_NAMES.put("C", char.class); TYPE_NAMES.put("D", double.class); TYPE_NAMES.put("F", float.class); TYPE_NAMES.put("I", int.class); TYPE_NAMES.put("J", long.class); TYPE_NAMES.put("S", short.class); } /** * Whether the servlet context is set to {@link ServletContextLocator}. */ protected boolean servletContextSet = false; private static SerializationPolicy loadHotSerializationPolicy(HttpServlet servlet, HttpServletRequest request, String moduleBaseURL, String strongName) { // The request can tell you the path of the web app relative to the // container root. String contextPath = request.getContextPath(); String modulePath = null; if (moduleBaseURL != null) { try { modulePath = new URL(moduleBaseURL).getPath(); } catch (MalformedURLException ex) { // log the information, we will default servlet.log("Malformed moduleBaseURL: " + moduleBaseURL, ex); } } SerializationPolicy serializationPolicy = null; /* * Check that the module path must be in the same web app as the servlet * itself. If you need to implement a scheme different than this, * override this method. */ if (modulePath == null || !modulePath.startsWith(contextPath)) { String message = "ERROR: The module path requested, " + modulePath + ", is not in the same web application as this servlet, " + contextPath + ". Your module may not be properly configured or your client and server code maybe out of date."; servlet.log(message, null); } else { // Strip off the context path from the module base URL. It should be // a // strict prefix. String contextRelativePath = modulePath.substring(contextPath.length()); String serializationPolicyFilePath = HotSerializationPolicyLoader .getSerializationPolicyFileName(contextRelativePath + strongName); // Open the RPC resource file and read its contents. InputStream is = servlet.getServletContext().getResourceAsStream(serializationPolicyFilePath); try { if (is != null) { try { serializationPolicy = HotSerializationPolicyLoader.loadFromStream(is, null); } catch (ParseException e) { servlet.log("ERROR: Failed to parse the policy file '" + serializationPolicyFilePath + "'", e); } catch (IOException e) { servlet.log("ERROR: Could not read the policy file '" + serializationPolicyFilePath + "'", e); } } else { String message = "ERROR: The serialization policy file '" + serializationPolicyFilePath + "' was not found; did you forget to include it in this deployment?"; servlet.log(message, null); } } finally { if (is != null) { try { is.close(); } catch (IOException e) { // Ignore this error } } } } return serializationPolicy; } @Override public void init() throws ServletException { super.init(); getServletContext().setAttribute("slim3.controllerPackage", "server.controller"); if (ServletContextLocator.get() == null) { ServletContextLocator.set(getServletContext()); servletContextSet = true; } } @Override public void destroy() { if (servletContextSet) { ServletContextLocator.set(null); } } /** * Process a call originating from the given request. Uses the * {@link RPC#invokeAndEncodeResponse(Object, java.lang.reflect.Method, Object[])} * method to do the actual work. * <p> * Subclasses may optionally override this method to handle the payload in * any way they desire (by routing the request to a framework component, for * instance). The {@link HttpServletRequest} and {@link HttpServletResponse} * can be accessed via the {@link #getThreadLocalRequest()} and * {@link #getThreadLocalResponse()} methods. * </p> * This is public so that it can be unit tested easily without HTTP. * * @param payload * the UTF-8 request payload * @return a string which encodes either the method's return, an exception * thrown by the method * @throws SerializationException * if we cannot serialize the response */ @Override public String processCall(String payload) throws SerializationException { HttpServletRequest previousRequest = RequestLocator.get(); if (previousRequest == null) { RequestLocator.set(getThreadLocalRequest()); ResponseLocator.set(getThreadLocalResponse()); } S3RPCRequest request = null; RPCRequest rpcRequest = null; try { CipherFactory.getFactory().clearLimitedKey(); request = decodeRequest(payload); rpcRequest = request.getOriginalRequest(); onAfterRequestDeserialized(rpcRequest); Object result = invoke(request.getService(), rpcRequest.getMethod(), rpcRequest.getParameters()); return RPC.encodeResponseForSuccess(rpcRequest.getMethod(), result, rpcRequest.getSerializationPolicy()); } catch (IncompatibleRemoteServiceException ex) { log("An IncompatibleRemoteServiceException was thrown while processing this call.", ex); return RPC.encodeResponseForFailure(null, ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); log("An exception was thrown while processing this call.", cause); SerializationPolicy sp = rpcRequest != null ? rpcRequest.getSerializationPolicy() : null; return RPC.encodeResponseForFailure(null, cause, sp); } finally { if (previousRequest == null) { RequestLocator.set(null); ResponseLocator.set(null); } } } /** * Returns RPC request. * * @param encodedRequest * a string that encodes the service interface, the service * method, and the arguments * @return RPC request * * @throws NullPointerException * if the encodedRequest is <code>null</code> * @throws IllegalArgumentException * if the encodedRequest is an empty string * @throws IncompatibleRemoteServiceException * if any of the following conditions apply: * <ul> * <li>if the types in the encoded request cannot be * deserialized</li> * <li>if the {@link ClassLoader} acquired from * <code>Thread.currentThread().getContextClassLoader()</code> * cannot load the service interface or any of the types * specified in the encodedRequest</li> * <li>the requested interface is not assignable to * {@link RemoteService}</li> * <li>the service method requested in the encodedRequest is not * a member of the requested service interface</li> * <li>the type parameter is not <code>null</code> and is not * assignable to the requested {@link RemoteService} interface * </ul> */ protected S3RPCRequest decodeRequest(String encodedRequest) { if (encodedRequest == null) { throw new NullPointerException("The encodedRequest parameter cannot be null."); } if (encodedRequest.length() == 0) { throw new IllegalArgumentException("The encodedRequest parameter cannot be empty."); } ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(classLoader, this); streamReader.prepareToRead(encodedRequest); String interfaceName = readClassName(streamReader); Class<?> serviceClass = getServiceClass(interfaceName); Object service = getService(serviceClass); SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy(); String methodName = streamReader.readString(); int paramCount = streamReader.readInt(); Class<?>[] parameterTypes = new Class[paramCount]; for (int i = 0; i < parameterTypes.length; i++) { String paramClassName = readClassName(streamReader); parameterTypes[i] = getClass(paramClassName); } try { Method method = serviceClass.getMethod(methodName, parameterTypes); Object[] parameterValues = new Object[parameterTypes.length]; for (int i = 0; i < parameterValues.length; i++) { parameterValues[i] = streamReader.deserializeValue(parameterTypes[i]); } return new S3RPCRequest(service, new RPCRequest(method, parameterValues, serializationPolicy, streamReader.getFlags())); } catch (NoSuchMethodException e) { throw new IncompatibleRemoteServiceException(e.getMessage(), e); } } catch (SerializationException ex) { throw new IncompatibleRemoteServiceException(ex.getMessage(), ex); } } /** * Returns class name. * * @param streamReader * the stream reader * @return class name * @throws SerializationException * if {@link SerializationException} occurred */ protected String readClassName(ServerSerializationStreamReader streamReader) throws SerializationException { String name = streamReader.readString(); int index; if (streamReader.hasFlags(AbstractSerializationStream.FLAG_ELIDE_TYPE_NAMES)) { SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy(); if (!(serializationPolicy instanceof TypeNameObfuscator)) { throw new IncompatibleRemoteServiceException("RPC request was encoded with obfuscated type names, " + "but the SerializationPolicy in use does not implement " + TypeNameObfuscator.class.getName()); } String maybe = ((TypeNameObfuscator) serializationPolicy).getClassNameForTypeId(name); if (maybe != null) { return maybe; } } else if ((index = name.indexOf('/')) != -1) { return name.substring(0, index); } return name; } /** * Invokes the service method. * * @param service * the service * @param method * the method * @param args * the arguments * @return the return value of the method * @throws InvocationTargetException * if the method throws an exception */ protected Object invoke(Object service, Method method, Object[] args) throws InvocationTargetException { Object result = null; try { result = method.invoke(service, args); } catch (IllegalArgumentException e) { throw new IncompatibleRemoteServiceException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new IncompatibleRemoteServiceException(e.getMessage(), e); } catch (ClassCastException e) { String msg = e.getMessage(); String[] msgs = StringUtil.split(msg, " "); if (msgs.length > 2 && msgs[0].equals(msgs[msgs.length - 1])) { msg = "The class(" + msgs[0] + ") is loaded by deferent class loaders."; } throw new IncompatibleRemoteServiceException(msg, e); } return result; } @Override protected SerializationPolicy doGetSerializationPolicy(HttpServletRequest request, String moduleBaseURL, String strongName) { if (Thread.currentThread().getContextClassLoader() instanceof HotReloadingClassLoader) { return loadHotSerializationPolicy(this, request, moduleBaseURL, strongName); } return super.doGetSerializationPolicy(request, moduleBaseURL, strongName); } /** * Returns the class specified by the name. * * @param className * the class name * @return the class * @throws NullPointerException * if the className parameter is null */ protected Class<?> getClass(String className) throws NullPointerException { if (className == null) { throw new NullPointerException("The className parameter is null."); } Class<?> clazz = TYPE_NAMES.get(className); if (clazz != null) { return clazz; } try { return Class.forName(className, false, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IncompatibleRemoteServiceException( "Could not load the class(" + className + ") in context classloader", e); } } /** * Returns the service class specified by the interface name. * * @param interfaceName * the interface name * @return the service class * @throws NullPointerException * if the interfaceName parameter is null */ protected Class<?> getServiceClass(String interfaceName) throws NullPointerException { if (interfaceName == null) { throw new NullPointerException("The interfaceName parameter is null."); } String className = interfaceName.replace(".client.", ".server.") + "Impl"; return getClass(className); } /** * Returns the service. * * @param serviceClass * the class name * @return the service * @throws NullPointerException * if the serviceClass parameter is null */ protected Object getService(Class<?> serviceClass) throws NullPointerException { if (serviceClass == null) { throw new NullPointerException("The serviceClass parameter is null."); } return ClassUtil.newInstance(serviceClass); } }