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