001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.ametys.core.ui; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.function.Function; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.configuration.ConfigurationException; 030import org.apache.avalon.framework.configuration.DefaultConfiguration; 031import org.apache.avalon.framework.configuration.MutableConfiguration; 032import org.apache.avalon.framework.service.ServiceException; 033 034import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 035 036/** 037 * Implementation of an ExtensionPoint for client side elements. 038 * @param <T> The client side element implementation 039 */ 040public abstract class AbstractClientSideExtensionPoint<T extends ClientSideElement> extends AbstractThreadSafeComponentExtensionPoint<T> 041{ 042 private Map<String, Configuration> _configurations = new HashMap<>(); 043 private Map<String, String> _configurationPlugins = new HashMap<>(); 044 045 private List<AbstractClientSideExtensionPoint<T>> _registeredManagers = new ArrayList<>(); 046 047 private List<ReferencingExtension> _referencingExtensions = new ArrayList<>(); 048 049 private boolean _initialized; 050 051 @Override 052 public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException 053 { 054 if (configuration.getAttribute("ref-id", null) != null) 055 { 056 if (_initialized) 057 { 058 _addReferencingExtension(id, pluginName, featureName, configuration); 059 } 060 else 061 { 062 _referencingExtensions.add(new ReferencingExtension(id, pluginName, featureName, configuration)); 063 } 064 } 065 else 066 { 067 _configurations.put(id, configuration); 068 _configurationPlugins.put(id, pluginName); 069 super.addExtension(id, pluginName, featureName, configuration); 070 } 071 } 072 073 private void _addReferencingExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException 074 { 075 try 076 { 077 String refId = configuration.getAttribute("ref-id"); 078 String extensionPoint = configuration.getAttribute("point"); 079 AbstractClientSideExtensionPoint clientSideElementManager = (AbstractClientSideExtensionPoint) _cocoonManager.lookup(extensionPoint); 080 Configuration baseConfiguration = clientSideElementManager._getConfiguration(refId); 081 if (baseConfiguration == null) 082 { 083 throw new ConfigurationException("Unknow ref-id '" + refId + "' for the extension point '" + extensionPoint + "'", configuration); 084 } 085 086 DefaultConfiguration mergedConfiguration = new DefaultConfiguration(baseConfiguration, true); 087 _getMerdedChildsConfiguration(configuration, mergedConfiguration); 088 mergedConfiguration.setAttribute("ref-id", null); 089 090 _configurations.put(id, mergedConfiguration); 091 _configurationPlugins.put(id, pluginName); 092 super.addExtension(id, pluginName, featureName, mergedConfiguration); 093 } 094 catch (ServiceException e) 095 { 096 throw new ConfigurationException("Invalid id referenced by the attribute 'ref-id'", configuration); 097 } 098 } 099 100 private Configuration _getConfiguration(String id) throws ConfigurationException 101 { 102 Configuration configuration = _configurations.get(id); 103 if (configuration != null) 104 { 105 return _getContexutalizedConfiguration(configuration, _configurationPlugins.get(id)); 106 } 107 108 for (AbstractClientSideExtensionPoint manager : _registeredManagers) 109 { 110 configuration = manager._getConfiguration(id); 111 if (configuration != null) 112 { 113 return configuration; 114 } 115 } 116 117 return null; 118 } 119 120 private Configuration _getContexutalizedConfiguration(Configuration configuration, String pluginName) throws ConfigurationException 121 { 122 DefaultConfiguration contextualizedConfiguration = new DefaultConfiguration(configuration); 123 124 _contexutalizeConfiguration(contextualizedConfiguration, pluginName); 125 126 return contextualizedConfiguration; 127 } 128 129 private void _contexutalizeConfiguration(MutableConfiguration configuration, String pluginName) throws ConfigurationException 130 { 131 for (MutableConfiguration child : configuration.getMutableChildren()) 132 { 133 if ((child.getName().equals("file") || child.getAttributeAsBoolean("file", false) || "file".equals(child.getAttribute("type", null))) && child.getAttribute("plugin", null) == null) 134 { 135 child.setAttribute("plugin", pluginName); 136 } 137 else if (("true".equals(child.getAttribute("i18n", "false")) || "i18n".equals(child.getAttribute("type", null))) && child.getValue().indexOf(":") < 0 && child.getValue().length() > 0) 138 { 139 child.setValue("plugin." + pluginName + ":" + child.getValue()); 140 } 141 _contexutalizeConfiguration(child, pluginName); 142 } 143 } 144 145 private void _getMerdedChildsConfiguration(Configuration configuration, MutableConfiguration base) throws ConfigurationException 146 { 147 base.addAllAttributes(configuration); 148 for (Configuration child : configuration.getChildren()) 149 { 150 String tagName = child.getName(); 151 MutableConfiguration baseChild = base.getMutableChild(tagName); 152 if ("scripts".equals(tagName) || "css".equals(tagName)) 153 { 154 for (Configuration fileChild : child.getChildren()) 155 { 156 baseChild.addChild(fileChild); 157 } 158 } 159 else 160 { 161 _mergeChildsConfiguration(child, baseChild); 162 } 163 } 164 } 165 166 private void _mergeChildsConfiguration(Configuration configuration, MutableConfiguration base) throws ConfigurationException 167 { 168 String value = configuration.getValue(null); 169 if (value != null) 170 { 171 base.setValue(value); 172 } 173 174 base.addAllAttributes(configuration); 175 176 Set<String> childrenToProcess = Arrays.stream(configuration.getChildren()) 177 .map(Configuration::getName) 178 .collect(Collectors.toSet()); 179 for (String childName : childrenToProcess) 180 { 181 MutableConfiguration[] baseChildren = base.getMutableChildren(childName); 182 Configuration[] newChildren = configuration.getChildren(childName); 183 184 if (baseChildren.length == newChildren.length) 185 { 186 for (int i = 0; i < baseChildren.length; i++) 187 { 188 _mergeChildsConfiguration(newChildren[i], baseChildren[i]); 189 } 190 } 191 else 192 { 193 for (Configuration baseChild : baseChildren) 194 { 195 base.removeChild(baseChild); 196 } 197 198 for (Configuration newChild : newChildren) 199 { 200 base.addChild(newChild); 201 } 202 } 203 } 204 } 205 206 @Override 207 public void initializeExtensions() throws Exception 208 { 209 Map<String, ReferencingExtension> refExtIds = _referencingExtensions.stream().collect(Collectors.toMap(ReferencingExtension::getId, Function.identity())); 210 List<String> processing = new ArrayList<>(); 211 for (ReferencingExtension refExt : _referencingExtensions) 212 { 213 _lazyInitializeReferencingExtension(refExt, refExtIds, processing); 214 } 215 216 super.initializeExtensions(); 217 _initialized = true; 218 } 219 220 private void _lazyInitializeReferencingExtension(ReferencingExtension ext, Map<String, ReferencingExtension> refExtIds, List<String> processing) throws ConfigurationException 221 { 222 if (!processing.contains(ext.getId())) 223 { 224 processing.add(ext.getId()); 225 if (refExtIds.containsKey(ext.getRefId())) 226 { 227 // if we are referencing another referencing extension, make sure it is initialized before 228 _lazyInitializeReferencingExtension(refExtIds.get(ext.getRefId()), refExtIds, processing); 229 } 230 231 _addReferencingExtension(ext.getId(), ext.getPluginName(), ext.getFeatureName(), ext.getConfiguration()); 232 } 233 } 234 235 /** 236 * Register a new ribbon manager whose extensions will also be managed by this RibbonControlsManager 237 * @param manager The manager to register 238 */ 239 public void registerRibbonManager(AbstractClientSideExtensionPoint<T> manager) 240 { 241 _registeredManagers.add(manager); 242 } 243 244 /** 245 * Remove a previously registered ribbon manager 246 * @param manager The manager to remove 247 */ 248 public void unregisterRibbonManager(AbstractClientSideExtensionPoint<T> manager) 249 { 250 _registeredManagers.remove(manager); 251 } 252 253 @Override 254 public T getExtension(String id) 255 { 256 T extension = super.getExtension(id); 257 if (extension == null) 258 { 259 for (AbstractClientSideExtensionPoint<T> manager : _registeredManagers) 260 { 261 extension = manager.getExtension(id); 262 if (extension != null) 263 { 264 return extension; 265 } 266 } 267 } 268 269 return extension; 270 } 271 272 @Override 273 public Set<String> getExtensionsIds() 274 { 275 Set<String> extensionsIds = super.getExtensionsIds(); 276 for (AbstractClientSideExtensionPoint<T> manager : _registeredManagers) 277 { 278 extensionsIds.addAll(manager.getExtensionsIds()); 279 } 280 return extensionsIds; 281 } 282 283 /** 284 * Find a dependency of this manager from the Client side elements it knows. 285 * @param pattern The matching pattern to find the dependency. 286 * @return The dependency, or null if no Client side element matched. 287 */ 288 public List<ClientSideElement> findDependency(String pattern) 289 { 290 ClientSideElement extension = getExtension(pattern); 291 292 if (extension == null) 293 { 294 throw new IllegalArgumentException("Unable to find dependency with id : " + pattern + "."); 295 } 296 297 List<ClientSideElement> result = new ArrayList<>(); 298 result.add(extension); 299 return result; 300 } 301 302 private class ReferencingExtension 303 { 304 private String _id; 305 private String _pluginName; 306 private String _featureName; 307 private Configuration _configuration; 308 private String _refId; 309 310 public ReferencingExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException 311 { 312 _id = id; 313 _pluginName = pluginName; 314 _featureName = featureName; 315 _configuration = configuration; 316 317 _refId = configuration.getAttribute("ref-id"); 318 } 319 320 /** 321 * Return the id 322 * @return the id 323 */ 324 public String getId() 325 { 326 return _id; 327 } 328 329 /** 330 * Return the plugin name 331 * @return the plugin name 332 */ 333 public String getPluginName() 334 { 335 return _pluginName; 336 } 337 338 /** 339 * Return the feature name 340 * @return the feature name 341 */ 342 public String getFeatureName() 343 { 344 return _featureName; 345 } 346 347 /** 348 * Return the configuration 349 * @return the configuration 350 */ 351 public Configuration getConfiguration() 352 { 353 return _configuration; 354 } 355 356 /** 357 * Get the referenced extension id 358 * @return The ref-id 359 */ 360 public String getRefId() 361 { 362 return _refId; 363 } 364 } 365}