/*******************************************************************************
* 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.json.service;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.butor.json.CommonRequestArgs;
import org.butor.json.JsonHelper;
import org.butor.json.JsonServiceRequest;
import org.butor.utils.ApplicationException;
import org.butor.utils.CommonMessageID;
import org.butor.utils.Message;
import org.butor.utils.Message.MessageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.base.Preconditions;
public class DefaultServiceManager implements ServiceManager,InitializingBean,BeanFactoryAware {
protected Logger _logger = LoggerFactory.getLogger(getClass());
private JsonHelper _jsh = new JsonHelper();
private PlatformTransactionManager transactionManager;
private BeanFactory beanFactory;
private ConcurrentMap<String, ServiceComponent> _cmps =
new ConcurrentHashMap<String, ServiceComponent>();
private List<ServiceComponent> _components;
@Override
public void registerServices(List<ServiceComponent> components_) {
for (ServiceComponent ser : components_)
registerService(ser);
}
@Override
public void registerService(ServiceComponent serviceCmp_) {
String ns = serviceCmp_.getNamespace();
if (_cmps.containsKey(ns)) {
_logger.error(String.format("Namespace %s has been used by component %s",
ns, _cmps.get(ns).toString()));
return;
}
_cmps.put(ns, serviceCmp_);
}
@Override
public void unregisterService(ServiceComponent cmp_) {
_cmps.remove(cmp_.getNamespace());
}
@Override
public void invoke(final Context ctx_) {
JsonServiceRequest req = (JsonServiceRequest)ctx_.getRequest();
final String namespace = req.getNamespace();
final ServiceComponent cmp = _cmps.get(namespace);
if (cmp == null) {
String msg = String.format("No service component found with ns=%s", namespace);
_logger.info(msg);
return;
}
List<?> params = _jsh.deserialize(req.getServiceArgsJson(), List.class);
int nbArgs = params.size() +1; // +1 for context arg
final String serviceName = req.getService();
final Method serviceMethod = cmp.getService(serviceName, nbArgs);
if (serviceMethod == null) {
String msg = String.format("No service=%s found with ns=%s",
serviceName, namespace);
_logger.info(msg);
ctx_.getResponseHandler().addMessage(new Message(0, MessageType.ERROR, msg));
return;
}
try {
final CommonRequestArgs cr = new CommonRequestArgs();
cr.setLang(req.getLang());
cr.setReqId(req.getReqId());
cr.setSessionId(req.getSessionId());
cr.setUserId(req.getUserId());
Context ctx = new Context() {
@Override
public ResponseHandler<Object> getResponseHandler() {
return ctx_.getResponseHandler();
}
@Override
public CommonRequestArgs getRequest() {
return cr;
}
};
Class<?>[] pts = serviceMethod.getParameterTypes();
final Object[] serviceParameters = new Object[pts.length];
serviceParameters[0] = ctx;
for (int ii=1; ii<pts.length; ii++) {
Object par = params.get(ii-1);
String so = _jsh.serialize(par);
serviceParameters[ii] = _jsh.deserialize(so, pts[ii]);
}
if (serviceMethod.isAnnotationPresent(Transactional.class)) {
Transactional trx = serviceMethod.getAnnotation(Transactional.class);
Preconditions.checkNotNull(transactionManager,"The method is transactionnal, but no transaction manager was detected!");
TransactionTemplate trxTpl = new TransactionTemplate(transactionManager);
trxTpl.setIsolationLevel(trx.isolation().value());
trxTpl.setReadOnly(trx.readOnly());
trxTpl.setPropagationBehavior(trx.propagation().value());
trxTpl.setTimeout(trx.timeout());
trxTpl.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
serviceMethod.invoke(cmp.getComponent(), serviceParameters);
} catch (Throwable e) {
status.setRollbackOnly();
if (e instanceof InvocationTargetException) {
handleException(ctx_, namespace, serviceName, ((InvocationTargetException)e).getTargetException());
} else {
handleException(ctx_, namespace, serviceName, e);
}
}
}});
} else {
serviceMethod.invoke(cmp.getComponent(), serviceParameters);
}
} catch (InvocationTargetException e) {
handleException(ctx_, namespace, serviceName, e.getTargetException());
} catch (Throwable e) {
handleException(ctx_, namespace, serviceName, e);
}
}
private void handleException(final Context ctx_, String ns, String serviceName, Throwable e) {
if (e instanceof ApplicationException) {
ApplicationException appEx = (ApplicationException)e;
_logger.warn(String.format("Failed to invoke service e=%s, ns=%s",
serviceName, ns), appEx);
for (Message message : appEx.getMessages()) {
ctx_.getResponseHandler().addMessage(message);
}
} else {
_logger.error(String.format("Failed to invoke service=%s, ns=%s",
serviceName, ns), e);
ctx_.getResponseHandler().addMessage(CommonMessageID.SERVICE_FAILURE.getMessage());
}
}
public List<ServiceComponent> getComponents() {
return _components;
}
public void setComponents(List<ServiceComponent> components_) {
_components = components_;
registerServices(components_);
}
@Override
public void afterPropertiesSet() throws Exception {
try {
transactionManager = beanFactory.getBean(PlatformTransactionManager.class);
} catch (NoSuchBeanDefinitionException e) {
_logger.warn("No (or more than one) TransactionManager defined in your Spring context. Will not set transaction manager.",e);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}