| /********************************************************************************* |
| * Copyright (c) 2020-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.visualization.ui.registry; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| 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.concurrent.CopyOnWriteArrayList; |
| import java.util.stream.Collectors; |
| |
| import javax.annotation.PostConstruct; |
| |
| import org.apache.commons.lang.ClassUtils; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| import org.osgi.service.component.annotations.ReferencePolicyOption; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Registry that collects all {@link Visualization} components. |
| */ |
| @Component(service=ModelVisualizationRegistry.class) |
| public class ModelVisualizationRegistry { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(ModelVisualizationRegistry.class); |
| |
| private ConcurrentHashMap<String, List<ModelVisualization>> visualizationRegistry = new ConcurrentHashMap<>(); |
| |
| @Reference( |
| cardinality = ReferenceCardinality.MULTIPLE, |
| policy = ReferencePolicy.DYNAMIC, |
| policyOption = ReferencePolicyOption.GREEDY) |
| void bindModelVisualization(Visualization visualization, Map<String, Object> properties) { |
| |
| String modelClassName = getModelClassName(visualization, properties); |
| if (modelClassName != null) { |
| List<ModelVisualization> visualizations = |
| visualizationRegistry.computeIfAbsent(modelClassName, key -> new CopyOnWriteArrayList<>()); |
| |
| String id = (String) properties.getOrDefault("id", visualization.getClass().getName()); |
| if (visualizations.stream().noneMatch(v -> v.getId().equals(id))) { |
| visualizations.add(new ModelVisualization( |
| id, |
| visualization, |
| (String) properties.getOrDefault("name", visualization.getClass().getSimpleName()), |
| (String) properties.getOrDefault("description", null), |
| modelClassName)); |
| } else { |
| LOGGER.error("A visualization with the ID {} already exists!", id); |
| } |
| } else { |
| LOGGER.error("Unable to extract model class name for Visualization {}", visualization.getClass().getName()); |
| } |
| } |
| |
| void unbindModelVisualization(Visualization visualization, Map<String, Object> properties) { |
| String modelClassName = getModelClassName(visualization, properties); |
| if (modelClassName != null) { |
| visualizationRegistry.remove(modelClassName); |
| } |
| } |
| |
| /** |
| * Get all {@link ModelVisualization} objects that are registered for the given classes. |
| * |
| * @param classes The classes for which the {@link ModelVisualization}s are requested. |
| * @return All {@link ModelVisualization} objects that are registered for the |
| * given classes. |
| */ |
| @SuppressWarnings("unchecked") |
| public List<ModelVisualization> getVisualizations(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 -> visualizationRegistry.getOrDefault(name, new ArrayList<>()).stream()) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Extracts the model class name for which the {@link Visualization} 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 visualization The {@link Visualization} for which the model class name |
| * should be returned. |
| * @param properties The component properties map of the |
| * {@link Visualization} object. |
| * @return The model class name for which the {@link Visualization} should be |
| * registered. |
| */ |
| private String getModelClassName(Visualization visualization, 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<? extends Visualization> visuClass = visualization.getClass(); |
| Method[] methods = visuClass.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; |
| } |
| |
| } |