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

fr.hhdev.ocelot.core.CallServiceManager Maven / Gradle / Ivy

The newest version!
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 
 */
package fr.hhdev.ocelot.core;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.ArrayType;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.SimpleType;
import fr.hhdev.ocelot.configuration.OcelotConfiguration;
import fr.hhdev.ocelot.annotations.DataService;
import fr.hhdev.ocelot.annotations.JsCacheResult;
import fr.hhdev.ocelot.messaging.Fault;
import fr.hhdev.ocelot.messaging.MessageFromClient;
import fr.hhdev.ocelot.messaging.MessageToClient;
import fr.hhdev.ocelot.resolvers.DataServiceResolverIdLitteral;
import fr.hhdev.ocelot.spi.DataServiceException;
import fr.hhdev.ocelot.spi.IDataServiceResolver;
import fr.hhdev.ocelot.spi.Scope;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.el.MethodNotFoundException;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract class of OcelotDataService
 *
 * @author hhfrancois
 */
public class CallServiceManager {

	private static final Logger logger = LoggerFactory.getLogger(CallServiceManager.class);

	@Inject
	private Cleaner cleaner;

	@Inject
	@Any
	private Instance resolvers;

	@Inject
	private OcelotConfiguration configuration;

	@Inject
	private CacheManager cacheManager;

	protected IDataServiceResolver getResolver(String type) {
		return resolvers.select(new DataServiceResolverIdLitteral(type)).get();
	}

	/**
	 * Get pertinent method and fill the argument list from message arguments
	 *
	 * @param dsClass
	 * @param message
	 * @param arguments
	 * @return
	 */
	protected Method getMethodFromDataService(final Class dsClass, final MessageFromClient message, Object[] arguments) throws MethodNotFoundException {
		logger.debug("Try to find method {} on class {}", message.getOperation(), dsClass);
		List parameters = message.getParameters();
		for (Method method : dsClass.getMethods()) {
			if (method.getName().equals(message.getOperation()) && method.getParameterTypes().length == parameters.size()) {
				logger.debug("Process method {}", method.getName());
				try {
					Type[] params = method.getGenericParameterTypes();
					int idx = 0;
					for (Type param : params) {
						String arg = cleaner.cleanArg(parameters.get(idx));
						logger.debug("Get argument ({}) {} : {}.", new Object[]{idx, param.toString(), arg});
						arguments[idx++] = convertArgument(arg, param);
					}
					logger.debug("Method {}.{} with good signature found.", dsClass, message.getOperation());
					return method;
				} catch (IllegalArgumentException iae) {
					logger.debug("Method {}.{} not found. Arguments didn't match. {}.", new Object[]{dsClass, message.getOperation(), iae.getMessage()});
				}
			}
		}
		throw new MethodNotFoundException(dsClass + "." + message.getOperation());
	}

	/**
	 * Get pertinent method and fill the argument list from message arguments This method inject an additional argument, the client session
	 *
	 * @param session
	 * @param dsClass
	 * @param message
	 * @param arguments
	 * @return
	 */
	protected Method getMethodFromDataServiceWithSessionInjection(final Session session, final Class dsClass, final MessageFromClient message, Object[] arguments) throws MethodNotFoundException {
		logger.debug("Try to find method with session {} on class {}", message.getOperation(), dsClass);
		List parameters = message.getParameters();
		for (Method method : dsClass.getMethods()) {
			int nbParamater = parameters.size() + 1;
			if (method.getName().equals(message.getOperation()) && method.getParameterTypes().length == nbParamater) {
				logger.debug("Process method {}", method.getName());
				try {
					Type[] params = method.getGenericParameterTypes();
					int idx = 0, pidx = 0;
					for (Type param : params) {
						if (idx < nbParamater) {
							if(Session.class.equals(param)) {
								arguments[idx++] = session;
							} else {
								String jsonArg = parameters.get(pidx++);
								String arg = cleaner.cleanArg(jsonArg);
								logger.debug("Get argument ({}) {} : {}.", new Object[]{idx, param.toString(), arg});
								arguments[idx++] = convertArgument(arg, param);
							}
						}
					}
					logger.debug("Method {}.{} with good signature found.", dsClass, message.getOperation());
					return method;
				} catch (IllegalArgumentException iae) {
					logger.debug("Method {}.{} not found. Arguments didn't match. {}.", new Object[]{dsClass, message.getOperation(), iae.getMessage()});
				}
			}
		}
		throw new MethodNotFoundException(dsClass + "." + message.getOperation());
	}

	private Object convertArgument(String arg, Type param) throws IllegalArgumentException {
		Object result = null;
		logger.debug("Try to convert {} : param = {} : {}", new Object[]{arg, param, param.getClass()});
		try {
			ObjectMapper mapper = new ObjectMapper();
			if (ParameterizedType.class.isInstance(param)) {
				JavaType javaType = getJavaType(param);
				logger.debug("Try to convert '{}' to JavaType : '{}'", arg, param);
				result = mapper.readValue(arg, javaType);
				logger.debug("Conversion of '{}' to '{}' : OK", arg, param);
			} else if (Class.class.isInstance(param)) {
				Class cls = (Class) param;
				logger.debug("Try to convert '{}' to Class '{}'", arg, param);
				if (cls.equals(String.class) && (!arg.startsWith("\"") || !arg.endsWith("\""))) { // on cherche une string
					throw new IOException();
				}
				if (!cls.equals(String.class) && arg.startsWith("\"") && arg.endsWith("\"")) { // on a une string
					throw new IOException();
				}
				result = mapper.readValue(arg, cls);
				logger.debug("Conversion of '{}' to '{}' : OK", arg, param);
			}
		} catch (IOException ex) {
			logger.debug("Conversion of '{}' to '{}' failed", arg, param);
			throw new IllegalArgumentException(param.toString());
		}
		return result;
	}

