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.console.car; 018 019 import java.io.BufferedInputStream; 020 import java.io.File; 021 import java.io.FileInputStream; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.OutputStream; 025 import java.io.PrintWriter; 026 import java.util.Iterator; 027 import java.util.List; 028 import java.util.Set; 029 import javax.servlet.ServletException; 030 import javax.servlet.http.HttpServlet; 031 import javax.servlet.http.HttpServletRequest; 032 import javax.servlet.http.HttpServletResponse; 033 import javax.xml.parsers.DocumentBuilder; 034 import javax.xml.parsers.DocumentBuilderFactory; 035 import javax.xml.parsers.ParserConfigurationException; 036 import javax.xml.transform.OutputKeys; 037 import javax.xml.transform.Transformer; 038 import javax.xml.transform.TransformerException; 039 import javax.xml.transform.TransformerFactory; 040 import javax.xml.transform.dom.DOMSource; 041 import javax.xml.transform.stream.StreamResult; 042 import org.apache.commons.logging.Log; 043 import org.apache.commons.logging.LogFactory; 044 import org.apache.geronimo.gbean.AbstractName; 045 import org.apache.geronimo.gbean.AbstractNameQuery; 046 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory; 047 import org.apache.geronimo.kernel.Kernel; 048 import org.apache.geronimo.kernel.KernelRegistry; 049 import org.apache.geronimo.kernel.util.XmlUtil; 050 import org.apache.geronimo.kernel.config.ConfigurationInfo; 051 import org.apache.geronimo.kernel.config.ConfigurationManager; 052 import org.apache.geronimo.kernel.config.ConfigurationStore; 053 import org.apache.geronimo.kernel.config.ConfigurationUtil; 054 import org.apache.geronimo.kernel.config.NoSuchStoreException; 055 import org.apache.geronimo.kernel.config.NoSuchConfigException; 056 import org.apache.geronimo.kernel.repository.Artifact; 057 import org.apache.geronimo.kernel.repository.Repository; 058 import org.apache.geronimo.kernel.repository.Version; 059 import org.apache.geronimo.system.plugin.PluginInstaller; 060 import org.apache.geronimo.system.plugin.PluginMetadata; 061 import org.w3c.dom.Document; 062 import org.w3c.dom.Element; 063 import org.w3c.dom.Text; 064 065 /** 066 * Servlet that lets you download a CAR from the repository 067 * 068 * @version $Rev: 549707 $ $Date: 2007-06-22 00:54:05 -0400 (Fri, 22 Jun 2007) $ 069 */ 070 public class GeronimoAsMavenServlet extends HttpServlet { 071 private final static Log log = LogFactory.getLog(GeronimoAsMavenServlet.class); 072 073 protected void doHead(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { 074 handleRequest(httpServletRequest, httpServletResponse, false); 075 } 076 077 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 078 handleRequest(request, response, true); 079 } 080 protected void handleRequest(HttpServletRequest request, HttpServletResponse response, boolean reply) throws ServletException, IOException { 081 String path = request.getPathInfo(); 082 if(path == null) { 083 throw new ServletException("No configId specified for CAR download"); 084 } 085 Kernel kernel = KernelRegistry.getSingleKernel(); 086 if(path.equals("/geronimo-plugins.xml")) { 087 response.setContentType("text/xml"); 088 if(reply) { 089 try { 090 generateConfigFile(request, kernel, response.getWriter()); 091 } catch (Exception e) { 092 throw new ServletException("Unable to generate Geronimo configuration list", e); 093 } 094 } 095 } else if(path.endsWith("/maven-metadata.xml")) { 096 response.setContentType("text/xml"); 097 try { 098 String start = path.substring(0, path.lastIndexOf('/')); 099 if(start.charAt(0) == '/') { 100 start = start.substring(1); 101 } 102 String[] parts = start.split("/"); 103 if(parts.length > 2) { 104 StringBuffer buf = new StringBuffer(); 105 for (int i = 0; i < parts.length-1; i++) { 106 String part = parts[i]; 107 if(i > 0) buf.append('.'); 108 buf.append(part); 109 } 110 generateMavenFile(kernel, response.getWriter(), buf.toString(), parts[parts.length-1], reply); 111 } else { 112 generateMavenFile(kernel, response.getWriter(), parts[0], parts[1], reply); 113 } 114 } catch (Exception e) { 115 throw new ServletException("Unable to generate Geronimo configuration list", e); 116 } 117 } else { 118 if(path.startsWith("/")) { 119 path = path.substring(1); 120 } 121 String configId = parsePath(path, response); 122 if(configId == null) { // we already sent the 404 123 return; 124 } 125 if(!produceDownloadFile(kernel, Artifact.create(configId), response, reply)) { 126 response.sendError(404, "Cannot locate download file "+path); 127 } 128 } 129 } 130 131 private static String parsePath(String path, HttpServletResponse response) throws IOException { 132 String[] parts = path.split("/"); 133 String groupId, artifactId, version, type; 134 if(parts.length < 4) { 135 response.sendError(404, "Unrecognized path form "+path); 136 return null; 137 } else { // e.g. console/MyDatabase/1.0-SNAPSHOT/MyDatabase-1.0-SNAPSHOT.rar 138 groupId = parts[0]; 139 for(int i=4; i<parts.length; i++) { 140 groupId = groupId+"."+parts[i-3]; 141 } 142 artifactId = parts[parts.length-3]; 143 version = parts[parts.length-2]; 144 if(!parts[parts.length-1].startsWith(artifactId+"-"+version)) { 145 response.sendError(404, "Unrecognized path structure "+path); 146 return null; 147 } 148 type = parts[parts.length-1].substring(artifactId.length()+version.length()+2); 149 } 150 return groupId+"/"+artifactId+"/"+version+"/"+type; 151 } 152 153 private boolean produceDownloadFile(Kernel kernel, Artifact configId, HttpServletResponse response, boolean reply) throws IOException { 154 //todo: replace kernel mumbo jumbo with JSR-77 navigation 155 // Step 1: check if it's in a configuration store 156 ConfigurationManager mgr = ConfigurationUtil.getConfigurationManager(kernel); 157 if(mgr.isConfiguration(configId)) { 158 ConfigurationStore store = mgr.getStoreForConfiguration(configId); 159 response.setContentType("application/zip"); 160 if(!reply) { 161 return true; 162 } 163 try { 164 store.exportConfiguration(configId, response.getOutputStream()); 165 return true; 166 } catch (NoSuchConfigException e) { 167 log.error("Inconsistent ConfigurationStore data; ConfigManager claims it has configuration "+configId+" but store claims it doesn't",e); 168 throw new IOException("Unable to write ZIP file; see server log for details"); 169 } 170 } 171 // Step 2: check if it's in a repository 172 Set repos = kernel.listGBeans(new AbstractNameQuery(Repository.class.getName())); 173 for (Iterator it = repos.iterator(); it.hasNext();) { 174 AbstractName name = (AbstractName) it.next(); 175 Repository repo = (Repository) kernel.getProxyManager().createProxy(name, Repository.class); 176 if(repo.contains(configId)) { 177 File path = repo.getLocation(configId); 178 if(!path.exists()) throw new IllegalStateException("Can't find file '"+path.getAbsolutePath()+"' though repository said there's an artifact there!"); 179 response.setContentType("application/zip"); 180 if(!reply) { 181 return true; 182 } 183 InputStream in = new BufferedInputStream(new FileInputStream(path)); 184 response.setContentLength((int)path.length()); 185 OutputStream out = response.getOutputStream(); 186 byte[] buf = new byte[1024]; 187 int count; 188 while((count = in.read(buf)) > -1) { 189 out.write(buf, 0, count); 190 } 191 in.close(); 192 return true; 193 } 194 } 195 // Step 3: wasn't found 196 return false; 197 } 198 199 private void generateConfigFile(HttpServletRequest request, Kernel kernel, PrintWriter out) throws ParserConfigurationException, NoSuchStoreException, TransformerException { 200 ConfigurationManager mgr = ConfigurationUtil.getConfigurationManager(kernel); 201 PluginInstaller installer = getInstaller(kernel); 202 DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory(); 203 factory.setNamespaceAware(true); 204 DocumentBuilder builder = factory.newDocumentBuilder(); 205 Document doc = builder.newDocument(); 206 Element root = doc.createElementNS("http://geronimo.apache.org/xml/ns/plugins-1.1", "geronimo-plugin-list"); 207 root.setAttribute("xmlns", "http://geronimo.apache.org/xml/ns/plugins-1.1"); 208 doc.appendChild(root); 209 List stores = mgr.listStores(); 210 for (int i = 0; i < stores.size(); i++) { 211 AbstractName name = (AbstractName) stores.get(i); 212 List configs = mgr.listConfigurations(name); 213 for (int j = 0; j < configs.size(); j++) { 214 ConfigurationInfo info = (ConfigurationInfo) configs.get(j); 215 PluginMetadata data = installer.getPluginMetadata(info.getConfigID()); 216 Element config = doc.createElement("plugin"); 217 root.appendChild(config); 218 createText(doc, config, "name", data.getName()); 219 createText(doc, config, "module-id", data.getModuleId().toString()); 220 createText(doc, config, "category", "Geronimo Deployments"); 221 createText(doc, config, "description", data.getCategory().equals("Unknown") ? "Automatically generated plugin metadata" : data.getDescription()); 222 if(data.getPluginURL() != null) { 223 createText(doc, config, "url", data.getPluginURL()); 224 } 225 if(data.getAuthor() != null) { 226 createText(doc, config, "author", data.getAuthor()); 227 } 228 for (int k = 0; k < data.getLicenses().length; k++) { 229 PluginMetadata.License license = data.getLicenses()[k]; 230 Element lic = doc.createElement("license"); 231 lic.setAttribute("osi-approved", Boolean.toString(license.isOsiApproved())); 232 createText(doc, lic, license.getName()); 233 config.appendChild(lic); 234 } 235 // Skip hash since the CAR will be re-exported anyway and the file will be different 236 PluginMetadata.geronimoVersions[] versions = data.getGeronimoVersions(); 237 for (int k = 0; k < versions.length; k++) { 238 PluginMetadata.geronimoVersions ver = versions[k]; 239 writeGeronimoVersion(doc, config, ver); 240 } 241 String[] jvmVersions = data.getJvmVersions(); 242 for (int k = 0; k < versions.length; k++) { 243 String ver = jvmVersions[k]; 244 createText(doc, config, "jvm-version", ver); 245 } 246 for (int k = 0; k < data.getPrerequisites().length; k++) { 247 PluginMetadata.Prerequisite prereq = data.getPrerequisites()[k]; 248 writePrerequisite(doc, config, prereq); 249 } 250 for (int k = 0; k < data.getDependencies().length; k++) { 251 String dep = data.getDependencies()[k]; 252 createText(doc, config, "dependency", dep); 253 } 254 for (int k = 0; k < data.getObsoletes().length; k++) { 255 String obs = data.getObsoletes()[k]; 256 createText(doc, config, "obsoletes", obs); 257 } 258 // Skip repositories since we want the download to come from here 259 } 260 } 261 String repo = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+request.getServletPath(); 262 if(!repo.endsWith("/")) repo += "/"; 263 createText(doc, root, "default-repository", repo); 264 TransformerFactory xfactory = XmlUtil.newTransformerFactory(); 265 Transformer xform = xfactory.newTransformer(); 266 xform.setOutputProperty(OutputKeys.INDENT, "yes"); 267 xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 268 xform.transform(new DOMSource(doc), new StreamResult(out)); 269 } 270 271 private PluginInstaller getInstaller(Kernel kernel) { 272 Set names = kernel.listGBeans(new AbstractNameQuery(PluginInstaller.class.getName())); 273 if(names.size() == 0) { 274 return null; 275 } 276 return (PluginInstaller) kernel.getProxyManager().createProxy((AbstractName) names.iterator().next(), PluginInstaller.class); 277 } 278 279 private void generateMavenFile(Kernel kernel, PrintWriter writer, String groupId, String artifactId, boolean reply) throws ParserConfigurationException, TransformerException { 280 ConfigurationManager mgr = ConfigurationUtil.getConfigurationManager(kernel); 281 Artifact[] artifacts = mgr.getArtifactResolver().queryArtifacts(new Artifact(groupId, artifactId, (Version)null, null)); 282 if(!reply) { 283 return; 284 } 285 286 DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory(); 287 DocumentBuilder builder = factory.newDocumentBuilder(); 288 Document doc = builder.newDocument(); 289 Element root = doc.createElement("metadata"); 290 doc.appendChild(root); 291 createText(doc, root, "groupId", groupId); 292 createText(doc, root, "artifactId", artifactId); 293 if(artifacts.length > 0) { 294 createText(doc, root, "version", artifacts[0].getVersion().toString()); 295 } 296 Element versioning = doc.createElement("versioning"); 297 root.appendChild(versioning); 298 Element versions = doc.createElement("versions"); 299 versioning.appendChild(versions); 300 for (int i = 0; i < artifacts.length; i++) { 301 Artifact artifact = artifacts[i]; 302 createText(doc, versions, "version", artifact.getVersion().toString()); 303 } 304 TransformerFactory xfactory = XmlUtil.newTransformerFactory(); 305 Transformer xform = xfactory.newTransformer(); 306 xform.setOutputProperty(OutputKeys.INDENT, "yes"); 307 xform.transform(new DOMSource(doc), new StreamResult(writer)); 308 } 309 310 private void writePrerequisite(Document doc, Element config, PluginMetadata.Prerequisite req) { 311 Element prereq = doc.createElement("prerequisite"); 312 config.appendChild(prereq); 313 createText(doc, prereq, "id", req.getModuleId().toString()); 314 createText(doc, prereq, "resource-type", req.getResourceType()); 315 createText(doc, prereq, "description", req.getDescription()); 316 } 317 318 private void writeGeronimoVersion(Document doc, Element config, PluginMetadata.geronimoVersions ver){ 319 Element ger = doc.createElement("geronimo-versions"); 320 createText(doc, ger, "version", ver.getVersion()); 321 if (ver.getModuleId() != null){ 322 createText(doc, ger, "module-id", ver.getModuleId()); 323 } 324 if (ver.getRepository() != null) { 325 String[] repos = ver.getRepository(); 326 for ( int i=0; i < repos.length; i++ ) { 327 createText(doc, ger, "source-repository", repos[i]); 328 } 329 } 330 if (ver.getPreReqs() != null){ 331 for (int j = 0; j < ver.getPreReqs().length; j++) { 332 PluginMetadata.Prerequisite prereq = ver.getPreReqs()[j]; 333 Element pre = doc.createElement("prerequisite"); 334 createText(doc, pre, "id", prereq.getModuleId().toString()); 335 if(prereq.getResourceType() != null) { 336 createText(doc, pre, "resource-type", prereq.getResourceType()); 337 } 338 if(prereq.getDescription() != null) { 339 createText(doc, pre, "description", prereq.getDescription()); 340 } 341 ger.appendChild(pre); 342 } 343 } 344 } 345 346 private void createText(Document doc, Element parent, String name, String text) { 347 Element child = doc.createElement(name); 348 parent.appendChild(child); 349 Text node = doc.createTextNode(text); 350 child.appendChild(node); 351 } 352 353 private void createText(Document doc, Element parent, String text) { 354 Text node = doc.createTextNode(text); 355 parent.appendChild(node); 356 } 357 }