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.geronimo.deployment.plugin;
018    
019    import java.io.BufferedReader;
020    import java.io.File;
021    import java.io.FileReader;
022    import java.io.IOException;
023    import java.io.InputStreamReader;
024    import java.io.Reader;
025    import java.util.Collection;
026    import java.util.LinkedList;
027    import java.util.List;
028    import java.util.Stack;
029    import java.util.jar.JarEntry;
030    import java.util.jar.JarFile;
031    import javax.enterprise.deploy.spi.TargetModuleID;
032    import javax.xml.parsers.ParserConfigurationException;
033    import javax.xml.parsers.SAXParser;
034    import javax.xml.parsers.SAXParserFactory;
035    import org.apache.geronimo.common.DeploymentException;
036    import org.apache.geronimo.common.FileUtils;
037    import org.apache.geronimo.kernel.repository.Artifact;
038    import org.apache.geronimo.kernel.repository.Version;
039    import org.apache.geronimo.kernel.util.XmlUtil;
040    import org.apache.commons.logging.Log;
041    import org.apache.commons.logging.LogFactory;
042    import org.xml.sax.Attributes;
043    import org.xml.sax.InputSource;
044    import org.xml.sax.SAXException;
045    import org.xml.sax.helpers.DefaultHandler;
046    
047    /**
048     * Knows how to suck a Config ID out of a module and/or plan
049     *
050     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
051     */
052    public class ConfigIDExtractor {
053    
054        private static final Log log = LogFactory.getLog(ConfigIDExtractor.class);
055    
056        /**
057         * Attempt to calculate the Geronimo ModuleID for a J2EE application
058         * module.
059         *
060         * Given a File representing an archive (which may be a JAR file or a
061         * directory laid out like a JAR file), identify it's J2EE module type
062         * based on which (if any) deployment descriptor is present, and then look
063         * for a Geronimo deployment plan in the usual place, and if one is found,
064         * retrieve the configId from the Geronimo deployment plan.
065         *
066         * todo: Handle Spring and other weird deployment types?
067         *
068         * @param module A Jar file or directory representing a J2EE module
069         * @return The configId in the Geronimo deployment plan for this module,
070         *         or null if no Geronimo deployment plan was identified.
071         */
072        public static String extractModuleIdFromArchive(File module) throws IOException, DeploymentException {
073            if(!module.canRead()) {
074                throw new DeploymentException("Not a readable file ("+module.getAbsolutePath()+")");
075            }
076            if(module.isDirectory()) {
077                File target;
078                if(new File(module, "WEB-INF/web.xml").canRead()) {
079                    target = new File(module, "WEB-INF/geronimo-web.xml");
080                } else if(new File(module, "META-INF/application.xml").canRead()) {
081                    target = new File(module, "META-INF/geronimo-application.xml");
082                } else if(new File(module, "META-INF/ejb-jar.xml").canRead()) {
083                    target = new File(module, "META-INF/openejb-jar.xml");
084                } else if(new File(module, "META-INF/ra.xml").canRead()) {
085                    target = new File(module, "META-INF/geronimo-ra.xml");
086                } else if(new File(module, "META-INF/application-client.xml").canRead()) {
087                    target = new File(module, "META-INF/geronimo-application-client.xml");
088                } else {
089                    target = new File(module, "META-INF/geronimo-service.xml");
090                }
091                if(target.canRead()) {
092                    Reader in = new BufferedReader(new FileReader(target));
093                    String name = extractModuleIdFromPlan(in);
094                    if(name != null) {
095                        Artifact artifact = Artifact.create(name);
096                        if(artifact.getArtifactId() == null) {
097                            name = new Artifact(artifact.getGroupId(), module.getName(), artifact.getVersion(), artifact.getType()).toString();
098                        }
099                    }
100                    return name;
101                }
102            } else {
103                if(!FileUtils.isZipFile(module)) {
104                    throw new DeploymentException(module.getAbsolutePath()+" is neither a JAR file nor a directory!");
105                }
106                JarFile input = new JarFile(module);
107                //todo: instead of looking for specific file names here, do something generic.
108                //      Perhaps load a DConfigBeanRoot and look for a configId property on the first child,
109                //      though that would probably be a little heavyweight.
110                try {
111                    JarEntry entry;
112                    if(input.getJarEntry("WEB-INF/web.xml") != null) {
113                        entry = input.getJarEntry("WEB-INF/geronimo-web.xml");
114                    } else if(input.getJarEntry("META-INF/application.xml") != null) {
115                        entry = input.getJarEntry("META-INF/geronimo-application.xml");
116                    } else if(input.getJarEntry("META-INF/ejb-jar.xml") != null) {
117                        entry = input.getJarEntry("META-INF/openejb-jar.xml");
118                    } else if(input.getJarEntry("META-INF/ra.xml") != null) {
119                        entry = input.getJarEntry("META-INF/geronimo-ra.xml");
120                    } else if(input.getJarEntry("META-INF/application-client.xml") != null) {
121                        entry = input.getJarEntry("META-INF/geronimo-application-client.xml");
122                    } else {
123                        entry = input.getJarEntry("META-INF/geronimo-service.xml");
124                    }
125                    if(entry != null) {
126                        Reader in = new BufferedReader(new InputStreamReader(input.getInputStream(entry)));
127                        String name = extractModuleIdFromPlan(in);
128                        if(name != null) {
129                            Artifact artifact = Artifact.create(name);
130                            if(artifact.getArtifactId() == null) {
131                                name = new Artifact(artifact.getGroupId(), module.getName(), artifact.getVersion(), artifact.getType()).toString();
132                            }
133                        }
134                        return name;
135                    }
136                } finally {
137                    input.close();
138                }
139            }
140            return null;
141        }
142    
143        /**
144         * Attempt to calculate the Geronimo ModuleID for a Geronimo deployment
145         * plan.
146         *
147         * @param plan A Geronimo deployment plan (which must be an XML file).
148         * @return The configId in the Geronimo deployment plan for this module.
149         */
150        public static String extractModuleIdFromPlan(File plan) throws IOException {
151            if(plan.isDirectory() || !plan.canRead()) {
152                throw new IllegalArgumentException(plan.getAbsolutePath()+" is not a readable XML file!");
153            }
154            Reader in = new BufferedReader(new FileReader(plan));
155            return extractModuleIdFromPlan(in);
156        }
157    
158        /**
159         * Given a list of all available TargetModuleIDs and the name of a module,
160         * find the TargetModuleIDs that represent that module.
161         *
162         * @param allModules  The list of all available modules
163         * @param name        The module name to search for
164         * @param fromPlan    Should be true if the module name was loaded from a
165         *                    deployment plan (thus no group means the default
166         *                    group) or false if the module name was provided by
167         *                    the user (thus no group means any group).
168         *
169         * @throws DeploymentException If no TargetModuleIDs have that module.
170         */
171        public static Collection identifyTargetModuleIDs(TargetModuleID[] allModules, String name, boolean fromPlan) throws DeploymentException {
172            List list = new LinkedList();
173            int pos;
174            if((pos = name.indexOf('|')) > -1) {
175                String target = name.substring(0, pos);
176                String module = name.substring(pos+1);
177                Artifact artifact = Artifact.create(module);
178                artifact = new Artifact(artifact.getGroupId() == null && fromPlan ? Artifact.DEFAULT_GROUP_ID : artifact.getGroupId(),
179                        artifact.getArtifactId(), fromPlan ? (Version)null : artifact.getVersion(), artifact.getType());
180                // First pass: exact match
181                for(int i=0; i<allModules.length; i++) {
182                    if(allModules[i].getTarget().getName().equals(target) && artifact.matches(Artifact.create(allModules[i].getModuleID()))) {
183                        list.add(allModules[i]);
184                    }
185                }
186            }
187            if(!list.isEmpty()) {
188                return list;
189            }
190            // second pass: module matches
191            Artifact artifact;
192            if (name.indexOf("/") > -1) {
193                artifact = Artifact.create(name);
194                artifact = new Artifact(artifact.getGroupId() == null && fromPlan ? Artifact.DEFAULT_GROUP_ID : artifact.getGroupId(),
195                        artifact.getArtifactId(), fromPlan ? (Version)null : artifact.getVersion(), artifact.getType());
196            } else {
197                artifact = new Artifact(fromPlan ? Artifact.DEFAULT_GROUP_ID : null, name, (Version)null, null);
198            }
199            for(int i = 0; i < allModules.length; i++) {
200                if(artifact.matches(Artifact.create(allModules[i].getModuleID()))) {
201                    list.add(allModules[i]);
202                }
203            }
204            if(list.isEmpty()) {
205                throw new DeploymentException(name+" does not appear to be a the name of a module " +
206                        "available on the selected server. Perhaps it has already been " +
207                        "stopped or undeployed?  If you're trying to specify a " +
208                        "TargetModuleID, use the syntax TargetName|ModuleName instead. " +
209                        "If you're not sure what's running, try the list-modules command.");
210            }
211            return list;
212        }
213    
214        private static String extractModuleIdFromPlan(Reader plan) throws IOException {
215            SAXParserFactory factory = XmlUtil.newSAXParserFactory();
216            factory.setNamespaceAware(true);
217            factory.setValidating(false);
218            try {
219                SAXParser parser = factory.newSAXParser();
220                ConfigIdHandler handler = new ConfigIdHandler();
221                parser.parse(new InputSource(plan), handler);
222                if(handler.formatIs10) {
223                    log.warn("Geronimo deployment plan uses Geronimo 1.0 syntax.  Please update to Geronimo 1.1 syntax when possible.");
224                }
225                return handler.configId;
226            } catch (ParserConfigurationException e) {
227                throw (IOException)new IOException("Unable to read plan: "+e.getMessage()).initCause(e);
228            } catch (SAXException e) {
229                throw (IOException)new IOException("Unable to read plan: "+e.getMessage()).initCause(e);
230            } finally {
231                plan.close();
232            }
233        }
234    
235        /**
236         * Try to determine whether a file is a JAR File (or, at least, a ZIP file).
237         *
238         * @deprecated See org.apache.geronimo.common.FileUtils.isJarFile
239         */
240        public static boolean isJarFile(File file) throws DeploymentException {
241            try {
242                return FileUtils.isZipFile(file);
243            } catch (IOException e) {
244                throw new DeploymentException("Unable to read file "+file.getAbsolutePath(), e);
245            }
246        }
247    
248        private static class ConfigIdHandler extends DefaultHandler {
249            private String configId;
250            private boolean inConfigId;
251            private String groupId = "", artifactId = "", version = "", type = "";
252            private String inElement = null;
253            private Stack parent = new Stack();
254            private boolean formatIs10 = false;
255            private String defaultType;
256    
257            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
258                if(defaultType == null && uri != null && !uri.equals("")) {
259                    setDefaultType(uri);
260                }
261                if(inConfigId) {
262                    if(localName.equals("groupId") || localName.equals("artifactId") || localName.equals("version") || localName.equals("type")) {
263                        inElement = localName;
264                    }
265                } else {
266                    if(parent.size() == 2 && localName.equals("moduleId")) {
267                        inConfigId = true; // only document/environment/configId, not e.g. configId in nested plan in EAR
268                    } else {
269                        if(parent.size() == 0 && attributes.getIndex("moduleId") > -1) {
270                            configId = attributes.getValue("moduleId");
271                            formatIs10 = true;
272                        }
273                    }
274                }
275                parent.push(localName);
276            }
277    
278            private void setDefaultType(String namespace) {
279                if(namespace.indexOf("web") > -1) {
280                    defaultType = "war";
281                } else if(namespace.indexOf("openejb") > -1) {
282                    defaultType = "jar";
283                } else if(namespace.indexOf("connector") > -1) {
284                    defaultType = "rar";
285                } else if(namespace.indexOf("application-client") > -1) {
286                    defaultType = "jar";
287                } else if(namespace.indexOf("application") > -1) {
288                    defaultType = "ear";
289                } else {
290                    defaultType = "car";
291                }
292            }
293    
294            public void characters(char ch[], int start, int length) throws SAXException {
295                if(inElement != null) {
296                    formatIs10 = false;
297                    if(inElement.equals("groupId")) groupId += new String(ch, start, length);
298                    else if(inElement.equals("artifactId")) artifactId += new String(ch, start, length);
299                    else if(inElement.equals("version")) version += new String(ch, start, length);
300                    else if(inElement.equals("type")) type += new String(ch, start, length);
301                }
302            }
303    
304            public void endElement(String uri, String localName, String qName) throws SAXException {
305                inElement = null;
306                if(inConfigId && localName.equals("moduleId")) {
307                    inConfigId = false;
308                }
309                if(parent.peek().equals(localName)) {
310                    parent.pop();
311                } else {
312                    throw new IllegalStateException("End of "+localName+" but expecting "+parent.peek());
313                }
314            }
315    
316            public void endDocument() throws SAXException {
317                if(!formatIs10) {
318                    if(type.equals("") && defaultType != null) {
319                        type = defaultType;
320                    }
321                    configId = groupId+"/"+artifactId+"/"+version+"/"+type;
322                }
323                if(configId.equals("///")) {
324                    configId = null;
325                }
326            }
327        }
328    }