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    }