Add decorating "type" attr extension for service refs
[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.net.URL;
12 import java.util.Collections;
13 import java.util.Set;
14 import org.apache.aries.blueprint.ComponentDefinitionRegistry;
15 import org.apache.aries.blueprint.NamespaceHandler;
16 import org.apache.aries.blueprint.ParserContext;
17 import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
18 import org.apache.aries.blueprint.mutable.MutableRefMetadata;
19 import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
20 import org.apache.aries.blueprint.mutable.MutableServiceMetadata;
21 import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata;
22 import org.apache.aries.blueprint.mutable.MutableValueMetadata;
23 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
24 import org.osgi.service.blueprint.container.ComponentDefinitionException;
25 import org.osgi.service.blueprint.reflect.BeanMetadata;
26 import org.osgi.service.blueprint.reflect.ComponentMetadata;
27 import org.osgi.service.blueprint.reflect.Metadata;
28 import org.osgi.service.blueprint.reflect.RefMetadata;
29 import org.osgi.service.blueprint.reflect.ReferenceMetadata;
30 import org.osgi.service.blueprint.reflect.ServiceMetadata;
31 import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
32 import org.osgi.service.blueprint.reflect.ValueMetadata;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.w3c.dom.Attr;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.Node;
38
39 /**
40  * The NamespaceHandler for Opendaylight blueprint extensions.
41  *
42  * @author Thomas Pantelis
43  */
44 public class OpendaylightNamespaceHandler implements NamespaceHandler {
45     public static final String NAMESPACE_1_0_0 = "http://opendaylight.org/xmlns/blueprint/v1.0.0";
46
47     private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class);
48     private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName();
49     private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates";
50     private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types";
51     private static final String TYPE_ATTR = "type";
52
53     @SuppressWarnings("rawtypes")
54     @Override
55     public Set<Class> getManagedClasses() {
56         return Collections.emptySet();
57     }
58
59     @Override
60     public URL getSchemaLocation(String namespace) {
61         if(NAMESPACE_1_0_0.equals(namespace)) {
62             URL url = getClass().getResource("/opendaylight-blueprint-ext-1.0.0.xsd");
63             LOG.debug("getSchemaLocation for {} returning URL {}", namespace, url);
64             return url;
65         }
66
67         return null;
68     }
69
70     @Override
71     public Metadata parse(Element element, ParserContext context) {
72         LOG.debug("In parse for {}", element);
73         throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName());
74     }
75
76     @Override
77     public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
78         if(node instanceof Attr) {
79             if (nodeNameEquals(node, RESTART_DEPENDENTS_ON_UPDATES)) {
80                 return decorateRestartDependentsOnUpdates((Attr)node, component, context);
81             } else if (nodeNameEquals(node, USE_DEFAULT_FOR_REFERENCE_TYPES)) {
82                 return decorateUseDefaultForReferenceTypes((Attr)node, component, context);
83             } else if (nodeNameEquals(node, TYPE_ATTR)) {
84                 if(component instanceof ServiceReferenceMetadata) {
85                     return decorateServiceReferenceType((Attr)node, component, context);
86                 } else if(component instanceof ServiceMetadata) {
87                     return decorateServiceType((Attr)node, component, context);
88                 }
89
90                 throw new ComponentDefinitionException("Attribute " + node.getNodeName() +
91                         " can only be used on a <reference>, <reference-list> or <service> element");
92             }
93
94             throw new ComponentDefinitionException("Unsupported attribute: " + node.getNodeName());
95         } else {
96             throw new ComponentDefinitionException("Unsupported node type: " + node);
97         }
98     }
99
100     private ComponentMetadata decorateServiceType(Attr attr, ComponentMetadata component, ParserContext context) {
101         if (!(component instanceof MutableServiceMetadata)) {
102             throw new ComponentDefinitionException("Expected an instanceof MutableServiceMetadata");
103         }
104
105         MutableServiceMetadata service = (MutableServiceMetadata)component;
106
107         LOG.debug("decorateServiceType for {} - adding type property {}", service.getId(), attr.getValue());
108
109         service.addServiceProperty(createValue(context, TYPE_ATTR), createValue(context, attr.getValue()));
110         return component;
111     }
112
113     private ComponentMetadata decorateServiceReferenceType(Attr attr, ComponentMetadata component, ParserContext context) {
114         if (!(component instanceof MutableServiceReferenceMetadata)) {
115             throw new ComponentDefinitionException("Expected an instanceof MutableServiceReferenceMetadata");
116         }
117
118         // We don't actually need the ComponentProcessor for augmenting the OSGi filter here but we create it
119         // to workaround an issue in Aries where it doesn't use the extended filter unless there's a
120         // Processor or ComponentDefinitionRegistryProcessor registered. This may actually be working as
121         // designed in Aries b/c the extended filter was really added to allow the OSGi filter to be
122         // substituted by a variable via the "cm:property-placeholder" processor. If so, it's a bit funky
123         // but as long as there's at least one processor registered, it correctly uses the extended filter.
124         registerComponentProcessor(context);
125
126         MutableServiceReferenceMetadata serviceRef = (MutableServiceReferenceMetadata)component;
127         String oldFilter = serviceRef.getExtendedFilter() == null ? null :
128             serviceRef.getExtendedFilter().getStringValue();
129
130         String filter;
131         if(Strings.isNullOrEmpty(oldFilter)) {
132             filter = String.format("(type=%s)", attr.getValue());
133         } else {
134             filter = String.format("(&(%s)(type=%s))", oldFilter, attr.getValue());
135         }
136
137         LOG.debug("decorateServiceReferenceType for {} with type {}, old filter: {}, new filter: {}",
138                 serviceRef.getId(), attr.getValue(), oldFilter, filter);
139
140         serviceRef.setExtendedFilter(createValue(context, filter));
141         return component;
142     }
143
144     private static ComponentMetadata decorateRestartDependentsOnUpdates(Attr attr, ComponentMetadata component,
145             ParserContext context) {
146         return enableComponentProcessorProperty(attr, component, context, "restartDependentsOnUpdates");
147     }
148
149     private static ComponentMetadata decorateUseDefaultForReferenceTypes(Attr attr, ComponentMetadata component,
150             ParserContext context) {
151         return enableComponentProcessorProperty(attr, component, context, "useDefaultForReferenceTypes");
152     }
153
154     private static ComponentMetadata enableComponentProcessorProperty(Attr attr, ComponentMetadata component,
155             ParserContext context, String propertyName) {
156         if(component != null) {
157             throw new ComponentDefinitionException("Attribute " + attr.getNodeName() +
158                     " can only be used on the root <blueprint> element");
159         }
160
161         LOG.debug("{}: {}", propertyName, attr.getValue());
162
163         if(!Boolean.TRUE.equals(Boolean.valueOf(attr.getValue()))) {
164             return component;
165         }
166
167         MutableBeanMetadata metadata = registerComponentProcessor(context);
168         metadata.addProperty(propertyName, createValue(context, "true"));
169
170         return component;
171     }
172
173     private static MutableBeanMetadata registerComponentProcessor(ParserContext context) {
174         ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
175         MutableBeanMetadata metadata = (MutableBeanMetadata) registry.getComponentDefinition(COMPONENT_PROCESSOR_NAME);
176         if(metadata == null) {
177             metadata = context.createMetadata(MutableBeanMetadata.class);
178             metadata.setProcessor(true);
179             metadata.setId(COMPONENT_PROCESSOR_NAME);
180             metadata.setActivation(BeanMetadata.ACTIVATION_EAGER);
181             metadata.setScope(BeanMetadata.SCOPE_SINGLETON);
182             metadata.setRuntimeClass(ComponentProcessor.class);
183             metadata.setDestroyMethod("destroy");
184             metadata.addProperty("bundle", createRef(context, "blueprintBundle"));
185             metadata.addProperty("blueprintContainerRestartService", createServiceRef(context,
186                     BlueprintContainerRestartService.class, null));
187
188             LOG.debug("Registering ComponentProcessor bean: {}", metadata);
189
190             registry.registerComponentDefinition(metadata);
191         }
192
193         return metadata;
194     }
195
196     private static ValueMetadata createValue(ParserContext context, String value) {
197         MutableValueMetadata m = context.createMetadata(MutableValueMetadata.class);
198         m.setStringValue(value);
199         return m;
200     }
201
202     private static MutableReferenceMetadata createServiceRef(ParserContext context, Class<?> cls, String filter) {
203         MutableReferenceMetadata m = context.createMetadata(MutableReferenceMetadata.class);
204         m.setRuntimeInterface(cls);
205         m.setInterface(cls.getName());
206         m.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
207         m.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
208
209         if(filter != null) {
210             m.setFilter(filter);
211         }
212
213         return m;
214     }
215
216     private static RefMetadata createRef(ParserContext context, String value) {
217         MutableRefMetadata metadata = context.createMetadata(MutableRefMetadata.class);
218         metadata.setComponentId(value);
219         return metadata;
220     }
221
222     private static boolean nodeNameEquals(Node node, String name) {
223         return name.equals(node.getNodeName()) || name.equals(node.getLocalName());
224     }
225 }