Warn on deprecated blueprint elements
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / ext / OpendaylightNamespaceHandler.java
1 /*
2  * Copyright (c) 2016 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.blueprint.ext;
9
10 import com.google.common.base.Strings;
11 import java.io.IOException;
12 import java.io.StringReader;
13 import java.net.URL;
14 import java.util.Collections;
15 import java.util.Set;
16 import org.apache.aries.blueprint.ComponentDefinitionRegistry;
17 import org.apache.aries.blueprint.NamespaceHandler;
18 import org.apache.aries.blueprint.ParserContext;
19 import org.apache.aries.blueprint.ext.ComponentFactoryMetadata;
20 import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
21 import org.apache.aries.blueprint.mutable.MutableRefMetadata;
22 import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
23 import org.apache.aries.blueprint.mutable.MutableServiceMetadata;
24 import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata;
25 import org.apache.aries.blueprint.mutable.MutableValueMetadata;
26 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
27 import org.opendaylight.mdsal.binding.api.NotificationService;
28 import org.opendaylight.mdsal.binding.api.RpcProviderService;
29 import org.opendaylight.mdsal.dom.api.DOMRpcProviderService;
30 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
31 import org.opendaylight.yangtools.util.xml.UntrustedXML;
32 import org.osgi.service.blueprint.container.ComponentDefinitionException;
33 import org.osgi.service.blueprint.reflect.BeanMetadata;
34 import org.osgi.service.blueprint.reflect.ComponentMetadata;
35 import org.osgi.service.blueprint.reflect.Metadata;
36 import org.osgi.service.blueprint.reflect.RefMetadata;
37 import org.osgi.service.blueprint.reflect.ReferenceMetadata;
38 import org.osgi.service.blueprint.reflect.ServiceMetadata;
39 import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
40 import org.osgi.service.blueprint.reflect.ValueMetadata;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.w3c.dom.Attr;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.Node;
46 import org.w3c.dom.NodeList;
47 import org.xml.sax.InputSource;
48 import org.xml.sax.SAXException;
49
50 /**
51  * The NamespaceHandler for Opendaylight blueprint extensions.
52  *
53  * @author Thomas Pantelis
54  */
55 public final class OpendaylightNamespaceHandler implements NamespaceHandler {
56     public static final String NAMESPACE_1_0_0 = "http://opendaylight.org/xmlns/blueprint/v1.0.0";
57     static final String ROUTED_RPC_REG_CONVERTER_NAME = "org.opendaylight.blueprint.RoutedRpcRegConverter";
58     static final String DOM_RPC_PROVIDER_SERVICE_NAME = "org.opendaylight.blueprint.DOMRpcProviderService";
59     static final String RPC_REGISTRY_NAME = "org.opendaylight.blueprint.RpcRegistry";
60     static final String BINDING_RPC_PROVIDER_SERVICE_NAME = "org.opendaylight.blueprint.RpcProviderService";
61     static final String SCHEMA_SERVICE_NAME = "org.opendaylight.blueprint.SchemaService";
62     static final String NOTIFICATION_SERVICE_NAME = "org.opendaylight.blueprint.NotificationService";
63     static final String TYPE_ATTR = "type";
64     static final String UPDATE_STRATEGY_ATTR = "update-strategy";
65
66     private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class);
67     private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName();
68     private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates";
69     private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types";
70     private static final String CLUSTERED_APP_CONFIG = "clustered-app-config";
71     private static final String INTERFACE = "interface";
72     private static final String REF_ATTR = "ref";
73     private static final String ID_ATTR = "id";
74     private static final String RPC_SERVICE = "rpc-service";
75     private static final String ACTION_SERVICE = "action-service";
76     private static final String SPECIFIC_SERVICE_REF_LIST = "specific-reference-list";
77     private static final String STATIC_REFERENCE = "static-reference";
78
79     @SuppressWarnings("rawtypes")
80     @Override
81     public Set<Class> getManagedClasses() {
82         return Collections.emptySet();
83     }
84
85     @Override
86     public URL getSchemaLocation(final String namespace) {
87         if (NAMESPACE_1_0_0.equals(namespace)) {
88             URL url = getClass().getResource("/opendaylight-blueprint-ext-1.0.0.xsd");
89             LOG.debug("getSchemaLocation for {} returning URL {}", namespace, url);
90             return url;
91         }
92
93         return null;
94     }
95
96     @Override
97     public Metadata parse(final Element element, final ParserContext context) {
98         LOG.debug("In parse for {}", element);
99
100         if (nodeNameEquals(element, RpcImplementationBean.RPC_IMPLEMENTATION)) {
101             warnDeprecatedElement(RpcImplementationBean.RPC_IMPLEMENTATION);
102             return parseRpcImplementation(element, context);
103         } else if (nodeNameEquals(element, RPC_SERVICE)) {
104             warnDeprecatedElement(RPC_SERVICE);
105             return parseRpcService(element, context);
106         } else if (nodeNameEquals(element, NotificationListenerBean.NOTIFICATION_LISTENER)) {
107             warnDeprecatedElement(NotificationListenerBean.NOTIFICATION_LISTENER);
108             return parseNotificationListener(element, context);
109         } else if (nodeNameEquals(element, CLUSTERED_APP_CONFIG)) {
110             return parseClusteredAppConfig(element, context);
111         } else if (nodeNameEquals(element, SPECIFIC_SERVICE_REF_LIST)) {
112             warnDeprecatedElement(SPECIFIC_SERVICE_REF_LIST);
113             return parseSpecificReferenceList(element, context);
114         } else if (nodeNameEquals(element, STATIC_REFERENCE)) {
115             warnDeprecatedElement(SPECIFIC_SERVICE_REF_LIST);
116             return parseStaticReference(element, context);
117         } else if (nodeNameEquals(element, ACTION_SERVICE)) {
118             warnDeprecatedElement(ACTION_SERVICE);
119             return parseActionService(element, context);
120         } else if (nodeNameEquals(element, ActionProviderBean.ACTION_PROVIDER)) {
121             warnDeprecatedElement(ActionProviderBean.ACTION_PROVIDER);
122             return parseActionProvider(element, context);
123         }
124
125         throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName());
126     }
127
128     private static void warnDeprecatedElement(final String element) {
129         LOG.warn("Encountered element {}, support for this element will be removed in the next major release", element);
130     }
131
132     @Override
133     public ComponentMetadata decorate(final Node node, final ComponentMetadata component, final ParserContext context) {
134         if (node instanceof Attr) {
135             if (nodeNameEquals(node, RESTART_DEPENDENTS_ON_UPDATES)) {
136                 return decorateRestartDependentsOnUpdates((Attr) node, component, context);
137             } else if (nodeNameEquals(node, USE_DEFAULT_FOR_REFERENCE_TYPES)) {
138                 return decorateUseDefaultForReferenceTypes((Attr) node, component, context);
139             } else if (nodeNameEquals(node, TYPE_ATTR)) {
140                 if (component instanceof ServiceReferenceMetadata) {
141                     return decorateServiceReferenceType((Attr) node, component, context);
142                 } else if (component instanceof ServiceMetadata) {
143                     return decorateServiceType((Attr)node, component, context);
144                 }
145
146                 throw new ComponentDefinitionException("Attribute " + node.getNodeName()
147                         + " can only be used on a <reference>, <reference-list> or <service> element");
148             }
149
150             throw new ComponentDefinitionException("Unsupported attribute: " + node.getNodeName());
151         } else {
152             throw new ComponentDefinitionException("Unsupported node type: " + node);
153         }
154     }
155
156     private static ComponentMetadata decorateServiceType(final Attr attr, final ComponentMetadata component,
157             final ParserContext context) {
158         if (!(component instanceof MutableServiceMetadata service)) {
159             throw new ComponentDefinitionException("Expected an instanceof MutableServiceMetadata");
160         }
161
162         LOG.debug("decorateServiceType for {} - adding type property {}", service.getId(), attr.getValue());
163
164         service.addServiceProperty(createValue(context, TYPE_ATTR), createValue(context, attr.getValue()));
165         return component;
166     }
167
168     private static ComponentMetadata decorateServiceReferenceType(final Attr attr, final ComponentMetadata component,
169             final ParserContext context) {
170         if (!(component instanceof MutableServiceReferenceMetadata)) {
171             throw new ComponentDefinitionException("Expected an instanceof MutableServiceReferenceMetadata");
172         }
173
174         // We don't actually need the ComponentProcessor for augmenting the OSGi filter here but we create it
175         // to workaround an issue in Aries where it doesn't use the extended filter unless there's a
176         // Processor or ComponentDefinitionRegistryProcessor registered. This may actually be working as
177         // designed in Aries b/c the extended filter was really added to allow the OSGi filter to be
178         // substituted by a variable via the "cm:property-placeholder" processor. If so, it's a bit funky
179         // but as long as there's at least one processor registered, it correctly uses the extended filter.
180         registerComponentProcessor(context);
181
182         MutableServiceReferenceMetadata serviceRef = (MutableServiceReferenceMetadata)component;
183         String oldFilter = serviceRef.getExtendedFilter() == null ? null :
184             serviceRef.getExtendedFilter().getStringValue();
185
186         String filter;
187         if (Strings.isNullOrEmpty(oldFilter)) {
188             filter = String.format("(type=%s)", attr.getValue());
189         } else {
190             filter = String.format("(&(%s)(type=%s))", oldFilter, attr.getValue());
191         }
192
193         LOG.debug("decorateServiceReferenceType for {} with type {}, old filter: {}, new filter: {}",
194                 serviceRef.getId(), attr.getValue(), oldFilter, filter);
195
196         serviceRef.setExtendedFilter(createValue(context, filter));
197         return component;
198     }
199
200     private static ComponentMetadata decorateRestartDependentsOnUpdates(final Attr attr,
201             final ComponentMetadata component, final ParserContext context) {
202         return enableComponentProcessorProperty(attr, component, context, "restartDependentsOnUpdates");
203     }
204
205     private static ComponentMetadata decorateUseDefaultForReferenceTypes(final Attr attr,
206             final ComponentMetadata component, final ParserContext context) {
207         return enableComponentProcessorProperty(attr, component, context, "useDefaultForReferenceTypes");
208     }
209
210     private static ComponentMetadata enableComponentProcessorProperty(final Attr attr,
211             final ComponentMetadata component, final ParserContext context, final String propertyName) {
212         if (component != null) {
213             throw new ComponentDefinitionException("Attribute " + attr.getNodeName()
214                     + " can only be used on the root <blueprint> element");
215         }
216
217         LOG.debug("Property {} = {}", propertyName, attr.getValue());
218
219         if (!Boolean.parseBoolean(attr.getValue())) {
220             return component;
221         }
222
223         MutableBeanMetadata metadata = registerComponentProcessor(context);
224         metadata.addProperty(propertyName, createValue(context, "true"));
225
226         return component;
227     }
228
229     private static MutableBeanMetadata registerComponentProcessor(final ParserContext context) {
230         ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
231         MutableBeanMetadata metadata = (MutableBeanMetadata) registry.getComponentDefinition(COMPONENT_PROCESSOR_NAME);
232         if (metadata == null) {
233             metadata = createBeanMetadata(context, COMPONENT_PROCESSOR_NAME, ComponentProcessor.class, false, true);
234             metadata.setProcessor(true);
235             addBlueprintBundleRefProperty(context, metadata);
236             metadata.addProperty("blueprintContainerRestartService", createServiceRef(context,
237                     BlueprintContainerRestartService.class, null));
238
239             LOG.debug("Registering ComponentProcessor bean: {}", metadata);
240
241             registry.registerComponentDefinition(metadata);
242         }
243
244         return metadata;
245     }
246
247     private static Metadata parseActionProvider(final Element element, final ParserContext context) {
248         registerDomRpcProviderServiceRefBean(context);
249         registerBindingRpcProviderServiceRefBean(context);
250         registerSchemaServiceRefBean(context);
251
252         MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), ActionProviderBean.class,
253                 true, true);
254         addBlueprintBundleRefProperty(context, metadata);
255         metadata.addProperty("domRpcProvider", createRef(context, DOM_RPC_PROVIDER_SERVICE_NAME));
256         metadata.addProperty("bindingRpcProvider", createRef(context, BINDING_RPC_PROVIDER_SERVICE_NAME));
257         metadata.addProperty("schemaService", createRef(context, SCHEMA_SERVICE_NAME));
258         metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE)));
259
260         if (element.hasAttribute(REF_ATTR)) {
261             metadata.addProperty("implementation", createRef(context, element.getAttribute(REF_ATTR)));
262         }
263
264         LOG.debug("parseActionProvider returning {}", metadata);
265         return metadata;
266     }
267
268
269     private static Metadata parseRpcImplementation(final Element element, final ParserContext context) {
270         registerBindingRpcProviderServiceRefBean(context);
271
272         MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), RpcImplementationBean.class,
273                 true, true);
274         addBlueprintBundleRefProperty(context, metadata);
275         metadata.addProperty("rpcProvider", createRef(context, BINDING_RPC_PROVIDER_SERVICE_NAME));
276         metadata.addProperty("implementation", createRef(context, element.getAttribute(REF_ATTR)));
277
278         if (element.hasAttribute(INTERFACE)) {
279             metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE)));
280         }
281
282         LOG.debug("parseRpcImplementation returning {}", metadata);
283         return metadata;
284     }
285
286     private static Metadata parseActionService(final Element element, final ParserContext context) {
287         ComponentFactoryMetadata metadata = new ActionServiceMetadata(getId(context, element),
288                 element.getAttribute(INTERFACE));
289
290         LOG.debug("parseActionService returning {}", metadata);
291
292         return metadata;
293     }
294
295     private static Metadata parseRpcService(final Element element, final ParserContext context) {
296         ComponentFactoryMetadata metadata = new RpcServiceMetadata(getId(context, element),
297                 element.getAttribute(INTERFACE));
298
299         LOG.debug("parseRpcService returning {}", metadata);
300
301         return metadata;
302     }
303
304     private static void registerDomRpcProviderServiceRefBean(final ParserContext context) {
305         registerRefBean(context, DOM_RPC_PROVIDER_SERVICE_NAME, DOMRpcProviderService.class);
306     }
307
308     private static void registerBindingRpcProviderServiceRefBean(final ParserContext context) {
309         registerRefBean(context, BINDING_RPC_PROVIDER_SERVICE_NAME, RpcProviderService.class);
310     }
311
312     private static void registerSchemaServiceRefBean(final ParserContext context) {
313         registerRefBean(context, SCHEMA_SERVICE_NAME, DOMSchemaService.class);
314     }
315
316     private static void registerRefBean(final ParserContext context, final String name, final Class<?> clazz) {
317         ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
318         if (registry.getComponentDefinition(name) == null) {
319             MutableReferenceMetadata metadata = createServiceRef(context, clazz, null);
320             metadata.setId(name);
321             registry.registerComponentDefinition(metadata);
322         }
323     }
324
325     private static Metadata parseNotificationListener(final Element element, final ParserContext context) {
326         registerNotificationServiceRefBean(context);
327
328         MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), NotificationListenerBean.class,
329                 true, true);
330         addBlueprintBundleRefProperty(context, metadata);
331         metadata.addProperty("notificationService", createRef(context, NOTIFICATION_SERVICE_NAME));
332         metadata.addProperty("notificationListener", createRef(context, element.getAttribute(REF_ATTR)));
333
334         LOG.debug("parseNotificationListener returning {}", metadata);
335
336         return metadata;
337     }
338
339     private static void registerNotificationServiceRefBean(final ParserContext context) {
340         ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
341         if (registry.getComponentDefinition(NOTIFICATION_SERVICE_NAME) == null) {
342             MutableReferenceMetadata metadata = createServiceRef(context, NotificationService.class, null);
343             metadata.setId(NOTIFICATION_SERVICE_NAME);
344             registry.registerComponentDefinition(metadata);
345         }
346     }
347
348     private static Metadata parseClusteredAppConfig(final Element element, final ParserContext context) {
349         LOG.debug("parseClusteredAppConfig");
350
351         // Find the default-config child element representing the default app config XML, if present.
352         Element defaultConfigElement = null;
353         NodeList children = element.getChildNodes();
354         for (int i = 0; i < children.getLength(); i++) {
355             Node child = children.item(i);
356             if (nodeNameEquals(child, DataStoreAppConfigMetadata.DEFAULT_CONFIG)) {
357                 defaultConfigElement = (Element) child;
358                 break;
359             }
360         }
361
362         Element defaultAppConfigElement = null;
363         if (defaultConfigElement != null) {
364             // Find the CDATA element containing the default app config XML.
365             children = defaultConfigElement.getChildNodes();
366             for (int i = 0; i < children.getLength(); i++) {
367                 Node child = children.item(i);
368                 if (child.getNodeType() == Node.CDATA_SECTION_NODE) {
369                     defaultAppConfigElement = parseXML(DataStoreAppConfigMetadata.DEFAULT_CONFIG,
370                             child.getTextContent());
371                     break;
372                 }
373             }
374         }
375
376         return new DataStoreAppConfigMetadata(getId(context, element), element.getAttribute(
377                 DataStoreAppConfigMetadata.BINDING_CLASS), element.getAttribute(
378                         DataStoreAppConfigMetadata.LIST_KEY_VALUE), element.getAttribute(
379                         DataStoreAppConfigMetadata.DEFAULT_CONFIG_FILE_NAME), parseUpdateStrategy(
380                         element.getAttribute(UPDATE_STRATEGY_ATTR)), defaultAppConfigElement);
381     }
382
383     private static UpdateStrategy parseUpdateStrategy(final String updateStrategyValue) {
384         if (Strings.isNullOrEmpty(updateStrategyValue)
385                 || updateStrategyValue.equalsIgnoreCase(UpdateStrategy.RELOAD.name())) {
386             return UpdateStrategy.RELOAD;
387         } else if (updateStrategyValue.equalsIgnoreCase(UpdateStrategy.NONE.name())) {
388             return UpdateStrategy.NONE;
389         } else {
390             LOG.warn("update-strategy {} not supported, using reload", updateStrategyValue);
391             return UpdateStrategy.RELOAD;
392         }
393     }
394
395     private static Metadata parseSpecificReferenceList(final Element element, final ParserContext context) {
396         ComponentFactoryMetadata metadata = new SpecificReferenceListMetadata(getId(context, element),
397                 element.getAttribute(INTERFACE));
398
399         LOG.debug("parseSpecificReferenceList returning {}", metadata);
400
401         return metadata;
402     }
403
404     private static Metadata parseStaticReference(final Element element, final ParserContext context) {
405         ComponentFactoryMetadata metadata = new StaticReferenceMetadata(getId(context, element),
406                 element.getAttribute(INTERFACE));
407
408         LOG.debug("parseStaticReference returning {}", metadata);
409
410         return metadata;
411     }
412
413     private static Element parseXML(final String name, final String xml) {
414         try {
415             return UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(xml))).getDocumentElement();
416         } catch (SAXException | IOException e) {
417             throw new ComponentDefinitionException(String.format("Error %s parsing XML: %s", name, xml), e);
418         }
419     }
420
421     private static ValueMetadata createValue(final ParserContext context, final String value) {
422         MutableValueMetadata metadata = context.createMetadata(MutableValueMetadata.class);
423         metadata.setStringValue(value);
424         return metadata;
425     }
426
427     private static MutableReferenceMetadata createServiceRef(final ParserContext context, final Class<?> cls,
428             final String filter) {
429         MutableReferenceMetadata metadata = context.createMetadata(MutableReferenceMetadata.class);
430         metadata.setRuntimeInterface(cls);
431         metadata.setInterface(cls.getName());
432         metadata.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
433         metadata.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
434
435         if (filter != null) {
436             metadata.setFilter(filter);
437         }
438
439         return metadata;
440     }
441
442     private static RefMetadata createRef(final ParserContext context, final String id) {
443         MutableRefMetadata metadata = context.createMetadata(MutableRefMetadata.class);
444         metadata.setComponentId(id);
445         return metadata;
446     }
447
448     private static String getId(final ParserContext context, final Element element) {
449         if (element.hasAttribute(ID_ATTR)) {
450             return element.getAttribute(ID_ATTR);
451         } else {
452             return context.generateId();
453         }
454     }
455
456     private static boolean nodeNameEquals(final Node node, final String name) {
457         return name.equals(node.getNodeName()) || name.equals(node.getLocalName());
458     }
459
460     private static void addBlueprintBundleRefProperty(final ParserContext context, final MutableBeanMetadata metadata) {
461         metadata.addProperty("bundle", createRef(context, "blueprintBundle"));
462     }
463
464     private static MutableBeanMetadata createBeanMetadata(final ParserContext context, final String id,
465             final Class<?> runtimeClass, final boolean initMethod, final boolean destroyMethod) {
466         MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
467         metadata.setId(id);
468         metadata.setScope(BeanMetadata.SCOPE_SINGLETON);
469         metadata.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
470         metadata.setRuntimeClass(runtimeClass);
471
472         if (initMethod) {
473             metadata.setInitMethod("init");
474         }
475
476         if (destroyMethod) {
477             metadata.setDestroyMethod("destroy");
478         }
479
480         return metadata;
481     }
482 }