001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.geronimo.openejb.deployment;
019    
020    import org.apache.geronimo.common.DeploymentException;
021    import org.apache.geronimo.deployment.service.EnvironmentBuilder;
022    import org.apache.geronimo.deployment.xmlbeans.XmlBeansUtil;
023    import org.apache.geronimo.kernel.repository.Artifact;
024    import org.apache.geronimo.kernel.repository.Dependency;
025    import org.apache.geronimo.kernel.repository.Environment;
026    import org.apache.geronimo.openejb.xbeans.ejbjar.OpenejbEjbJarDocument;
027    import org.apache.geronimo.openejb.xbeans.ejbjar.OpenejbGeronimoEjbJarType;
028    import org.apache.geronimo.schema.SchemaConversionUtils;
029    import org.apache.geronimo.xbeans.javaee.EjbJarDocument;
030    import org.apache.geronimo.xbeans.javaee.EjbJarType;
031    import org.apache.openejb.jee.EjbJar;
032    import org.apache.openejb.jee.EnterpriseBean;
033    import org.apache.openejb.jee.PersistenceContextRef;
034    import org.apache.openejb.jee.PersistenceContextType;
035    import org.apache.openejb.jee.oejb2.ArtifactType;
036    import org.apache.openejb.jee.oejb2.DependencyType;
037    import org.apache.openejb.jee.oejb2.EnvironmentType;
038    import org.apache.openejb.jee.oejb2.GeronimoEjbJarType;
039    import org.apache.openejb.jee.oejb2.ImportType;
040    import org.apache.openejb.jee.oejb2.JaxbOpenejbJar2;
041    import org.apache.xmlbeans.XmlCursor;
042    import org.apache.xmlbeans.XmlDocumentProperties;
043    import org.apache.xmlbeans.XmlException;
044    import org.apache.xmlbeans.XmlObject;
045    
046    import javax.xml.bind.JAXBContext;
047    import javax.xml.bind.JAXBElement;
048    import javax.xml.bind.JAXBException;
049    import javax.xml.bind.Marshaller;
050    import javax.xml.bind.ValidationEvent;
051    import javax.xml.namespace.QName;
052    import java.io.ByteArrayOutputStream;
053    import java.io.File;
054    import java.io.FileOutputStream;
055    import java.io.InputStream;
056    import java.io.IOException;
057    
058    public final class XmlUtil {
059        public static final QName OPENEJBJAR_QNAME = OpenejbEjbJarDocument.type.getDocumentElementName();
060        private static final QName CMP_VERSION = new QName(SchemaConversionUtils.J2EE_NAMESPACE, "cmp-version");
061    
062        private XmlUtil() {
063        }
064    
065        public static <T> String marshal(T object) throws DeploymentException {
066            try {
067                Class type = object.getClass();
068    
069                if (object instanceof JAXBElement) {
070                    JAXBElement element = (JAXBElement) object;
071                    type = element.getValue().getClass();
072                }
073    
074                JAXBContext ctx = JAXBContext.newInstance(type);
075                Marshaller marshaller = ctx.createMarshaller();
076    
077                ByteArrayOutputStream baos = new ByteArrayOutputStream();
078                marshaller.marshal(object, baos);
079    
080                String xml = new String(baos.toByteArray());
081                return xml;
082            } catch (JAXBException e) {
083                throw new DeploymentException(e);
084            }
085        }
086    
087        public static EjbJarType convertToXmlbeans(EjbJar ejbJar) throws DeploymentException {
088            //
089            // it would be nice if Jaxb had a way to convert the object to a
090            // sax reader that could be fed directly into xmlbeans
091            //
092    
093            // the geronimo xml beans tree is totally broken... fix some obvious stuff here
094            for (EnterpriseBean enterpriseBean : ejbJar.getEnterpriseBeans()) {
095                for (PersistenceContextRef ref : enterpriseBean.getPersistenceContextRef()) {
096                    if (ref.getPersistenceContextType() == PersistenceContextType.TRANSACTION) {
097                        ref.setPersistenceContextType(null);
098                    }
099                }
100            }
101    
102            // marshal to xml
103            String xml = marshal(ejbJar);
104            try {
105                // parse the xml
106                EjbJarDocument ejbJarDoc = convertToEJBSchema(XmlBeansUtil.parse(xml));
107                EjbJarType ejbJarType = ejbJarDoc.getEjbJar();
108                return ejbJarType;
109            } catch (XmlException e) {
110                throw new DeploymentException("Error parsing ejb-jar.xml", e);
111            }
112    
113        }
114    
115        public static OpenejbGeronimoEjbJarType convertToXmlbeans(GeronimoEjbJarType geronimoEjbJarType) throws DeploymentException {
116            //
117            // it would be nice if Jaxb had a way to convert the object to a
118            // sax reader that could be fed directly into xmlbeans
119            //
120            JAXBElement root = new JAXBElement(new QName("http://geronimo.apache.org/xml/ns/j2ee/ejb/openejb-2.0","ejb-jar"), GeronimoEjbJarType.class, geronimoEjbJarType);
121    
122            // marshal to xml
123    
124            String xml = marshal(root);
125    
126            try {
127                XmlObject xmlObject = XmlBeansUtil.parse(xml);
128    
129                OpenejbGeronimoEjbJarType geronimoOpenejb = (OpenejbGeronimoEjbJarType) SchemaConversionUtils.fixGeronimoSchema(xmlObject, OPENEJBJAR_QNAME, OpenejbGeronimoEjbJarType.type);
130                return geronimoOpenejb;
131            } catch (Throwable e) {
132                String filePath = "<error: could not be written>";
133                try {
134                    File tempFile = File.createTempFile("openejb-jar-", ".xml");
135                    try {
136                        FileOutputStream out = new FileOutputStream(tempFile);
137                        out.write(xml.getBytes());
138                        out.close();
139                    } catch (Exception weTried) {
140                    }
141                    filePath = tempFile.getAbsolutePath();
142                } catch (IOException notImportant) {
143                }
144    
145                throw new DeploymentException("Error parsing geronimo-openejb.xml with xmlbeans.  For debug purposes, XML content written to: "+filePath, e);
146            }
147        }
148    
149        public static Environment buildEnvironment(EnvironmentType environmentType, Environment defaultEnvironment) {
150            Environment environment = new Environment();
151            if (environmentType != null) {
152                if (environmentType.getModuleId() != null) {
153                    environment.setConfigId(toArtifact(environmentType.getModuleId(), null));
154                }
155    
156                if (environmentType.getDependencies() != null) {
157                    for (DependencyType dependencyType : environmentType.getDependencies().getDependency()) {
158                        Dependency dependency = toDependency(dependencyType);
159                        environment.addDependency(dependency);
160                    }
161                }
162                environment.setInverseClassLoading(environmentType.isInverseClassloading());
163                environment.setSuppressDefaultEnvironment(environmentType.isSuppressDefaultEnvironment());
164                if (environmentType.getHiddenClasses() != null) {
165                    environment.setHiddenClasses(environmentType.getHiddenClasses().getFilter());
166                }
167                if (environmentType.getNonOverridableClasses() != null) {
168                    environment.setNonOverrideableClasses(environmentType.getNonOverridableClasses().getFilter());
169                }
170            }
171            if (!environment.isSuppressDefaultEnvironment()) {
172                EnvironmentBuilder.mergeEnvironments(environment, defaultEnvironment);
173            }
174    
175            return environment;
176        }
177    
178        private static Dependency toDependency(DependencyType dependencyType) {
179            Artifact artifact = toArtifact(dependencyType, null);
180            if (ImportType.CLASSES.equals(dependencyType.getImport())) {
181                return new Dependency(artifact, org.apache.geronimo.kernel.repository.ImportType.CLASSES);
182            } else if (ImportType.SERVICES.equals(dependencyType.getImport())) {
183                return new Dependency(artifact, org.apache.geronimo.kernel.repository.ImportType.SERVICES);
184            } else if (dependencyType.getImport() == null) {
185                return new Dependency(artifact, org.apache.geronimo.kernel.repository.ImportType.ALL);
186            } else {
187                throw new IllegalArgumentException("Unknown import type: " + dependencyType.getImport());
188            }
189        }
190    
191        private static Artifact toArtifact(ArtifactType artifactType, String defaultType) {
192            String groupId = artifactType.getGroupId();
193            String type = artifactType.getType();
194            if (type == null) type = defaultType;
195            String artifactId = artifactType.getArtifactId();
196            String version = artifactType.getVersion();
197            return new Artifact(groupId, artifactId, version, type);
198        }
199    
200        public static GeronimoEjbJarType createDefaultPlan(String name, EjbJar ejbJar) {
201            String id = ejbJar.getId();
202            if (id == null) {
203                id = name;
204                if (id.endsWith(".jar")) {
205                    id = id.substring(0, id.length() - 4);
206                }
207                if (id.endsWith("/")) {
208                    id = id.substring(0, id.length() - 1);
209                }
210            }
211    
212    
213            ArtifactType artifactType = new ArtifactType();
214            artifactType.setArtifactId(id);
215    
216            EnvironmentType environmentType = new EnvironmentType();
217            environmentType.setModuleId(artifactType);
218    
219            GeronimoEjbJarType geronimoEjbJarType = new GeronimoEjbJarType();
220            geronimoEjbJarType.setEnvironment(environmentType);
221    
222            return geronimoEjbJarType;
223        }
224    
225        public static String getJ2eeStringValue(org.apache.geronimo.xbeans.javaee.String string) {
226            if (string == null) {
227                return null;
228            }
229            return string.getStringValue();
230        }
231    
232        public static class ValidationEventHandler implements javax.xml.bind.ValidationEventHandler {
233            public boolean handleEvent(ValidationEvent validationEvent) {
234                System.out.println(validationEvent.getMessage());
235                return true;
236            }
237        }
238    
239        // TODO I don't think we need this since openejb will always generate the newest spec,
240        // but this code is doing more than just schema conversion, it is also converting message
241        // driven properties to activation-config
242        // coerce to newest spec... this shouldn't be necessary as the jaxb tree always creates the newest spec
243        public static EjbJarDocument convertToEJBSchema(XmlObject xmlObject) throws XmlException {
244            if (EjbJarDocument.type.equals(xmlObject.schemaType())) {
245    //            XmlBeansUtil.validateDD(xmlObject);
246                return (EjbJarDocument) xmlObject;
247            }
248            XmlCursor cursor = xmlObject.newCursor();
249            XmlCursor moveable = xmlObject.newCursor();
250            //cursor is intially located before the logical STARTDOC token
251            try {
252                cursor.toFirstChild();
253                if (EjbJarDocument.type.getDocumentElementName().getNamespaceURI().equals(cursor.getName().getNamespaceURI())) {
254                    XmlObject result = xmlObject.changeType(EjbJarDocument.type);
255                    // XmlBeansUtil.validateDD(result);
256                    return (EjbJarDocument) result;
257                }
258                // deployment descriptor is probably in EJB 1.1 or 2.0 format
259                XmlDocumentProperties xmlDocumentProperties = cursor.documentProperties();
260                String publicId = xmlDocumentProperties.getDoctypePublicId();
261                String cmpVersion;
262                if ("-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN".equals(publicId)) {
263                    cmpVersion = "1.x";
264                } else if ("-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN".equals(publicId)) {
265                    cmpVersion = null;//2.x is the default "2.x";
266                } else {
267                    throw new XmlException("Unrecognized document type: " + publicId);
268                }
269                String schemaLocationURL = "http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd";
270                String version = "2.1";
271                SchemaConversionUtils.convertToSchema(cursor, SchemaConversionUtils.J2EE_NAMESPACE, schemaLocationURL, version);
272                //play with message-driven
273                cursor.toStartDoc();
274                convertBeans(cursor, moveable, cmpVersion);
275            } finally {
276                cursor.dispose();
277                moveable.dispose();
278            }
279            XmlObject result = xmlObject.changeType(EjbJarDocument.type);
280            if (result != null) {
281                XmlBeansUtil.validateDD(result);
282                return (EjbJarDocument) result;
283            }
284            XmlBeansUtil.validateDD(xmlObject);
285            return (EjbJarDocument) xmlObject;
286        }
287    
288        private static void convertBeans(XmlCursor cursor, XmlCursor moveable, String cmpVersion) {
289            cursor.toChild(SchemaConversionUtils.J2EE_NAMESPACE, "ejb-jar");
290            cursor.toChild(SchemaConversionUtils.J2EE_NAMESPACE, "enterprise-beans");
291            if (cursor.toFirstChild()) {
292                //there's at least one ejb...
293                do {
294                    cursor.push();
295                    String type = cursor.getName().getLocalPart();
296                    if ("session".equals(type)) {
297                        cursor.toChild(SchemaConversionUtils.J2EE_NAMESPACE, "transaction-type");
298                        cursor.toNextSibling();
299                        SchemaConversionUtils.convertToJNDIEnvironmentRefsGroup(SchemaConversionUtils.J2EE_NAMESPACE, cursor, moveable);
300                    } else if ("entity".equals(type)) {
301                        cursor.toChild(SchemaConversionUtils.J2EE_NAMESPACE, "persistence-type");
302                        String persistenceType = cursor.getTextValue();
303                        //reentrant is the last required tag before jndiEnvironmentRefsGroup
304                        cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "reentrant");
305                        //Convert 2.0 True/False to true/false for 2.1
306                        cursor.setTextValue(cursor.getTextValue().toLowerCase());
307                        if (cmpVersion != null && !cursor.toNextSibling(CMP_VERSION) && "Container".equals(persistenceType)) {
308                            cursor.toNextSibling();
309                            cursor.insertElementWithText(CMP_VERSION, cmpVersion);
310                        }
311    
312                        cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "abstract-schema-name");
313                        while (cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "cmp-field")) {
314                        }
315                        cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "primkey-field");
316                        cursor.toNextSibling();
317                        SchemaConversionUtils.convertToJNDIEnvironmentRefsGroup(SchemaConversionUtils.J2EE_NAMESPACE, cursor, moveable);
318                    } else if ("message-driven".equals(type)) {
319                        cursor.toFirstChild();
320                        if (cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "messaging-type")) {
321                            cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "transaction-type");
322                        } else {
323                            cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "transaction-type");
324                            //insert messaging-type (introduced in EJB 2.1 spec) before transaction-type
325                            cursor.insertElementWithText("messaging-type", SchemaConversionUtils.J2EE_NAMESPACE, "javax.jms.MessageListener");
326                            //cursor still on transaction-type
327                        }
328                        if (!cursor.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "activation-config")) {
329                            //skip transaction-type
330                            cursor.toNextSibling();
331                            //convert EJB 2.0 elements to activation-config-properties.
332                            moveable.toCursor(cursor);
333                            cursor.push();
334                            cursor.beginElement("activation-config", SchemaConversionUtils.J2EE_NAMESPACE);
335                            boolean hasProperties = addActivationConfigProperty(moveable, cursor, "message-selector", "messageSelector");
336                            hasProperties |= addActivationConfigProperty(moveable, cursor, "acknowledge-mode", "acknowledgeMode");
337                            if (new QName(SchemaConversionUtils.J2EE_NAMESPACE, "message-driven-destination").equals(moveable.getName()) ||
338                                    moveable.toNextSibling(SchemaConversionUtils.J2EE_NAMESPACE, "message-driven-destination")) {
339                                moveable.push();
340                                moveable.toFirstChild();
341                                hasProperties |= addActivationConfigProperty(moveable, cursor, "destination-type", "destinationType");
342                                hasProperties |= addActivationConfigProperty(moveable, cursor, "subscription-durability", "subscriptionDurability");
343                                moveable.pop();
344                                moveable.removeXml();
345                            }
346                            cursor.pop();
347                            if (!hasProperties) {
348                                //the activation-config element that we created is empty so delete it
349                                cursor.toPrevSibling();
350                                cursor.removeXml();
351                                //cursor should now be at first element in JNDIEnvironmentRefsGroup
352                            }
353                        } else {
354                            //cursor pointing at activation-config
355                            cursor.toNextSibling();
356                            //cursor should now be at first element in JNDIEnvironmentRefsGroup
357                        }
358                        SchemaConversionUtils.convertToJNDIEnvironmentRefsGroup(SchemaConversionUtils.J2EE_NAMESPACE, cursor, moveable);
359                    }
360                    cursor.pop();
361                } while (cursor.toNextSibling());
362            }
363        }
364    
365        private static boolean addActivationConfigProperty(XmlCursor moveable, XmlCursor cursor, String elementName, String propertyName) {
366            QName name = new QName(SchemaConversionUtils.J2EE_NAMESPACE, elementName);
367            if (name.equals(moveable.getName()) || moveable.toNextSibling(name)) {
368                cursor.push();
369                cursor.beginElement("activation-config-property", SchemaConversionUtils.J2EE_NAMESPACE);
370                cursor.insertElementWithText("activation-config-property-name", SchemaConversionUtils.J2EE_NAMESPACE, propertyName);
371                cursor.insertElementWithText("activation-config-property-value", SchemaConversionUtils.J2EE_NAMESPACE, moveable.getTextValue());
372                moveable.removeXml();
373                cursor.pop();
374                cursor.toNextSibling();
375                return true;
376            }
377            return false;
378        }
379    }