/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI licenses this file to you 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.openengsb.core.services.internal;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.lang.ClassUtils;
import org.openengsb.core.api.Connector;
import org.openengsb.core.api.ConnectorInstanceFactory;
import org.openengsb.core.api.ConnectorValidationFailedException;
import org.openengsb.core.api.Constants;
import org.openengsb.core.api.Domain;
import org.openengsb.core.api.DomainProvider;
import org.openengsb.core.api.MixinDomain;
import org.openengsb.core.api.OpenEngSBService;
import org.openengsb.core.api.OsgiUtilsService;
import org.openengsb.core.api.model.ConnectorDescription;
import org.openengsb.core.api.security.model.SecurityAttributeEntry;
import org.openengsb.core.common.SecurityAttributeProviderImpl;
import org.openengsb.core.ekb.api.TransformationEngine;
import org.openengsb.core.util.DefaultOsgiUtilsService;
import org.openengsb.core.util.FilterUtils;
import org.openengsb.core.util.MapAsDictionary;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class ConnectorRegistrationManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectorRegistrationManager.class);
/*
* These attributes may not be removed from a service.
*/
private static final List<String> PROTECTED_PROPERTIES = Arrays.asList(
org.osgi.framework.Constants.SERVICE_ID,
org.osgi.framework.Constants.SERVICE_PID,
org.osgi.framework.Constants.OBJECTCLASS,
Constants.DOMAIN_KEY,
Constants.CONNECTOR_KEY,
"location.root");
private static final Set<String> INTERCEPTOR_BLACKLIST = Sets.newHashSet("authentication", "authorization");
private static final Class<?>[] CONNECTOR_INSTANCE_COMMON_INTERFACES =
new Class<?>[]{ OpenEngSBService.class, Domain.class, Connector.class };
private OsgiUtilsService serviceUtils;
private BundleContext bundleContext;
private SecurityAttributeProviderImpl attributeStore;
private Map<String, ServiceRegistration> registrations = Maps.newHashMap();
private Map<String, Connector> instances = Maps.newHashMap();
private MethodInterceptor securityInterceptor;
private TransformationEngine transformationEngine;
public ConnectorRegistrationManager(BundleContext bundleContext, TransformationEngine transformationEngine,
MethodInterceptor securityInterceptor, SecurityAttributeProviderImpl attributeStore) {
this.bundleContext = bundleContext;
serviceUtils = new DefaultOsgiUtilsService(bundleContext);
this.transformationEngine = transformationEngine;
this.securityInterceptor = securityInterceptor;
this.attributeStore = attributeStore;
}
public void updateRegistration(String id, ConnectorDescription connectorDescription)
throws ConnectorValidationFailedException {
if (!instances.containsKey(id)) {
createService(id, connectorDescription);
} else if (connectorDescription.getAttributes() != null) {
updateAttributes(id, connectorDescription);
}
updateProperties(id, connectorDescription.getProperties());
}
public void forceUpdateRegistration(String id, ConnectorDescription connectorDescription) {
if (!instances.containsKey(id)) {
forceCreateService(id, connectorDescription);
} else if (connectorDescription.getAttributes() == null) {
forceUpdateAttributes(id, connectorDescription);
}
updateProperties(id, connectorDescription.getProperties());
};
public void remove(String id) {
registrations.get(id).unregister();
registrations.remove(id);
// FIXME: [OPENENGSB-1809] clean way to shutdown the container
instances.remove(id);
}
private void createService(String id, ConnectorDescription description)
throws ConnectorValidationFailedException {
ConnectorInstanceFactory factory = getConnectorFactoryForDescription(description);
Map<String, String> errors = factory.getValidationErrors(description.getAttributes());
if (!errors.isEmpty()) {
throw new ConnectorValidationFailedException(errors);
}
finishCreatingInstance(id, description, factory);
}
private void forceCreateService(String id, ConnectorDescription description) {
ConnectorInstanceFactory factory = getConnectorFactoryForDescription(description);
finishCreatingInstance(id, description, factory);
}
private void finishCreatingInstance(String id, ConnectorDescription description,
ConnectorInstanceFactory factory) {
Connector serviceInstance = factory.createNewInstance(id.toString());
if (serviceInstance == null) {
throw new IllegalStateException("Factory cannot create a new service for instance id " + id.toString());
}
serviceInstance = factory.applyAttributes(serviceInstance, description.getAttributes());
if (!description.getAttributes().containsKey(Constants.SKIP_SET_DOMAIN_TYPE)) {
serviceInstance.setDomainId(description.getDomainType());
serviceInstance.setConnectorId(description.getConnectorType());
}
instances.put(id, serviceInstance);
doRegisterServiceInstance(id, description, serviceInstance);
}
private void doRegisterServiceInstance(String id, ConnectorDescription description, Connector serviceInstance) {
if (registrations.containsKey(id)) {
registrations.get(id).unregister();
registrations.remove(id);
}
Class<? extends Domain> domainInterface = getDomainProvider(description.getDomainType()).getDomainInterface();
Class<?>[] clazzes = generateInterfaceListForRegistration(domainInterface, serviceInstance);
Connector proxiedInstance = proxyForTransformation(serviceInstance, domainInterface, clazzes);
proxiedInstance = proxyForSecurity(id, description, proxiedInstance, clazzes);
Map<String, Object> properties = populatePropertiesWithRequiredAttributes(id, description);
ServiceRegistration serviceRegistration =
bundleContext.registerService(convertClassesToNames(clazzes), proxiedInstance,
MapAsDictionary.wrap(properties));
registrations.put(id, serviceRegistration);
}
private Class<?>[] generateInterfaceListForRegistration(Class<? extends Domain> domainInterface,
Connector serviceInstance) {
Set<Class<?>> classSet = Sets.newHashSet(CONNECTOR_INSTANCE_COMMON_INTERFACES);
classSet.add(domainInterface);
classSet.addAll(getMetaDomainInterfacesFromInstance(serviceInstance));
return classSet.toArray(new Class<?>[classSet.size()]);
}
private Set<Class<?>> getMetaDomainInterfacesFromInstance(Connector serviceInstance) {
Set<Class<?>> result = Sets.newHashSet();
for (Class<?> interfaze : serviceInstance.getClass().getInterfaces()) {
if (interfaze.getAnnotation(MixinDomain.class) != null) {
result.add(interfaze);
}
}
return result;
}
private Connector proxyForSecurity(String id, ConnectorDescription description, Connector serviceInstance,
Class<?>[] clazzes) {
if (INTERCEPTOR_BLACKLIST.contains(description.getDomainType())) {
LOGGER.info("not proxying service because domain is blacklisted: {} ", serviceInstance);
return serviceInstance;
}
if (securityInterceptor == null) {
LOGGER.warn("security interceptor is not available yet");
return serviceInstance;
}
// @extract-start register-secure-service-code
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(clazzes);
proxyFactory.addAdvice(securityInterceptor);
proxyFactory.setTarget(serviceInstance);
Connector result = (Connector) proxyFactory.getProxy(this.getClass().getClassLoader());
// @extract-end
attributeStore.replaceAttributes(serviceInstance, new SecurityAttributeEntry("name", id));
return result;
}
private Connector proxyForTransformation(Connector serviceInstance, Class<? extends Domain> domainInterface,
Class<?>[] clazzes) {
if (domainInterface.isAssignableFrom(serviceInstance.getClass())) {
return serviceInstance;
}
LOGGER.info("creating transforming proxy for connector-service");
return (Connector) Proxy.newProxyInstance(domainInterface.getClassLoader(),
clazzes, new TransformingConnectorHandler(transformationEngine, serviceInstance));
}
private String[] convertClassesToNames(Class<?>[] clazzes) {
List<Class<?>> classList = Arrays.asList(clazzes);
List<String> list = ClassUtils.convertClassesToClassNames(classList);
return list.toArray(new String[list.size()]);
}
private Map<String, Object> populatePropertiesWithRequiredAttributes(
String id, ConnectorDescription description) {
Map<String, Object> properties = description.getProperties();
Map<String, Object> result = new HashMap<String, Object>(properties);
for (Entry<String, Object> entry : properties.entrySet()) {
result.put(entry.getKey(), entry.getValue());
}
result.put(Constants.DOMAIN_KEY, description.getDomainType());
result.put(Constants.CONNECTOR_KEY, description.getConnectorType());
result.put(org.osgi.framework.Constants.SERVICE_PID, id);
return result;
}
private void forceUpdateAttributes(String id, ConnectorDescription description) {
ConnectorInstanceFactory factory = getConnectorFactoryForDescription(description);
Connector current = instances.get(id);
doUpdateAttributes(id, description, factory, current);
}
private void updateAttributes(String id, ConnectorDescription description)
throws ConnectorValidationFailedException {
ConnectorInstanceFactory factory = getConnectorFactoryForDescription(description);
Connector current = instances.get(id);
Map<String, String> validationErrors = factory.getValidationErrors(current, description.getAttributes());
if (!validationErrors.isEmpty()) {
throw new ConnectorValidationFailedException(validationErrors);
}
doUpdateAttributes(id, description, factory, current);
}
private void doUpdateAttributes(String id, ConnectorDescription description,
ConnectorInstanceFactory factory, Connector current) {
Connector connector = factory.applyAttributes(current, description.getAttributes());
if (connector != current) {
instances.put(id, connector);
doRegisterServiceInstance(id, description, connector);
}
}
private void updateProperties(String id, Map<String, Object> properties) {
if (properties == null) {
return;
}
Map<String, Object> newProps = new HashMap<String, Object>(properties);
ServiceRegistration registration = registrations.get(id);
ServiceReference reference = registration.getReference();
for (String key : PROTECTED_PROPERTIES) {
if (newProps.get(key) == null) {
Object originalValue = reference.getProperty(key);
if (originalValue != null) {
newProps.put(key, originalValue);
}
}
}
registration.setProperties(MapAsDictionary.wrap(newProps));
}
protected ConnectorInstanceFactory getConnectorFactoryForDescription(ConnectorDescription description) {
Filter connectorFilter = FilterUtils.makeFilter(ConnectorInstanceFactory.class,
String.format("(&(%s=%s)(%s=%s))",
Constants.DOMAIN_KEY, description.getDomainType(),
Constants.CONNECTOR_KEY, description.getConnectorType()));
ConnectorInstanceFactory service =
serviceUtils.getOsgiServiceProxy(connectorFilter, ConnectorInstanceFactory.class);
return service;
}
private DomainProvider getDomainProvider(String domain) {
Filter domainFilter =
FilterUtils.makeFilter(DomainProvider.class, String.format("(%s=%s)", Constants.DOMAIN_KEY, domain));
DomainProvider domainProvider = serviceUtils.getOsgiServiceProxy(domainFilter, DomainProvider.class);
return domainProvider;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
serviceUtils = new DefaultOsgiUtilsService(bundleContext);
}
public void setSecurityInterceptor(MethodInterceptor securityInterceptor) {
this.securityInterceptor = securityInterceptor;
}
public void setAttributeStore(SecurityAttributeProviderImpl attributeStore) {
this.attributeStore = attributeStore;
}
}