package fr.lteconsulting.hexa.server.rpc; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.slf4j.Logger; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import fr.lteconsulting.hexa.client.comm.DataProxy; import fr.lteconsulting.hexa.client.comm.FieldName; import fr.lteconsulting.hexa.client.common.HexaDateTime; import fr.lteconsulting.hexa.server.tools.LoggerFactory; import fr.lteconsulting.hexa.server.tools.Trace; public abstract class HexaGWTServlet extends HttpServlet { private static final long serialVersionUID = 3471403271348698342L; private final Logger log = LoggerFactory.getLogger(); HashMap<String, ServiceDescription> services = new HashMap<String, ServiceDescription>(); abstract protected void onInit(); // opportunity to configure implemented // services @Override public void init() { onInit(); } protected void addService( String serviceName, String serviceChecksum, Object delegate, String[] methodShortcuts ) { services.put( serviceName, new ServiceDescription( serviceName, serviceChecksum, delegate, methodShortcuts ) ); } private ServiceDescription getServiceDelegate( String serviceName, String serviceChecksum ) { ServiceDescription desc = services.get( serviceName ); if( desc != null && !desc.checksum.equals( serviceChecksum ) ) { Trace.it( "Service not available with this checksum: " + serviceName + ":" + serviceChecksum + " (we have " + desc.checksum + ")" ); return null; } return desc; } @Override protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException { resp.addHeader( "Pragma", "no-cache" ); resp.addHeader( "Cache-Control", "no-cache" ); // TODO get locale // TODO create a logger ServletFileUpload upload = new ServletFileUpload(); try { FileItemIterator iterator = upload.getItemIterator( req ); while( iterator.hasNext() ) { FileItemStream stream = iterator.next(); if( stream.getFieldName().equals( "payload" ) ) { String response = processPayload( stream.openStream() ); resp.getWriter().write( response ); return; } } } catch( Exception e ) { resp.getWriter().write( "Exception during POST processing : " + e.getMessage() ); e.printStackTrace(); return; } resp.getWriter().write( "INSUFICIENT PAYLOAD" ); } private String processPayload( InputStream payloadStream ) throws IOException, HexaGWTRPCException { Trace.push(); // read the rpc payload String payloadString = readPayloadStream( payloadStream ); if( payloadString == null ) throw new HexaGWTRPCException( "Unreadable payload : " + Trace.pop() ); Trace.it( "Processing payload :" ); Trace.it( "Payload size: " + payloadString.length() ); Trace.it( "Json content: " + payloadString ); // parse the payload JsonArray payload = parsePayload( payloadString ); if( payload == null ) throw new HexaGWTRPCException( "Unparsable payload : " + Trace.pop() ); JsonArray serviceDescriptions = payload.get( 0 ).getAsJsonArray(); JsonArray calls = payload.get( 1 ).getAsJsonArray(); // Service object bindings ServiceDescription[] services = prepareUsedServices( serviceDescriptions ); if( services == null ) throw new HexaGWTRPCException( "Unavailable service(s) : " + Trace.pop() ); // prepare answer JsonArray result = new JsonArray(); Trace.it( "Preparing " + calls.size() + " call(s)" ); // calls to the services for( int i = 0; i < calls.size(); i++ ) { Trace.push(); Trace.it( "Call " + i ); JsonArray call = calls.get( i ).getAsJsonArray(); JsonArray responseSerialized = processCall( call, services ); // appending the answer result.add( responseSerialized ); Trace.pop(); } // log.log(Level.INFO, Trace.peek()); Trace.pop(); return result.toString(); } private String readPayloadStream( InputStream payloadStream ) throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader( payloadStream, "utf-8" ) ); StringBuilder sb = new StringBuilder(); int readden = 1024; char[] buf = new char[readden]; do { readden = in.read( buf ); sb.append( buf, 0, readden ); } while( readden == buf.length ); return sb.toString(); } private JsonArray parsePayload( String payload ) { JsonParser parser = new JsonParser(); JsonElement element = parser.parse( payload ); if( element == null ) return null; JsonArray result = element.getAsJsonArray(); return result; } private ServiceDescription[] prepareUsedServices( JsonArray serviceDescriptions ) { boolean ok = true; ServiceDescription[] services = new ServiceDescription[serviceDescriptions.size()]; for( int i = 0; i < serviceDescriptions.size(); i++ ) { JsonArray serviceDescription = serviceDescriptions.get( i ).getAsJsonArray(); String serviceName = serviceDescription.get( 0 ).getAsString(); // Service // name String serviceChecksum = serviceDescription.get( 1 ).getAsString(); // service // checksum services[i] = getServiceDelegate( serviceName, serviceChecksum ); if( services[i] == null ) { Trace.it( "Service not available : " + serviceName + ":" + serviceChecksum ); ok = false; } } return ok ? services : null; } private JsonArray processCall( JsonArray call, ServiceDescription[] services ) { String method = call.get( 0 ).getAsString(); JsonArray parameters = call.get( 1 ).getAsJsonArray(); int serviceIdx = call.get( 2 ).getAsInt(); Trace.it( "Service:" + services[serviceIdx].name + ", method: " + method + ", params:" + parameters ); // call to the method JsonArray responseSerialized = new JsonArray(); Object response = null; try { response = services[serviceIdx].call( method, parameters ); Trace.it( "Call made Ok." ); // encode the response JsonArray encodedResponse = new JsonArray(); if( response != null && response.getClass().isArray() ) { Trace.it( "Multi-valued return value." ); // multiple return values Object[] responses = (Object[]) response; for( int r = 0; r < responses.length; r++ ) encodedResponse.add( encodeResponse( responses[r] ) ); } else { encodedResponse.add( encodeResponse( response ) ); } // serialization of the returned value responseSerialized.add( new JsonPrimitive( 0 ) ); // ServerState/Level responseSerialized.add( new JsonPrimitive( "" ) ); // ServerState/Message responseSerialized.add( null ); // Hang_Out_Code responseSerialized.add( encodedResponse ); // Encoded_response } catch( Exception e ) { Trace.throwable( e ); Trace.it( "Call impossible, exception during call !!!" ); log.info( Trace.peek() ); // serialization of the returned value responseSerialized.add( new JsonPrimitive( 3 ) ); // ServerState/Level responseSerialized.add( new JsonPrimitive( "Exception during call : " + Trace.peek() ) ); // ServerState/Message responseSerialized.add( null ); // Hang_Out_Code responseSerialized.add( null ); // Encoded_response } return responseSerialized; } public static JsonElement encodeResponse( Object response ) { if( response == null ) return null; if( response instanceof List<?> ) { List<?> list = (List<?>) response; JsonArray encoded = new JsonArray(); for( Object e : list ) encoded.add( encodeResponse( e ) ); return encoded; } if( response instanceof Iterable<?> ) { Iterable<?> iterable = (Iterable<?>) response; JsonArray encoded = new JsonArray(); for( Object e : iterable ) encoded.add( encodeResponse( e ) ); return encoded; } if( response instanceof DataProxy ) { // encode a bean JsonObject encoded = new JsonObject(); // find the DataProxy sub class this response herits from HashSet<Method> methods = new HashSet<Method>(); getDataProxyMethods( response.getClass(), methods ); for( Method m : methods ) { FieldName fieldNameAnnotation = m.getAnnotation( FieldName.class ); if( fieldNameAnnotation == null ) continue; try { String field = fieldNameAnnotation.fieldName(); Object value = m.invoke( response ); encoded.add( field, encodeResponse( value ) ); } catch( Exception e ) { e.printStackTrace(); } } return encoded; } if( response instanceof String ) return new JsonPrimitive( (String) response ); if( response instanceof Number ) return new JsonPrimitive( (Number) response ); if( response instanceof HexaDateTime ) return new JsonPrimitive( ((HexaDateTime) response).getString() ); return null; } private static void getDataProxyMethods( Class<?> clazz, HashSet<Method> result ) { if( clazz == null || clazz == Object.class ) return; // find annotated methods within the super class getDataProxyMethods( clazz.getSuperclass(), result ); // find annotated methods within the implemented interfaces Class<?>[] implementedInterfaces = clazz.getInterfaces(); for( int i = 0; i < implementedInterfaces.length; i++ ) getDataProxyMethods( implementedInterfaces[i], result ); // find annoted methods in the class Method[] methods = clazz.getDeclaredMethods(); for( int i = 0; i < methods.length; i++ ) { Method m = methods[i]; FieldName fieldNameAnnotation = m.getAnnotation( FieldName.class ); if( fieldNameAnnotation == null ) continue; result.add( m ); } } }