blob: abe559def3314cb33220ad6d0d544f5de8ab0c09 [file] [log] [blame]
/*********************************************************************************
* Copyright (c) 2021 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.model.editor.contribution.registry;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.apache.commons.lang.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ModelServiceRegistry<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelServiceRegistry.class);
private ConcurrentHashMap<String, Map<String, RegistryServiceWrapper<T>>> registry = new ConcurrentHashMap<>();
protected void bindService(T service, Map<String, Object> properties) {
String modelClassName = getModelClassName(service, properties);
if (modelClassName != null) {
Map<String, RegistryServiceWrapper<T>> services =
this.registry.computeIfAbsent(modelClassName, key -> new ConcurrentHashMap<String, RegistryServiceWrapper<T>>());
String id = (String) properties.getOrDefault("id", service.getClass().getName());
if (!services.containsKey(id)) {
services.put(id,
new RegistryServiceWrapper<>(
id,
service,
(String) properties.getOrDefault("name", service.getClass().getSimpleName()),
(String) properties.getOrDefault("description", null),
modelClassName));
} else {
LOGGER.error("A contribution service with the ID {} already exists!", id);
}
} else {
LOGGER.error("Unable to extract model class name for contribution service {}", service.getClass().getName());
}
}
protected void unbindService(T service, Map<String, Object> properties) {
String className = getModelClassName(service, properties);
String id = (String) properties.getOrDefault("id", service.getClass().getName());
if (className != null) {
Map<String, RegistryServiceWrapper<T>> services = this.registry.getOrDefault(className, new HashMap<>());
services.remove(id);
}
}
/**
* Get all service wrapper objects that are registered for the given classes.
*
* @param classes The classes for which the wrappers are requested.
* @return All service wrapper objects that are registered for the
* given classes.
*/
@SuppressWarnings("unchecked")
public List<RegistryServiceWrapper<T>> getServices(List<Class<?>> classes) {
HashSet<String> classNames = new LinkedHashSet<>();
for (Class<?> clazz : classes) {
if (clazz != null) {
classNames.add(clazz.getName());
List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(clazz);
classNames.addAll(allInterfaces.stream().map(Class::getName).collect(Collectors.toList()));
}
}
return classNames.stream()
.filter(Objects::nonNull)
.flatMap(name -> this.registry.getOrDefault(name, new HashMap<>()).values().stream())
.collect(Collectors.toList());
}
/**
*
* @param modelClassName The class name for which the service is registered.
* @param id The id of the service.
* @return The service wrapper.
*/
public RegistryServiceWrapper<T> getService(String modelClassName, String id) {
return this.registry.getOrDefault(modelClassName, new HashMap<>()).get(id);
}
/**
* Extracts the model class name for which the service should be
* registered. First checks the <i>modelClass</i> component property. If it is
* not set the type of the first parameter of the method annotated with
* {@link PostConstruct} will be returned.
*
* @param service The service for which the model class name
* should be returned.
* @param properties The component properties map of the
* service object.
* @return The model class name for which the service should be
* registered.
*/
private String getModelClassName(T service, Map<String, Object> properties) {
// check if property for modelClass is set
String modelClassName = (String) properties.getOrDefault("modelClass", null);
if (modelClassName == null) {
// else find method annotated with @PostConstruct
Class<?> creationClass = service.getClass();
Method[] methods = creationClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(PostConstruct.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 0) {
if (Collection.class.isAssignableFrom(parameterTypes[0])) {
// extract generic information for List support
Type[] genericParameterTypes = method.getGenericParameterTypes();
if (genericParameterTypes[0] instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType)genericParameterTypes[0]).getActualTypeArguments();
modelClassName = typeArguments.length > 0 ? typeArguments[0].getTypeName() : null;
}
} else {
modelClassName = parameterTypes[0].getName();
}
break;
}
}
}
}
return modelClassName;
}
}