/*******************************************************************************
* Copyright 2013 butor.com
*
* 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.butor.web.servlet;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.butor.json.JsonHelper;
import org.butor.json.JsonServiceRequest;
import org.butor.json.service.Context;
import org.butor.json.service.ServiceCallerFactory;
import org.butor.utils.Message;
import org.butor.utils.Message.MessageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import com.google.common.base.Preconditions;
import com.google.common.reflect.Reflection;
/**
* Factory class that return a proxy of the service interface
* that calls a remote service
*
* This eliminate the need of creating a new service class
* that use a service caller to invoke remote services with identical
* name and signature (pass through services).
*
* @author asawan
*
* @param <T>
*/
public class AjaxServiceCallerFactory<T> implements InitializingBean, FactoryBean<T>{
protected Logger _logger = LoggerFactory.getLogger(getClass());
private Class<T> serviceInterface;
private T proxy;
private String namespace;
private String url;
private ServiceCallerFactory scf;
private int maxPayloadLengthToLog = -1;
private Set<String> servicesToNotLogArgs = Collections.emptySet();
@Override
public void afterPropertiesSet() throws Exception {
Preconditions.checkNotNull(namespace,"namespace is mandatory");
Preconditions.checkNotNull(url, "url is mandatory");
Preconditions.checkNotNull(serviceInterface, "serviceInterface is mandatory");
scf = createServiceCallerFactory(namespace, url, maxPayloadLengthToLog, servicesToNotLogArgs);
proxy = buildProxy();
}
public ServiceCallerFactory createServiceCallerFactory(String namespace,
String url, int maxPayloadLengthToLog, Set<String> servicesToNotLogArgs) {
return new ServiceCallerFactory(namespace, url, maxPayloadLengthToLog, servicesToNotLogArgs);
}
@Override
public T getObject() throws Exception {
return proxy;
}
@Override
public Class<?> getObjectType() {
return serviceInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public void setServiceInterface(Class<T> serviceInterface) {
this.serviceInterface = serviceInterface;
}
private String buildKey(String methodName_, int nbArgs_) {
return String.format("%s.%d", methodName_, nbArgs_);
}
private T buildProxy() {
final T serviceCaller = scf.createServiceCaller(serviceInterface);
final Map<String, Method> methodsMap = new HashMap<String, Method>();
for (Method m : serviceCaller.getClass().getMethods()) {
String serviceName = m.getName();
if (Modifier.isPublic(m.getModifiers())) {
Class<?>[] argsTypes = m.getParameterTypes();
if (argsTypes.length > 0
&& Context.class.isAssignableFrom(argsTypes[0])) {
String key = buildKey(serviceName, argsTypes.length);
Preconditions.checkArgument(
!methodsMap.containsKey(key),
"Service with first parameter Context and same name and same number of args %s.%s",
serviceInterface, serviceName);
methodsMap.put(key, m);
}
}
}
final JsonHelper jsh = new JsonHelper();
InvocationHandler ih = new InvocationHandler() {
//private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
AjaxContext actx = (AjaxContext)args[0];
try {
String key = buildKey(method.getName(), args.length);
Method target = methodsMap.get(key);
if (target == null) {
//TODO
return null;
}
Context ctx = scf.getContext(actx.getRequest().getUserId(),
actx.getRequest().getSessionId(),
actx.getRequest().getLang(), actx.getResponseHandler());
Object[] serviceArgs = new Object[args.length];
serviceArgs[0] = ctx;
for (int ii=1; ii<args.length;ii++)
serviceArgs[ii] = args[ii];
JsonServiceRequest jsr = (JsonServiceRequest)ctx.getRequest();
String sargs = jsh.serialize(serviceArgs);
//logger.info("calling service {} with args {}", method.getName(), sargs);
jsr.setService(method.getName());
jsr.setServiceArgsJson(sargs);
return target.invoke(serviceCaller, serviceArgs);
} catch (Exception ex) {
actx.getResponseHandler().addMessage(new Message(0, MessageType.ERROR,
ex.getCause().toString()));
}
return null;
}
@Override
public String toString() {
return String.format("ServiceCallerInvokationHandler of service interface %s",
serviceInterface);
}
};
return Reflection.newProxy(serviceInterface, ih);
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public void setUrl(String url) {
this.url = url;
}
public String getNamespace() {
return namespace;
}
public void setMaxPayloadLengthToLog(int maxPayloadLengthToLog) {
this.maxPayloadLengthToLog = maxPayloadLengthToLog;
}
public void setServicesToNotLogArgs(Set<String> servicesToNotLogArgs) {
this.servicesToNotLogArgs = servicesToNotLogArgs;
}
}