001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.blueprint.handler;
018    
019    import java.net.URL;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.HashSet;
023    import java.util.List;
024    import java.util.Set;
025    import java.util.concurrent.Callable;
026    
027    import javax.xml.bind.Binder;
028    import javax.xml.bind.JAXBContext;
029    import javax.xml.bind.JAXBException;
030    
031    import org.w3c.dom.Document;
032    import org.w3c.dom.Element;
033    import org.w3c.dom.Node;
034    import org.w3c.dom.NodeList;
035    
036    import org.apache.aries.blueprint.NamespaceHandler;
037    import org.apache.aries.blueprint.ParserContext;
038    import org.apache.aries.blueprint.PassThroughMetadata;
039    import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
040    import org.apache.aries.blueprint.mutable.MutablePassThroughMetadata;
041    import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
042    import org.apache.camel.blueprint.BlueprintCamelContext;
043    import org.apache.camel.blueprint.CamelContextFactoryBean;
044    import org.apache.camel.core.xml.AbstractCamelContextFactoryBean;
045    import org.apache.camel.impl.DefaultCamelContextNameStrategy;
046    import org.apache.camel.model.AggregateDefinition;
047    import org.apache.camel.model.CatchDefinition;
048    import org.apache.camel.model.DataFormatDefinition;
049    import org.apache.camel.model.ExpressionNode;
050    import org.apache.camel.model.ExpressionSubElementDefinition;
051    import org.apache.camel.model.FromDefinition;
052    import org.apache.camel.model.MarshalDefinition;
053    import org.apache.camel.model.OnExceptionDefinition;
054    import org.apache.camel.model.ProcessorDefinition;
055    import org.apache.camel.model.ResequenceDefinition;
056    import org.apache.camel.model.RouteDefinition;
057    import org.apache.camel.model.SendDefinition;
058    import org.apache.camel.model.SortDefinition;
059    import org.apache.camel.model.UnmarshalDefinition;
060    import org.apache.camel.model.WireTapDefinition;
061    import org.apache.camel.model.language.ExpressionDefinition;
062    import org.apache.camel.spi.ComponentResolver;
063    import org.apache.camel.spi.DataFormatResolver;
064    import org.apache.camel.spi.LanguageResolver;
065    import org.apache.camel.util.ObjectHelper;
066    import org.apache.commons.logging.Log;
067    import org.apache.commons.logging.LogFactory;
068    import org.osgi.framework.Bundle;
069    import org.osgi.framework.BundleContext;
070    import org.osgi.service.blueprint.container.BlueprintContainer;
071    import org.osgi.service.blueprint.container.ComponentDefinitionException;
072    import org.osgi.service.blueprint.reflect.ComponentMetadata;
073    import org.osgi.service.blueprint.reflect.Metadata;
074    
075    public class CamelNamespaceHandler implements NamespaceHandler {
076    
077        private static final String CAMEL_CONTEXT = "camelContext";
078    
079        private static final String SPRING_NS = "http://camel.apache.org/schema/spring";
080        private static final String BLUEPRINT_NS = "http://camel.apache.org/schema/blueprint";
081    
082        private static final transient Log LOG = LogFactory.getLog(CamelNamespaceHandler.class);
083    
084        private JAXBContext jaxbContext;
085        
086        public static void renameNamespaceRecursive(Node node) {
087            if (node.getNodeType() == Node.ELEMENT_NODE) {
088                Document doc = node.getOwnerDocument();
089                if (((Element) node).getNamespaceURI().equals(BLUEPRINT_NS)) {
090                    doc.renameNode(node, SPRING_NS, node.getNodeName());
091                }
092            }
093            NodeList list = node.getChildNodes();
094            for (int i = 0; i < list.getLength(); ++i) {
095                renameNamespaceRecursive(list.item(i));
096            }
097        }
098    
099        public URL getSchemaLocation(String namespace) {
100            return getClass().getClassLoader().getResource("camel-blueprint.xsd");
101        }
102    
103        @SuppressWarnings("unchecked")
104        public Set<Class> getManagedClasses() {
105            return new HashSet<Class>(Arrays.asList(
106                    BlueprintCamelContext.class
107            ));
108        }
109    
110        public Metadata parse(Element element, ParserContext context) {
111            renameNamespaceRecursive(element);
112            if (element.getNodeName().equals(CAMEL_CONTEXT)) {
113                // Find the id, generate one if needed
114                String contextId = element.getAttribute("id");
115                boolean implicitId = false;
116    
117                // lets avoid folks having to explicitly give an ID to a camel context
118                if (ObjectHelper.isEmpty(contextId)) {
119                    // if no explicit id was set then use a default auto generated name
120                    contextId = DefaultCamelContextNameStrategy.getNextName();
121                    element.setAttribute("id", contextId);
122                    implicitId = true;
123                }
124    
125                // now lets parse the routes with JAXB
126                Binder<Node> binder;
127                try {
128                    binder = getJaxbContext().createBinder();
129                } catch (JAXBException e) {
130                    throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
131                }
132                Object value = parseUsingJaxb(element, context, binder);
133                if (!(value instanceof CamelContextFactoryBean)) {
134                    throw new ComponentDefinitionException("Expected an instance of " + CamelContextFactoryBean.class);
135                }
136    
137                CamelContextFactoryBean ccfb = (CamelContextFactoryBean) value;
138                try {
139                    PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintContainer");
140                    ccfb.setBlueprintContainer((BlueprintContainer) ptm.getObject());
141                    ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintBundleContext");
142                    ccfb.setBundleContext((BundleContext) ptm.getObject());
143                    ccfb.setImplicitId(implicitId);
144                    ccfb.afterPropertiesSet();
145                } catch (Exception e) {
146                    throw new ComponentDefinitionException("Unable to initialize camel context factory", e);
147                }
148    
149                Set<String> components = new HashSet<String>();
150                Set<String> languages = new HashSet<String>();
151                Set<String> dataformats = new HashSet<String>();
152                Set<String> dependsOn = new HashSet<String>();
153                for (RouteDefinition rd : ccfb.getContext().getRouteDefinitions()) {
154                    findInputComponents(rd.getInputs(), components, languages, dataformats);
155                    findOutputComponents(rd.getOutputs(), components, languages, dataformats);
156                }
157                try {
158                    for (String component : components) {
159                        ComponentMetadata cm = context.getComponentDefinitionRegistry().getComponentDefinition(".camelBlueprint.componentResolver."  + component);
160                        if (cm == null) {
161                            MutableReferenceMetadata svc = context.createMetadata(MutableReferenceMetadata.class);
162                            svc.setId(".camelBlueprint.componentResolver."  + component);
163                            svc.setFilter("(component=" + component + ")");
164                            try {
165                                // Try to set the runtime interface (only with aries blueprint > 0.1
166                                svc.getClass().getMethod("setRuntimeInterface", Class.class).invoke(svc, ComponentResolver.class);
167                            } catch (Throwable t) {
168                                // Check if the bundle can see the class
169                                try {
170                                    PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintBundle");
171                                    Bundle b = (Bundle) ptm.getObject();
172                                    if (b.loadClass(ComponentResolver.class.getName()) != ComponentResolver.class) {
173                                        throw new UnsupportedOperationException();
174                                    }
175                                    svc.setInterface(ComponentResolver.class.getName());
176                                } catch (Throwable t2) {
177                                    throw new UnsupportedOperationException();
178                                }
179                            }
180                            context.getComponentDefinitionRegistry().registerComponentDefinition(svc);
181                            dependsOn.add(svc.getId());
182                        }
183                    }
184                    for (String language : languages) {
185                        ComponentMetadata cm = context.getComponentDefinitionRegistry().getComponentDefinition(".camelBlueprint.languageResolver."  + language);
186                        if (cm == null) {
187                            MutableReferenceMetadata svc = context.createMetadata(MutableReferenceMetadata.class);
188                            svc.setId(".camelBlueprint.languageResolver."  + language);
189                            svc.setFilter("(language=" + language + ")");
190                            try {
191                                // Try to set the runtime interface (only with aries blueprint > 0.1
192                                svc.getClass().getMethod("setRuntimeInterface", Class.class).invoke(svc, LanguageResolver.class);
193                            } catch (Throwable t) {
194                                // Check if the bundle can see the class
195                                try {
196                                    PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintBundle");
197                                    Bundle b = (Bundle) ptm.getObject();
198                                    if (b.loadClass(LanguageResolver.class.getName()) != LanguageResolver.class) {
199                                        throw new UnsupportedOperationException();
200                                    }
201                                    svc.setInterface(LanguageResolver.class.getName());
202                                } catch (Throwable t2) {
203                                    throw new UnsupportedOperationException();
204                                }
205                            }
206                            context.getComponentDefinitionRegistry().registerComponentDefinition(svc);
207                            dependsOn.add(svc.getId());
208                        }
209                    }
210                    for (String dataformat : dataformats) {
211                        ComponentMetadata cm = context.getComponentDefinitionRegistry().getComponentDefinition(".camelBlueprint.dataformatResolver."  + dataformat);
212                        if (cm == null) {
213                            MutableReferenceMetadata svc = context.createMetadata(MutableReferenceMetadata.class);
214                            svc.setId(".camelBlueprint.dataformatResolver."  + dataformat);
215                            svc.setFilter("(dataformat=" + dataformat + ")");
216                            try {
217                                // Try to set the runtime interface (only with aries blueprint > 0.1
218                                svc.getClass().getMethod("setRuntimeInterface", Class.class).invoke(svc, DataFormatResolver.class);
219                            } catch (Throwable t) {
220                                // Check if the bundle can see the class
221                                try {
222                                    PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintBundle");
223                                    Bundle b = (Bundle) ptm.getObject();
224                                    if (b.loadClass(DataFormatResolver.class.getName()) != DataFormatResolver.class) {
225                                        throw new UnsupportedOperationException();
226                                    }
227                                    svc.setInterface(DataFormatResolver.class.getName());
228                                } catch (Throwable t2) {
229                                    throw new UnsupportedOperationException();
230                                }
231                            }
232                            context.getComponentDefinitionRegistry().registerComponentDefinition(svc);
233                            dependsOn.add(svc.getId());
234                        }
235                    }
236                } catch (UnsupportedOperationException e) {
237                    LOG.warn("Unable to add dependencies on to camel components OSGi services.  "
238                             + "The Apache Aries blueprint implementation used it too old and the blueprint bundle can not see the org.apache.camel.spi package.");
239                    components.clear();
240                    languages.clear();
241                    dataformats.clear();
242                }
243    
244                MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
245                factory.setId(".camelBlueprint.passThrough."  + contextId);
246                factory.setObject(new PassThroughCallable<Object>(value));
247                factory.setDependsOn(new ArrayList<String>(components));
248    
249                MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
250                factory2.setId(".camelBlueprint.factory." + contextId);
251                factory2.setFactoryComponent(factory);
252                factory2.setFactoryMethod("call");
253                factory2.setDestroyMethod("destroy");
254    
255                MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
256                ctx.setId(contextId);
257                ctx.setFactoryComponent(factory2);
258                ctx.setFactoryMethod("getContext");
259                ctx.setInitMethod("init");
260                ctx.setDestroyMethod("destroy");
261    
262                return ctx;
263            }
264            return null;
265        }
266    
267        private void findInputComponents(List<FromDefinition> defs, Set<String> components, Set<String> languages, Set<String> dataformats) {
268            if (defs != null) {
269                for (FromDefinition def : defs) {
270                    findUriComponent(def.getUri(), components);
271                }
272            }
273        }
274    
275        @SuppressWarnings("unchecked")
276        private void findOutputComponents(List<ProcessorDefinition> defs, Set<String> components, Set<String> languages, Set<String> dataformats) {
277            if (defs != null) {
278                for (ProcessorDefinition def : defs) {
279                    if (def instanceof SendDefinition) {
280                        findUriComponent(((SendDefinition) def).getUri(), components);
281                    }
282                    if (def instanceof MarshalDefinition) {
283                        findDataFormat(((MarshalDefinition) def).getDataFormatType(), dataformats);
284                    }
285                    if (def instanceof UnmarshalDefinition) {
286                        findDataFormat(((UnmarshalDefinition) def).getDataFormatType(), dataformats);
287                    }
288                    if (def instanceof ExpressionNode) {
289                        findLanguage(((ExpressionNode) def).getExpression(), languages);
290                    }
291                    if (def instanceof ResequenceDefinition) {
292                        findLanguage(((ResequenceDefinition) def).getExpressions(), languages);
293                    }
294                    if (def instanceof AggregateDefinition) {
295                        findLanguage(((AggregateDefinition) def).getExpression(), languages);
296                        findLanguage(((AggregateDefinition) def).getCorrelationExpression(), languages);
297                        findLanguage(((AggregateDefinition) def).getCompletionPredicate(), languages);
298                        findLanguage(((AggregateDefinition) def).getCompletionTimeoutExpression(), languages);
299                        findLanguage(((AggregateDefinition) def).getCompletionSizeExpression(), languages);
300                    }
301                    if (def instanceof CatchDefinition) {
302                        findLanguage(((CatchDefinition) def).getHandled(), languages);
303                    }
304                    if (def instanceof OnExceptionDefinition) {
305                        findLanguage(((OnExceptionDefinition) def).getRetryWhile(), languages);
306                        findLanguage(((OnExceptionDefinition) def).getHandled(), languages);
307                        findLanguage(((OnExceptionDefinition) def).getContinued(), languages);
308                    }
309                    if (def instanceof SortDefinition) {
310                        findLanguage(((SortDefinition) def).getExpression(), languages);
311                    }
312                    if (def instanceof WireTapDefinition) {
313                        findLanguage(((WireTapDefinition) def).getNewExchangeExpression(), languages);
314                    }
315                    findOutputComponents(def.getOutputs(), components, languages, dataformats);
316                }
317            }
318        }
319    
320        private void findLanguage(ExpressionDefinition expression, Set<String> languages) {
321            if (expression != null) {
322                String lang = expression.getLanguage();
323                if (lang != null && lang.length() > 0) {
324                    languages.add(lang);
325                }
326            }
327        }
328    
329        private void findLanguage(List<ExpressionDefinition> expressions, Set<String> languages) {
330            if (expressions != null) {
331                for (ExpressionDefinition e : expressions) {
332                    findLanguage(e, languages);
333                }
334            }
335        }
336    
337        private void findLanguage(ExpressionSubElementDefinition expression, Set<String> languages) {
338            if (expression != null) {
339                findLanguage(expression.getExpressionType(), languages);
340            }
341        }
342    
343        private void findDataFormat(DataFormatDefinition dfd, Set<String> dataformats) {
344            if (dfd != null && dfd.getDataFormatName() != null) {
345                dataformats.add(dfd.getDataFormatName());
346            }
347        }
348    
349        private void findUriComponent(String uri, Set<String> components) {
350            if (uri != null) {
351                String splitURI[] = ObjectHelper.splitOnCharacter(uri, ":", 2);
352                if (splitURI[1] != null) {
353                    String scheme = splitURI[0];
354                    components.add(scheme);
355                }
356            }
357        }
358    
359        public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
360            return null;
361        }
362    
363        protected Object parseUsingJaxb(Element element, ParserContext parserContext, Binder<Node> binder) {
364            try {
365                return binder.unmarshal(element);
366            } catch (JAXBException e) {
367                throw new ComponentDefinitionException("Failed to parse JAXB element: " + e, e);
368            }
369        }
370    
371        public JAXBContext getJaxbContext() throws JAXBException {
372            if (jaxbContext == null) {
373                jaxbContext = createJaxbContext();
374            }
375            return jaxbContext;
376        }
377    
378        protected JAXBContext createJaxbContext() throws JAXBException {
379            StringBuilder packages = new StringBuilder();
380            for (Class cl : getJaxbPackages()) {
381                if (packages.length() > 0) {
382                    packages.append(":");
383                }
384                packages.append(cl.getName().substring(0, cl.getName().lastIndexOf('.')));
385            }
386            return JAXBContext.newInstance(packages.toString(), getClass().getClassLoader());
387        }
388    
389        protected Set<Class> getJaxbPackages() {
390            Set<Class> classes = new HashSet<Class>();
391            classes.add(CamelContextFactoryBean.class);
392            classes.add(AbstractCamelContextFactoryBean.class);
393            classes.add(org.apache.camel.ExchangePattern.class);
394            classes.add(org.apache.camel.model.RouteDefinition.class);
395            classes.add(org.apache.camel.model.config.StreamResequencerConfig.class);
396            classes.add(org.apache.camel.model.dataformat.DataFormatsDefinition.class);
397            classes.add(org.apache.camel.model.language.ExpressionDefinition.class);
398            classes.add(org.apache.camel.model.loadbalancer.RoundRobinLoadBalancerDefinition.class);
399            return classes;
400        }
401    
402        public static class PassThroughCallable<T> implements Callable<T> {
403    
404            private T value;
405    
406            public PassThroughCallable(T value) {
407                this.value = value;
408            }
409    
410            public T call() throws Exception {
411                return value;
412            }
413        }
414    
415    }