blob: 01f33c2e37bb6a73a16703ae36c97c23a993c708 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.scout.commons.BeanUtility;
import org.eclipse.scout.commons.CompositeObject;
import org.eclipse.scout.commons.ConfigIniUtility;
import org.eclipse.scout.commons.TypeCastUtility;
import org.eclipse.scout.commons.VerboseUtility;
import org.eclipse.scout.commons.beans.FastBeanInfo;
import org.eclipse.scout.commons.beans.FastPropertyDescriptor;
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.commons.holders.HolderUtility;
import org.eclipse.scout.commons.holders.IHolder;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.service.internal.AbstractHolderArgumentVisitor;
/**
* Handle calls directly on current sesseion (no remoting)
*/
public final class ServiceUtility {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(ServiceUtility.class);
private ServiceUtility() {
}
/**
* see {@link INullService} and {@link SERVICES#getService(Class)}
* <p>
* Creates a void proxy for a service interface that does nothing and uses a classloader that return itself for every
* query. This trick voids out the ServiceUse class type check.
*/
public static final INullService NULL_SERVICE;
static {
INullService n = null;
try {
ClassLoader identityLoader = new ClassLoader(INullService.class.getClassLoader()) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("java.lang.")) {
return super.loadClass(name);
}
return INullService.class;
}
};
n = (INullService) Proxy.newProxyInstance(identityLoader, new Class<?>[]{INullService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
}
catch (Throwable t) {
//nop
}
NULL_SERVICE = n;
}
/**
* Inject config.ini bean properties.
* <p>
* A config ini bean property is a config.ini entry in the format classOrInterfaceName#propertyName=value
* <p>
* Example: org.myproject.IMyService#endpointAddress=http://....
* <p>
* or
* <p>
* <br>
* client.id=13 org.myproject.MyService#clientId=${client.id}
* <p>
* The property is set if service is a subtype of the config ini type.
*/
public static void injectConfigProperties(Object service) {
if (service == null) {
return;
}
FastBeanInfo beanInfo = BeanUtility.getFastBeanInfo(service.getClass(), null);
Map<String, String> map = ConfigIniUtility.getProperties(service.getClass());
for (Map.Entry<String, String> e : map.entrySet()) {
String name = e.getKey();
String text = e.getValue();
try {
FastPropertyDescriptor propDesc = beanInfo.getPropertyDescriptor(name);
Method setterMethod = propDesc.getWriteMethod();
if (setterMethod != null) {
Object value = TypeCastUtility.castValue(text, propDesc.getPropertyType());
setterMethod.invoke(service, value);
}
else {
LOG.warn("no setter for " + name + "=" + text + " on " + service.getClass());
}
}
catch (Exception ex) {
LOG.error("setting " + name + "=" + text + " on " + service.getClass(), ex);
}
}
}
/**
* @param service
* @param operation
* @param paramTypes
* @return the reflective service operation that can be called using {@link #invoke(Method,Object,Object[])}
* @throws ProcessingException
*/
public static Method getServiceOperation(Class<?> serviceClass, String operation, Class<?>[] paramTypes) throws ProcessingException {
try {
if (serviceClass == null) {
throw new ProcessingException("service class is null");
}
return serviceClass.getMethod(operation, paramTypes);
}
catch (ProcessingException e) {
throw e;
}
catch (Throwable t) {
if (t instanceof InvocationTargetException) {
Throwable test = ((InvocationTargetException) t).getTargetException();
if (test != null) {
t = test;
}
}
if (t instanceof ProcessingException) {
throw (ProcessingException) t;
}
else {
throw new ProcessingException(serviceClass.getName() + "#" + operation, t);
}
}
}
/**
* @param serviceOperation
* @param service
* @param callerArgs
* @return the service result
* @throws ProcessingException
* Invoke the service operation usign reflection. The service supports OUT variables using {@link IHolder}
* objects
*/
public static Object invoke(Method serviceOperation, Object service, Object[] callerArgs) throws ProcessingException {
try {
if (serviceOperation == null) {
throw new ProcessingException("serviceOperation is null");
}
if (service == null) {
throw new ProcessingException("service is null");
}
if (callerArgs == null) {
callerArgs = new Object[0];
}
Object data = serviceOperation.invoke(service, callerArgs);
return data;
}
catch (ProcessingException e) {
throw e;
}
catch (Throwable t) {
if (t instanceof InvocationTargetException) {
Throwable test = ((InvocationTargetException) t).getTargetException();
if (test != null) {
t = test;
}
}
if (t instanceof ProcessingException) {
throw (ProcessingException) t;
}
else {
throw new ProcessingException("service: " + service.getClass() + ", operation: " + serviceOperation.getName() + ", args: " + VerboseUtility.dumpObjects(callerArgs), t);
}
}
}
/**
* Holders and nvpairs need to be copied as value clones. A smartfield for
* example is a holder and must not go to backend. NVPairs with holder values
* ae replaced by NVPair with serializable holder arguments
*/
public static Object[] filterHolderArguments(Object[] callerArgs) {
Object[] serializableArgs = new Object[callerArgs.length];
new AbstractHolderArgumentVisitor() {
@SuppressWarnings("unchecked")
@Override
public void visitHolder(IHolder input, IHolder output) {
if (!HolderUtility.containEqualValues(output, input)) {
output.setValue(input.getValue());
}
}
@Override
public void visitOther(Object[] input, Object[] output, int index) {
output[index] = input[index];
}
}.startVisiting(callerArgs, serializableArgs, 1, true);
return serializableArgs;
}
/**
* Extract holders and nvpairs in callerArgs (and eventually in sub-arrays)
*/
public static Object[] extractHolderArguments(Object[] callerArgs) {
Object[] holderArgs = new Object[callerArgs.length];
new AbstractHolderArgumentVisitor() {
@Override
public void visitHolder(IHolder input, IHolder output) {
}
@Override
public void visitOther(Object[] input, Object[] output, int index) {
}
}.startVisiting(callerArgs, holderArgs, 1, true);
return holderArgs;
}
/**
* Apply changed holder and nvpair values from updatedArgs to callerArgs
*
* @param clearNonOutArgs
* if true deletes calerArgs that aren't out parameters
*/
@SuppressWarnings("unchecked")
public static void updateHolderArguments(Object[] callerArgs, Object[] updatedArgs, final boolean clearNonOutArgs) {
if (updatedArgs != null) {
new AbstractHolderArgumentVisitor() {
@Override
public void visitHolder(IHolder input, IHolder output) {
if (!HolderUtility.containEqualValues(output, input)) {
output.setValue(input.getValue());
}
}
@Override
public void visitOther(Object[] input, Object[] output, int index) {
if (clearNonOutArgs) {
output[index] = null;
}
}
}.startVisiting(updatedArgs, callerArgs, 1, false);
}
}
@SuppressWarnings("unchecked")
public static Class[] getInterfacesHierarchy(Class type, Class filterClass) {
HashSet<Class> resultSet = new HashSet<Class>();
ArrayList<Class> workList = new ArrayList<Class>();
ArrayList<Class> lookAheadList = new ArrayList<Class>();
if (type.isInterface()) {
lookAheadList.add(type);
}
else {
Class test = type;
while (test != null) {
lookAheadList.addAll(Arrays.asList(test.getInterfaces()));
test = test.getSuperclass();
}
}
while (lookAheadList.size() > 0) {
workList = lookAheadList;
lookAheadList = new ArrayList<Class>();
for (Class c : workList) {
if (!resultSet.contains(c)) {
resultSet.add(c);
// look ahead
Class[] ifs = c.getInterfaces();
if (ifs.length > 0) {
lookAheadList.addAll(Arrays.asList(ifs));
}
}
}
}
TreeMap<CompositeObject, Class> resultMap = new TreeMap<CompositeObject, Class>();
int index = 0;
for (Class c : resultSet) {
if (filterClass.isAssignableFrom(c)) {
int depth = 0;
Class test = c;
while (test != null) {
depth++;
Class[] xa = test.getInterfaces();
test = null;
if (xa != null) {
for (Class x : xa) {
if (filterClass.isAssignableFrom(x)) {
test = x;
}
}
}
}
resultMap.put(new CompositeObject(depth, index++), c);
}
}
return resultMap.values().toArray(new Class[resultMap.size()]);
}
}