All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jaxygen.invoker.ServiceInvoker Maven / Gradle / Ivy

There is a newer version: 1.0.9
Show newest version
package org.jaxygen.invoker;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.jaxygen.annotations.ClientIp;
import org.jaxygen.annotations.NetAPI;
import org.jaxygen.annotations.RequestURL;
import org.jaxygen.annotations.SessionContext;
import org.jaxygen.annotations.Validable;
import org.jaxygen.converters.ConvertersFactory;
import org.jaxygen.converters.RequestConverter;
import org.jaxygen.converters.ResponseConverter;
import org.jaxygen.converters.exceptions.SerializationError;
import org.jaxygen.converters.json.JsonHRResponseConverter;
import org.jaxygen.converters.json.JsonMultipartRequestConverter;
import org.jaxygen.converters.json.JsonRequestConverter;
import org.jaxygen.converters.json.JsonResponseConverter;
import org.jaxygen.converters.prop2Json.Prop2JSONConverter;
import org.jaxygen.converters.properties.PropertiesToBeanConverter;
import org.jaxygen.converters.sjo.SJORRequestConverter;
import org.jaxygen.converters.sjo.SJOResponseConverter;
import org.jaxygen.converters.xml.XMLResponseConverter;
import org.jaxygen.dto.Downloadable;
import org.jaxygen.dto.ExceptionResponse;
import org.jaxygen.dto.Response;
import org.jaxygen.dto.security.SecurityProfileDTO;
import org.jaxygen.exceptions.InvalidPropertyFormat;
import org.jaxygen.exceptions.ParametersError;
import org.jaxygen.http.HttpRequestParams;
import org.jaxygen.http.HttpRequestParser;
import org.jaxygen.objectsbuilder.ObjectBuilder;
import org.jaxygen.objectsbuilder.ObjectBuilderFactory;
import org.jaxygen.propertyinjector.PropertyInjector;
import org.jaxygen.propertyinjector.ValueProvider;
import org.jaxygen.propertyinjector.exceptions.PropertyEnhancementException;
import org.jaxygen.security.SecurityProfile;
import org.jaxygen.security.annotations.LoginMethod;
import org.jaxygen.security.annotations.LogoutMethod;
import org.jaxygen.security.annotations.Secured;
import org.jaxygen.security.annotations.SecurityContext;
import org.jaxygen.security.exceptions.NotAlowed;
import org.jaxygen.util.BeanUtil;

public class ServiceInvoker extends HttpServlet {
  
  private static final long serialVersionUID = 566338505269576162L;
  private static final Logger log = Logger.getLogger(ServiceInvoker.class.getCanonicalName());
  public static final String SERVICE_PATH = "servicePath";
  private String beensPath = null;
  
  static {
      // Register default converters
      ConvertersFactory.registerRequestConverter(new Prop2JSONConverter());
      ConvertersFactory.registerRequestConverter(new PropertiesToBeanConverter());
    ConvertersFactory.registerResponseConverter(new JsonResponseConverter());
    ConvertersFactory.registerRequestConverter(new JsonMultipartRequestConverter());
    ConvertersFactory.registerRequestConverter(new JsonRequestConverter());
    ConvertersFactory.registerRequestConverter(new SJORRequestConverter());
    ConvertersFactory.registerResponseConverter(new SJOResponseConverter());
    ConvertersFactory.registerResponseConverter(new XMLResponseConverter());
    ConvertersFactory.registerResponseConverter(new JsonHRResponseConverter());
  }
  