	private JavaType getJavaType(Type type) {
		Class clazz;
		logger.debug("Computing type of {} - {}", type.getClass(), type.toString());
		if (type instanceof ParameterizedType) {
			clazz = (Class) ((ParameterizedType) type).getRawType();
		} else {
			clazz = (Class) type;
		}
		JavaType javaType;
		Type actualType;
		if (Collection.class.isAssignableFrom(clazz)) {
			ParameterizedType pt = (ParameterizedType) type;
			actualType = pt.getActualTypeArguments()[0];
			JavaType t1 = getJavaType(actualType);
			javaType = CollectionType.construct(Collection.class, t1);
		} else if (clazz.isArray()) {
			Class t = clazz.getComponentType();
			JavaType t1 = getJavaType(t);
			javaType = ArrayType.construct(t1, null, null);
		} else if (Map.class.isAssignableFrom(clazz)) {
			ParameterizedType pt = (ParameterizedType) type;
			actualType = pt.getActualTypeArguments()[0];
			JavaType t1 = getJavaType(actualType);
			actualType = pt.getActualTypeArguments()[1];
			JavaType t2 = getJavaType(actualType);
			javaType = MapType.construct(Map.class, t1, t2);
		} else {
			javaType = SimpleType.construct(clazz);
		}
		return javaType;
	}

	/**
	 * Get Dataservice, maybe in session if session scope and stored J'aimerais bien faire cela avec un interceptor/decorator, mais comment passer la session à celui ci ?
	 *
	 * @param client
	 * @param cls
	 * @return
	 * @throws DataServiceException
	 */
	protected Object getDataService(Session client, Class cls) throws DataServiceException {
		String dataServiceClassName = cls.getName();
		logger.debug("Looking for dataservice : {}", dataServiceClassName);
		if (cls.isAnnotationPresent(DataService.class)) {
			DataService dataServiceAnno = (DataService) cls.getAnnotation(DataService.class);
			IDataServiceResolver resolver = getResolver(dataServiceAnno.resolver());
			Scope scope = resolver.getScope(cls);
			Object dataService = null;
			if (scope.equals(Scope.SESSION)) {
				dataService = client.getUserProperties().get(dataServiceClassName);
			}
			if (dataService == null) {
				dataService = resolver.resolveDataService(cls);
				if (scope.equals(Scope.SESSION)) {
					client.getUserProperties().put(dataServiceClassName, dataService);
				}
			}
			return dataService;
		} else {
			throw new DataServiceException(dataServiceClassName);
		}
	}

	/**
	 * Build and send response messages after call request
	 *
	 * @param client
	 * @param message
	 */
	public void sendMessageToClients(Session client, MessageFromClient message) {
		MessageToClient messageToClient = new MessageToClient();
		messageToClient.setId(message.getId());
		try {
			Class cls = Class.forName(message.getDataService());
			Object dataService = getDataService(client, cls);
			logger.debug("Process message {}", message);
			logger.debug("Invocation of : {}", message.getOperation());
			int nbParam = message.getParameters().size();
			Object[] arguments = new Object[nbParam];
			Method method = getMethodFromDataService(cls, message, arguments);
			if (!method.isAnnotationPresent(MethodWithSessionInjection.class)) {
				messageToClient.setResult(method.invoke(dataService, arguments));
			} else {
				arguments = new Object[nbParam + 1];
				Method methodWithInjection = getMethodFromDataServiceWithSessionInjection(client, cls, message, arguments);
				messageToClient.setResult(methodWithInjection.invoke(dataService, arguments));
			}
			try {
				Method nonProxiedMethod = getNonProxiedMethod(cls, method.getName(), method.getParameterTypes());
				if (cacheManager.isJsCached(nonProxiedMethod)) {
					JsCacheResult jcr = nonProxiedMethod.getAnnotation(JsCacheResult.class);
					messageToClient.setDeadline(cacheManager.getJsCacheResultDeadline(jcr));
				}
				cacheManager.processCleanCacheAnnotations(nonProxiedMethod, message.getParameterNames(), message.getParameters());
				if (logger.isDebugEnabled()) {
					logger.debug("Method {} proceed messageToClient : {}.", method.getName(), messageToClient.toJson());
				}
			} catch (NoSuchMethodException ex) {
				logger.error("Fail to process extra annotations (JsCacheResult, JsCacheRemove) for method : " + method.getName(), ex);
			}
		} catch (MethodNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassNotFoundException | DataServiceException ex) {
			int stacktracelength = configuration.getStacktracelength();
			Throwable cause = ex;
			if (InvocationTargetException.class.isInstance(ex)) {
				cause = ex.getCause();
			}
			if (stacktracelength == 0 || logger.isDebugEnabled()) {
				logger.error("Invocation failed", ex);
			}
			messageToClient.setFault(new Fault(cause, stacktracelength));
		}
		client.getAsyncRemote().sendObject(messageToClient);
	}

	/**
	 * Get the method on origin class without proxies
	 *
	 * @param cls
	 * @param methodName
	 * @param parameterTypes
	 * @throws NoSuchMethodException
	 * @return
	 */
	private Method getNonProxiedMethod(Class cls, String methodName, Class[] parameterTypes) throws NoSuchMethodException {
		try {
			return cls.getMethod(methodName, parameterTypes);
		} catch (SecurityException ex) {
		}
		throw new NoSuchMethodException(methodName);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy