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 }