  @Override
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    beensPath = config.getInitParameter(SERVICE_PATH);
  }
  
  private String buildClassName(final String servicesRoot, final String className) {
      String fullClassName = className;
      if (!servicesRoot.isEmpty()) {
          fullClassName = servicesRoot + "." + className;
      }
      return fullClassName;
  }
  
  @Override
  protected void doGet(HttpServletRequest request,
          HttpServletResponse response) throws ServletException, IOException {
    
    request.setCharacterEncoding("UTF-8");
    
    HttpRequestParams params = null;
    HttpSession session = request.getSession(true);
    try {
      params = new HttpRequestParser(request);
    } catch (Exception ex) {
      throwError(response, new JsonResponseConverter(), "Could not parse properties", ex);
    }
    if (beensPath == null) {
      beensPath = getServletContext().getInitParameter(SERVICE_PATH);
    }
    final String resourcePath = request.getPathInfo();
    final String queryString = request.getQueryString();
    
    final String inputFormat = params.getAsString("inputType", 0, 32, PropertiesToBeanConverter.NAME);
    final String outputFormat = params.getAsString("outputType", 0, 32, JsonResponseConverter.NAME);
    
    ResponseConverter responseConverter = ConvertersFactory.getResponseConverter(outputFormat);
    if (responseConverter == null) {
      responseConverter = new JsonResponseConverter();
    }
    
    String query = "";
    
    if (queryString != null) {
      query = URLDecoder.decode(queryString, "UTF-8");
    }
    
    log("Requesting resource" + resourcePath);
    
    String[] chunks = resourcePath.split("/");
    
    if (chunks.length < 2) {
      Logger.getLogger(ServiceInvoker.class.getName()).log(Level.SEVERE, "Invalid request, must be in format class/method");
      throw new ServletException("Invalid '" + resourcePath + "' request, must be in format class/method");
    }
    final String methodName = chunks[chunks.length - 1];
    final String className = buildClassName(beensPath, chunks[chunks.length - 2]);
    
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    Method[] methods;
    try {
      Class clazz = cl.loadClass(className);
      if (clazz != null) {
        boolean methodFound = false;
        methods = clazz.getMethods();
        for (Method m : methods) {
          if (m.isAnnotationPresent(NetAPI.class)
                  && m.getName().equals(methodName)) {
            try {
              methodFound = true;
              checkMethodAllowed(session, clazz.getCanonicalName(), m);
              final Class[] parameterTypes = m.getParameterTypes();
              Object[] parameters = parseParameters(parameterTypes, inputFormat, params, query);
              ObjectBuilder ob = ObjectBuilderFactory.instance();
              Object been = ob.create(clazz);
              validate(parameters);
              try {
                extendDTO(parameters, request);
                injectSecutityProfile(been, session);
                Class responseType = m.getReturnType();
                Object o = m.invoke(been, parameters);
                if (o instanceof Downloadable) {
                                    FileDeliveryHandler.postFile(request, response, (Downloadable) o);
                } else {
                  if (o instanceof SecurityProfile) {
                    SecurityProfileDTO profileDto = new SecurityProfileDTO();
                    SecurityProfile profile = (SecurityProfile) o;
                    profileDto.setGroups(profile.getUserGroups());
                    profileDto.setAllowedMethods(profile.getAllowedMethodDescriptors());
                    response.setCharacterEncoding("UTF-8");
                    sendSerializedResponse(SecurityProfileDTO.class, profileDto, responseConverter, response);
                  } else {
                    response.setCharacterEncoding("UTF-8");
                    sendSerializedResponse(responseType, o, responseConverter, response);
                  }
                }
                if (m.isAnnotationPresent(LoginMethod.class)) {
                  boolean profileConfigured = updateSessionSecurityProfile(been, session);
                  if (!profileConfigured && !(o instanceof SecurityProfile)) {
                    throwError(response, responseConverter, "Incompatible interface", "Method " + clazz + "." + methodName + " is annotated with @Login but does not return " + SecurityProfile.class.getCanonicalName());
                  }
                  if (o instanceof SecurityProfile) {
                    attachSecurityContextToSession(session, (SecurityProfile) o);
                  }
                }
                if (m.isAnnotationPresent(LogoutMethod.class)) {
                  detachSecurityContext(session);
                }
              } catch (InvocationTargetException ex) {
                throwError(response, responseConverter, "Call to bean failed : " + ex.getTargetException().getMessage(), ex.getTargetException());
              } catch (Exception ex) {
                throwError(response, responseConverter, "Call to bean failed : " + ex.getMessage(), ex);
              }
            } catch (Exception ex) {
              throwError(response, responseConverter, "Cann not intanitiate class " + clazz.getCanonicalName(), ex);
            }
            
          }
        }
        if (!methodFound) {
          throwError(response, responseConverter, "InvalidRequest", "Method " + className + "." + methodName + " not found");
        }
      } else {
        throwError(response, responseConverter, "InternalError", "Class '" + className + "' not fount");
      }
      
    } catch (ClassNotFoundException ex) {
      throwError(response, responseConverter, "Class '" + className + "' not fount", ex);
      
    } finally {
      if (params != null) {
        params.dispose();
      }
    }
    
  }
  
  private void extendDTO(Object[] objects, final HttpServletRequest request) throws PropertyEnhancementException {
    PropertyInjector.bind(objects,
            ValueProvider.on(ClientIp.class).provide(() -> {return getPublicIpAddress(request);}),
            ValueProvider.on(RequestURL.class).provide(() -> {return request.getRequestURL().toString();}));
  }
  
  
  private Object[] parseParameters(final Class[] parameterTypes, final String inputFormat, HttpRequestParams params, String query) throws ParametersError {
    Object parameters[] = new Object[parameterTypes.length];
    int i = 0;
    for (Class p : parameterTypes) {
      try {
        RequestConverter converter = ConvertersFactory.getRequestConverter(inputFormat);
        if (converter != null) {
          parameters[i] = converter.deserialise(params, p);
        } else {
          log.log(Level.WARNING, "Could not find converter for name ''{0}''", inputFormat);
        }
      } catch (Exception ex) {
        throw new ParametersError("Cann not parse parameters for parameters class " + p.getCanonicalName(), ex);
      }
      i++;
    }
    return parameters;
  }
  
  private static void callSetter(Field f, Object been, Object sp) throws SecurityException, IllegalArgumentException, IllegalAccessException {
    boolean accessibility = f.isAccessible();
    f.setAccessible(true);
    f.set(been, sp);
    f.setAccessible(accessibility);
  }
  
  private static Object callGetter(Field f, Object been) throws SecurityException, IllegalArgumentException, IllegalAccessException {
    boolean accessibility = f.isAccessible();
    f.setAccessible(true);
    Object sp = f.get(been);
    f.setAccessible(accessibility);
    return sp;
  }
  
  private void throwError(HttpServletResponse response, ResponseConverter converter, String string, Throwable ex) throws ServletException, IOException {
    log.log(Level.SEVERE, string, ex);
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    ExceptionResponse resp = new ExceptionResponse(ex, string);
    try {
      converter.serialize(resp, response.getOutputStream());
    } catch (SerializationError ex1) {
      log.log(Level.SEVERE, "Server was unable to inform peer about exception", ex);
    }
  }
  
  private void throwError(HttpServletResponse response, ResponseConverter converter, final String codeName, String message) throws ServletException, IOException {
    log.log(Level.SEVERE, message);
    ExceptionResponse resp = new ExceptionResponse(codeName, message);
    try {
      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      converter.serialize(resp, response.getOutputStream());
    } catch (SerializationError ex1) {
      log.log(Level.SEVERE, "Server was unable to inform peer about exception", ex1);
    }
  }
  
  @Override
  protected void doPost(HttpServletRequest request,
          HttpServletResponse response) throws ServletException, IOException {
    System.out.println("POST");
    doGet(request, response);
  }
  
  private void attachSecurityContextToSession(HttpSession session, SecurityProfile securityProvider) {
    session.setAttribute(SecurityProfile.class.getCanonicalName(), securityProvider);
  }
  
  private void checkMethodAllowed(HttpSession session, final String clazz, Method method) throws NotAlowed {
    SecurityProfile sp = (SecurityProfile) session.getAttribute(SecurityProfile.class.getCanonicalName());
    if (method.isAnnotationPresent(Secured.class) && (sp == null || sp.isAllowed(clazz, method.getName()) == null)) {
      throw new NotAlowed(clazz, method.getName());
    }
  }
  
  private void detachSecurityContext(HttpSession session) {
    session.setAttribute(SecurityProfile.class.getCanonicalName(), null);
  }

  //Inject security profile attribute if been contains field annotated by SecurityContext attribute
  private void injectSecutityProfile(Object been, HttpSession session) throws IllegalArgumentException, IllegalAccessException {
    for (Field f : been.getClass().getDeclaredFields()) {
      SecurityProfile sp = (SecurityProfile) session.getAttribute(SecurityProfile.class.getCanonicalName());
      {
        SecurityContext sc = f.getAnnotation(SecurityContext.class);
        if (sc != null) {
          callSetter(f, been, sp);
        }
      }
      {
        SessionContext sc = f.getAnnotation(SessionContext.class);
        if (sc != null) {
          callSetter(f, been, session);
        }
      }
    }
  }
  
  private boolean updateSessionSecurityProfile(Object been, HttpSession session) throws IllegalArgumentException, IllegalAccessException {
    SecurityProfile sp = (SecurityProfile) session.getAttribute(SecurityProfile.class.getCanonicalName());
    boolean sessionContextUpdated = false;
    SecurityProfile newSp = sp;
    for (Field f : been.getClass().getDeclaredFields()) {
      {
        SecurityContext sc = f.getAnnotation(SecurityContext.class);
        if (sc != null) {
          newSp = (SecurityProfile) callGetter(f, been);
          sessionContextUpdated = true;
        }
      }
    }
    if (sp != newSp) {
      session.setAttribute(SecurityProfile.class.getCanonicalName(), newSp);
    }
    return sessionContextUpdated;
  }
  
  private void validate(Object[] parameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, InvalidPropertyFormat {
    for (Object o : parameters) {
      if (o.getClass().isAnnotationPresent(Validable.class)) {
        BeanUtil.validateBean(o);
      }
    }
  }
  
  private void sendSerializedResponse(Class responseClass, Object o, ResponseConverter converter, HttpServletResponse response) throws SerializationError, IOException, ServletException {
    Response responseWraper = new Response(responseClass, o);
    converter.serialize(responseWraper, response.getOutputStream());
  }
  
  private String getPublicIpAddress(HttpServletRequest request) {
    String ipAddress = request.getHeader("x-forwarded-for");
    if (ipAddress == null) {
      ipAddress = request.getHeader("X_FORWARDED_FOR");
      if (ipAddress == null) {
        ipAddress = request.getRemoteAddr();
      }
    }
    return ipAddress;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy