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: 549455 $ $Date: 2007-06-21 08:12:27 -0400 (Thu, 21 Jun 2007) $
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 }