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