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.servicemix.jbi.deployment;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.InputStream;
024    import java.net.MalformedURLException;
025    import java.net.URL;
026    import java.util.ArrayList;
027    import java.util.List;
028    
029    import javax.xml.namespace.QName;
030    import javax.xml.parsers.DocumentBuilder;
031    import javax.xml.parsers.DocumentBuilderFactory;
032    import javax.xml.transform.stream.StreamSource;
033    import javax.xml.validation.Schema;
034    import javax.xml.validation.SchemaFactory;
035    import javax.xml.validation.Validator;
036    
037    import org.w3c.dom.Document;
038    import org.w3c.dom.DocumentFragment;
039    import org.w3c.dom.Element;
040    
041    import org.xml.sax.ErrorHandler;
042    import org.xml.sax.SAXException;
043    import org.xml.sax.SAXParseException;
044    
045    import org.apache.commons.logging.Log;
046    import org.apache.commons.logging.LogFactory;
047    import org.apache.servicemix.jbi.util.DOMUtil;
048    import org.apache.servicemix.jbi.util.FileUtil;
049    
050    /**
051     * @version $Revision: 359151 $
052     */
053    public final class DescriptorFactory {
054    
055        public static final String DESCRIPTOR_FILE = "META-INF/jbi.xml";
056    
057        /**
058         * JAXP attribute value indicating the XSD schema language.
059         */
060        private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
061    
062        private static final Log LOG = LogFactory.getLog(DescriptorFactory.class);
063    
064        private DescriptorFactory() {
065        }
066        
067        /**
068         * Build a jbi descriptor from a file archive
069         * 
070         * @param descriptorFile
071         *            path to the jbi descriptor, or to the root directory
072         * @return the Descriptor object
073         */
074        public static Descriptor buildDescriptor(File descriptorFile) {
075            if (descriptorFile.isDirectory()) {
076                descriptorFile = new File(descriptorFile, DESCRIPTOR_FILE);
077            }
078            if (descriptorFile.isFile()) {
079                try {
080                    return buildDescriptor(descriptorFile.toURL());
081                } catch (MalformedURLException e) {
082                    throw new RuntimeException("There is a bug here...", e);
083                }
084            }
085            return null;
086        }
087    
088        /**
089         * Build a jbi descriptor from the specified URL
090         * 
091         * @param url
092         *            url to the jbi descriptor
093         * @return the Descriptor object
094         */
095        public static Descriptor buildDescriptor(final URL url) {
096            try {
097                // Read descriptor
098                ByteArrayOutputStream baos = new ByteArrayOutputStream();
099                FileUtil.copyInputStream(url.openStream(), baos);
100                // Validate descriptor
101                SchemaFactory schemaFactory = SchemaFactory.newInstance(XSD_SCHEMA_LANGUAGE);
102                Schema schema = schemaFactory.newSchema(DescriptorFactory.class.getResource("/jbi-descriptor.xsd"));
103                Validator validator = schema.newValidator();
104                validator.setErrorHandler(new ErrorHandler() {
105                    public void warning(SAXParseException exception) throws SAXException {
106                        LOG.debug("Validation warning on " + url + ": " + exception);
107                    }
108                    public void error(SAXParseException exception) throws SAXException {
109                        LOG.info("Validation error on " + url + ": " + exception);
110                    }
111                    public void fatalError(SAXParseException exception) throws SAXException {
112                        throw exception;
113                    }
114                });
115                validator.validate(new StreamSource(new ByteArrayInputStream(baos.toByteArray())));
116                // Parse descriptor
117                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
118                factory.setNamespaceAware(true);
119                DocumentBuilder docBuilder = factory.newDocumentBuilder();
120                Document doc = docBuilder.parse(new ByteArrayInputStream(baos.toByteArray()));
121                Element jbi = doc.getDocumentElement();
122                Descriptor desc = new Descriptor();
123                desc.setVersion(Double.parseDouble(getAttribute(jbi, "version")));
124                Element child = DOMUtil.getFirstChildElement(jbi);
125                if ("component".equals(child.getLocalName())) {
126                    Component component = parseComponent(child);
127                    desc.setComponent(component);
128                } else if ("shared-library".equals(child.getLocalName())) {
129                    SharedLibrary sharedLibrary = parseSharedLibrary(child);
130                    desc.setSharedLibrary(sharedLibrary);
131                } else if ("service-assembly".equals(child.getLocalName())) {
132                    ServiceAssembly serviceAssembly = parseServiceAssembly(child);
133                    desc.setServiceAssembly(serviceAssembly);
134                } else if ("services".equals(child.getLocalName())) {
135                    Services services = parseServiceUnit(child);
136                    desc.setServices(services);
137                }
138                checkDescriptor(desc);
139                return desc;
140            } catch (Exception e) {
141                throw new RuntimeException(e);
142            }
143        }
144    
145        private static Services parseServiceUnit(Element child) {
146            Services services = new Services();
147            services.setBindingComponent(Boolean.valueOf(getAttribute(child, "binding-component")).booleanValue());
148            List<Provides> provides = new ArrayList<Provides>();
149            List<Consumes> consumes = new ArrayList<Consumes>();
150            for (Element e = DOMUtil.getFirstChildElement(child); e != null; e = DOMUtil.getNextSiblingElement(e)) {
151                if ("provides".equals(e.getLocalName())) {
152                    Provides p = new Provides();
153                    p.setInterfaceName(readAttributeQName(e, "interface-name"));
154                    p.setServiceName(readAttributeQName(e, "service-name"));
155                    p.setEndpointName(getAttribute(e, "endpoint-name"));
156                    provides.add(p);
157                } else if ("consumes".equals(e.getLocalName())) {
158                    Consumes c = new Consumes();
159                    c.setInterfaceName(readAttributeQName(e, "interface-name"));
160                    c.setServiceName(readAttributeQName(e, "service-name"));
161                    c.setEndpointName(getAttribute(e, "endpoint-name"));
162                    c.setLinkType(getAttribute(e, "link-type"));
163                    consumes.add(c);
164                }
165            }
166            services.setProvides(provides.toArray(new Provides[provides.size()]));
167            services.setConsumes(consumes.toArray(new Consumes[consumes.size()]));
168            return services;
169        }
170    
171        private static ServiceAssembly parseServiceAssembly(Element child) {
172            ServiceAssembly serviceAssembly = new ServiceAssembly();
173            List<ServiceUnit> sus = new ArrayList<ServiceUnit>();
174            for (Element e = DOMUtil.getFirstChildElement(child); e != null; e = DOMUtil.getNextSiblingElement(e)) {
175                if ("identification".equals(e.getLocalName())) {
176                    serviceAssembly.setIdentification(readIdentification(e));
177                } else if ("service-unit".equals(e.getLocalName())) {
178                    ServiceUnit su = new ServiceUnit();
179                    for (Element e2 = DOMUtil.getFirstChildElement(e); e2 != null; e2 = DOMUtil.getNextSiblingElement(e2)) {
180                        if ("identification".equals(e2.getLocalName())) {
181                            su.setIdentification(readIdentification(e2));
182                        } else if ("target".equals(e2.getLocalName())) {
183                            Target target = new Target();
184                            for (Element e3 = DOMUtil.getFirstChildElement(e2); e3 != null; e3 = DOMUtil.getNextSiblingElement(e3)) {
185                                if ("artifacts-zip".equals(e3.getLocalName())) {
186                                    target.setArtifactsZip(getText(e3));
187                                } else if ("component-name".equals(e3.getLocalName())) {
188                                    target.setComponentName(getText(e3));
189                                }
190                            }
191                            su.setTarget(target);
192                        }
193                    }
194                    sus.add(su);
195                } else if ("connections".equals(e.getLocalName())) {
196                    Connections connections = new Connections();
197                    List<Connection> cns = new ArrayList<Connection>();
198                    for (Element e2 = DOMUtil.getFirstChildElement(e); e2 != null; e2 = DOMUtil.getNextSiblingElement(e2)) {
199                        if ("connection".equals(e2.getLocalName())) {
200                            Connection cn = new Connection();
201                            for (Element e3 = DOMUtil.getFirstChildElement(e2); e3 != null; e3 = DOMUtil.getNextSiblingElement(e3)) {
202                                if ("consumer".equals(e3.getLocalName())) {
203                                    Consumer consumer = new Consumer();
204                                    consumer.setInterfaceName(readAttributeQName(e3, "interface-name"));
205                                    consumer.setServiceName(readAttributeQName(e3, "service-name"));
206                                    consumer.setEndpointName(getAttribute(e3, "endpoint-name"));
207                                    cn.setConsumer(consumer);
208                                } else if ("provider".equals(e3.getLocalName())) {
209                                    Provider provider = new Provider();
210                                    provider.setServiceName(readAttributeQName(e3, "service-name"));
211                                    provider.setEndpointName(getAttribute(e3, "endpoint-name"));
212                                    cn.setProvider(provider);
213                                }
214                            }
215                            cns.add(cn);
216                        }
217                    }
218                    connections.setConnections(cns.toArray(new Connection[cns.size()]));
219                    serviceAssembly.setConnections(connections);
220                }
221            }
222            serviceAssembly.setServiceUnits(sus.toArray(new ServiceUnit[sus.size()]));
223            return serviceAssembly;
224        }
225    
226        private static SharedLibrary parseSharedLibrary(Element child) {
227            SharedLibrary sharedLibrary = new SharedLibrary();
228            sharedLibrary.setClassLoaderDelegation(getAttribute(child, "class-loader-delegation"));
229            sharedLibrary.setVersion(getAttribute(child, "version"));
230            for (Element e = DOMUtil.getFirstChildElement(child); e != null; e = DOMUtil.getNextSiblingElement(e)) {
231                if ("identification".equals(e.getLocalName())) {
232                    sharedLibrary.setIdentification(readIdentification(e));
233                } else if ("shared-library-class-path".equals(e.getLocalName())) {
234                    ClassPath sharedLibraryClassPath = new ClassPath();
235                    List<String> l = new ArrayList<String>();
236                    for (Element e2 = DOMUtil.getFirstChildElement(e); e2 != null; e2 = DOMUtil.getNextSiblingElement(e2)) {
237                        if ("path-element".equals(e2.getLocalName())) {
238                            l.add(getText(e2));
239                        }
240                    }
241                    sharedLibraryClassPath.setPathList(l);
242                    sharedLibrary.setSharedLibraryClassPath(sharedLibraryClassPath);
243                }
244            }
245            return sharedLibrary;
246        }
247    
248        private static Component parseComponent(Element child) {
249            Component component = new Component();
250            component.setType(child.getAttribute("type"));
251            component.setComponentClassLoaderDelegation(getAttribute(child, "component-class-loader-delegation"));
252            component.setBootstrapClassLoaderDelegation(getAttribute(child, "bootstrap-class-loader-delegation"));
253            List<SharedLibraryList> sls = new ArrayList<SharedLibraryList>();
254            DocumentFragment ext = null;
255            for (Element e = DOMUtil.getFirstChildElement(child); e != null; e = DOMUtil.getNextSiblingElement(e)) {
256                if ("identification".equals(e.getLocalName())) {
257                    component.setIdentification(readIdentification(e));
258                } else if ("component-class-name".equals(e.getLocalName())) {
259                    component.setComponentClassName(getText(e));
260                    component.setDescription(getAttribute(e, "description"));
261                } else if ("component-class-path".equals(e.getLocalName())) {
262                    ClassPath componentClassPath = new ClassPath();
263                    List<String> l = new ArrayList<String>();
264                    for (Element e2 = DOMUtil.getFirstChildElement(e); e2 != null; e2 = DOMUtil.getNextSiblingElement(e2)) {
265                        if ("path-element".equals(e2.getLocalName())) {
266                            l.add(getText(e2));
267                        }
268                    }
269                    componentClassPath.setPathList(l);
270                    component.setComponentClassPath(componentClassPath);
271                } else if ("bootstrap-class-name".equals(e.getLocalName())) {
272                    component.setBootstrapClassName(getText(e));
273                } else if ("bootstrap-class-path".equals(e.getLocalName())) {
274                    ClassPath bootstrapClassPath = new ClassPath();
275                    List<String> l = new ArrayList<String>();
276                    for (Element e2 = DOMUtil.getFirstChildElement(e); e2 != null; e2 = DOMUtil.getNextSiblingElement(e2)) {
277                        if ("path-element".equals(e2.getLocalName())) {
278                            l.add(getText(e2));
279                        }
280                    }
281                    bootstrapClassPath.setPathList(l);
282                    component.setBootstrapClassPath(bootstrapClassPath);
283                } else if ("shared-library".equals(e.getLocalName())) {
284                    SharedLibraryList sl = new SharedLibraryList();
285                    sl.setName(getText(e));
286                    sl.setVersion(getAttribute(e, "version"));
287                    sls.add(sl);
288                } else {
289                    if (ext == null) {
290                        ext = child.getOwnerDocument().createDocumentFragment();
291                    }
292                    ext.appendChild(e);
293                }
294            }
295            component.setSharedLibraries(sls.toArray(new SharedLibraryList[sls.size()]));
296            if (ext != null) {
297                InstallationDescriptorExtension descriptorExtension = new InstallationDescriptorExtension();
298                descriptorExtension.setDescriptorExtension(ext);
299                component.setDescriptorExtension(descriptorExtension);
300            }
301            return component;
302        }
303        
304        private static String getAttribute(Element e, String name) {
305            if (e.hasAttribute(name)) {
306                return e.getAttribute(name);
307            } else {
308                return null;
309            }
310        }
311        
312        private static QName readAttributeQName(Element e, String name) {
313            String attr = getAttribute(e, name);
314            if (attr != null) {
315                return DOMUtil.createQName(e, attr);
316            } else {
317                return null;
318            }
319        }
320        
321        private static String getText(Element e) {
322            return DOMUtil.getElementText(e).trim();
323        }
324        
325        private static Identification readIdentification(Element e) {
326            Identification ident = new Identification();
327            for (Element e2 = DOMUtil.getFirstChildElement(e); e2 != null; e2 = DOMUtil.getNextSiblingElement(e2)) {
328                if ("name".equals(e2.getLocalName())) {
329                    ident.setName(DOMUtil.getElementText(e2));
330                } else if ("description".equals(e2.getLocalName())) {
331                    ident.setDescription(DOMUtil.getElementText(e2));
332                }
333            }
334            return ident;
335        }
336    
337        /**
338         * Check validity of the JBI descriptor
339         * 
340         * @param descriptor
341         *            the descriptor to check
342         * @throws Exception
343         *             if the descriptor is not valid
344         */
345        public static void checkDescriptor(Descriptor descriptor) {
346            List<String> violations = new ArrayList<String>();
347    
348            if (descriptor.getVersion() != 1.0) {
349                violations.add("JBI descriptor version should be set to '1.0' but is " + descriptor.getVersion());
350            }
351    
352            if (descriptor.getComponent() != null) {
353                checkComponent(violations, descriptor.getComponent());
354            } else if (descriptor.getServiceAssembly() != null) {
355                checkServiceAssembly(violations, descriptor.getServiceAssembly());
356            } else if (descriptor.getServices() != null) {
357                checkServiceUnit(violations, descriptor.getServices());
358            } else if (descriptor.getSharedLibrary() != null) {
359                checkSharedLibrary(violations, descriptor.getSharedLibrary());
360            } else {
361                violations.add("The jbi descriptor does not contain any informations");
362            }
363    
364            if (violations.size() > 0) {
365                throw new RuntimeException("The JBI descriptor is not valid, please correct these violations "
366                                + violations.toString());
367            }
368        }
369    
370        /**
371         * Checks that the component is valid
372         * 
373         * @param violations
374         *            A list of violations that the check can add to
375         * 
376         * @param component
377         *            The component descriptor that is being checked
378         */
379        private static void checkComponent(List<String> violations, Component component) {
380            if (component.getIdentification() == null) {
381                violations.add("The component has not identification");
382            } else {
383                if (isBlank(component.getIdentification().getName())) {
384                    violations.add("The component name is not set");
385                }
386            }
387            if (component.getBootstrapClassName() == null) {
388                violations.add("The component has not defined a boot-strap class name");
389            }
390            if (component.getBootstrapClassPath() == null || component.getBootstrapClassPath().getPathElements() == null) {
391                violations.add("The component has not defined any boot-strap class path elements");
392            }
393        }
394    
395        /**
396         * Checks that the service assembly is valid
397         * 
398         * @param violations
399         *            A list of violations that the check can add to
400         * 
401         * @param serviceAssembly
402         *            The service assembly descriptor that is being checked
403         */
404        private static void checkServiceAssembly(List<String> violations, ServiceAssembly serviceAssembly) {
405            if (serviceAssembly.getIdentification() == null) {
406                violations.add("The service assembly has not identification");
407            } else {
408                if (isBlank(serviceAssembly.getIdentification().getName())) {
409                    violations.add("The service assembly name is not set"); 
410                }
411            }
412        }
413    
414        /**
415         * Checks that the service unit is valid
416         * 
417         * @param violations
418         *            A list of violations that the check can add to
419         * 
420         * @param services
421         *            The service unit descriptor that is being checked
422         */
423        private static void checkServiceUnit(List<String> violations, Services services) {
424            // TODO Auto-generated method stub
425            
426        }
427    
428        /**
429         * Checks that the shared library is valid
430         * 
431         * @param violations
432         *            A list of violations that the check can add to
433         * 
434         * @param sharedLibrary
435         *            The shared library descriptor that is being checked
436         */
437        private static void checkSharedLibrary(List<String> violations, SharedLibrary sharedLibrary) {
438            if (sharedLibrary.getIdentification() == null) {
439                violations.add("The shared library has not identification");
440            } else {
441                if (isBlank(sharedLibrary.getIdentification().getName())) {
442                    violations.add("The shared library name is not set"); 
443                }
444            }
445        }
446    
447        /**
448         * Retrieves the jbi descriptor as a string
449         * 
450         * @param descriptorFile
451         *            path to the jbi descriptor, or to the root directory
452         * @return the contents of the jbi descriptor
453         */
454        public static String getDescriptorAsText(File descriptorFile) {
455            if (descriptorFile.isDirectory()) {
456                descriptorFile = new File(descriptorFile, DESCRIPTOR_FILE);
457            }
458            if (descriptorFile.isFile()) {
459                try {
460                    ByteArrayOutputStream os = new ByteArrayOutputStream();
461                    InputStream is = new FileInputStream(descriptorFile);
462                    FileUtil.copyInputStream(is, os);
463                    return os.toString();
464                } catch (Exception e) {
465                    LOG.debug("Error reading jbi descritor: " + descriptorFile, e);
466                }
467            }
468            return null;
469        }
470    
471        /**
472         * <p>Checks if a String is whitespace, empty ("") or null.</p>
473         *
474         * <pre>
475         * StringUtils.isBlank(null)      = true
476         * StringUtils.isBlank("")        = true
477         * StringUtils.isBlank(" ")       = true
478         * StringUtils.isBlank("bob")     = false
479         * StringUtils.isBlank("  bob  ") = false
480         * </pre>
481         *
482         * @param str  the String to check, may be null
483         * @return <code>true</code> if the String is null, empty or whitespace
484         * 
485         * Copied from org.apache.commons.lang.StringUtils#isBlanck
486         */
487        private static boolean isBlank(String str) {
488            if (str == null) {
489                return true;
490            }
491            int strLen = str.length();
492            if (strLen == 0) {
493                return true;
494            }
495            for (int i = 0; i < strLen; i++) {
496                if (!(Character.isWhitespace(str.charAt(i)))) {
497                    return false;
498                }
499            }
500            return true;
501        }
502    
503    }