/******************************************************************************* | |
* 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()]); | |
} | |
} |