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