package org.camunda.bpm.extension.osgi.el;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.beanutils.PropertyUtils;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.camunda.bpm.engine.impl.javax.el.BeanELResolver;
import org.camunda.bpm.engine.impl.javax.el.ELContext;
import org.camunda.bpm.engine.impl.pvm.delegate.ActivityBehavior;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
public class OSGiELResolver extends BeanELResolver {
private static final String LDAP_FILTER_KEY = "processExpression";
@Override
public Object getValue(ELContext context, Object base, Object property) {
Object returnValue = null;
if (base == null) {
String key = (String) property;
try {
String ldapFilter = "(" + LDAP_FILTER_KEY + "=" + key + ")";
// start with LDAP filter
returnValue = checkRegisteredServicesByLdapFilter(ldapFilter);
if (returnValue == null) {
// go on with JavaDelegates
returnValue = checkRegisteredOsgiServices(JavaDelegate.class, key);
}
if (returnValue == null) {
// finally ActivitiBehaviors
returnValue = checkRegisteredOsgiServices(ActivityBehavior.class, key);
}
} catch (InvalidSyntaxException e) {
throw new RuntimeException(e);
}
} else {
try {
boolean readable = PropertyUtils.isReadable(base, property.toString());
if (readable) {
returnValue = PropertyUtils.getProperty(base, property.toString());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (returnValue != null) {
context.setPropertyResolved(true);
}
return returnValue;
}
/**
* Checks the OSGi ServiceRegistry if a service matching the given filter is
* present.
*
* @param filter
* the LDAP filter
* @return null if no service could be found or the service object
* @throws InvalidSyntaxException
* if the filter has an invalid syntax
* @throws RuntimeException
* if more than one service is found
*/
private Object checkRegisteredServicesByLdapFilter(String filter) throws InvalidSyntaxException {
ServiceReference<?>[] references = getBundleContext().getServiceReferences((String) null, filter);
if (isEmptyOrNull(references)) {
return null;
}
if (references.length == 1) {
return getBundleContext().getService(references[0]);
}
throw new RuntimeException("Too many services registered for filter: " + filter);
}
/**
* Checks the OSGi ServiceRegistry if a service matching the class and key are
* present. The class name has to match the key where the first letter has to
* be lower case.
* <p>
* For example:<br/>
* <code>
* public class MyServiceTask extends JavaDelegate</code> <br/>
* matches {@link JavaDelegate} with key "myServiceTask".
*
* @param serviceClazz
* @param key
* the name of the class
* @return null if no service could be found or the service object
* @throws RuntimeException
* if more than one service is found
*/
private <T> T checkRegisteredOsgiServices(Class<T> serviceClazz, String key) throws InvalidSyntaxException {
Collection<ServiceReference<T>> references = getBundleContext().getServiceReferences(serviceClazz, null);
if (references == null || references.isEmpty()) {
return null;
}
Collection<T> matches = checkIfClassNamesMatchKey(references, key);
if (matches.size() == 1) {
return matches.iterator().next();
} else if (matches.size() > 1) {
throw new RuntimeException("Too many " + serviceClazz + " registered with name: " + key);
}
// zero matches
return null;
}
/**
* Gets the service objects from the {@link BundleContext} and compares the
* class names to the given key. For the comparison see
* {@link #checkRegisteredOsgiServices(String, String)}.
*
* @param references
* @param key
* @return
*/
private <T> Collection<T> checkIfClassNamesMatchKey(Collection<ServiceReference<T>> references, String key) {
Set<T> result = new HashSet<T>();
for (ServiceReference<T> ref : references) {
T service = getBundleContext().getService(ref);
if (service != null) {
String keyWithFirstLetterUppercase = Character.toUpperCase(key.charAt(0)) + key.substring(1);
if (service.getClass().getSimpleName().equals(keyWithFirstLetterUppercase)) {
result.add(service);
}
}
}
return result;
}
private boolean isEmptyOrNull(ServiceReference<?>[] serviceReferences) {
return serviceReferences == null ? true : serviceReferences.length == 0;
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
protected BundleContext getBundleContext() {
return FrameworkUtil.getBundle(getClass()).getBundleContext();
}
}