| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.webconsole.internal.misc; |
| |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.StringWriter; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.felix.utils.json.JSONWriter; |
| import org.apache.felix.utils.manifest.Clause; |
| import org.apache.felix.utils.manifest.Parser; |
| import org.apache.felix.webconsole.DefaultVariableResolver; |
| import org.apache.felix.webconsole.SimpleWebConsolePlugin; |
| import org.apache.felix.webconsole.WebConsoleUtil; |
| import org.apache.felix.webconsole.internal.OsgiManagerPlugin; |
| import org.apache.felix.webconsole.internal.Util; |
| import org.osgi.framework.Bundle; |
| |
| |
| /** |
| * LicenseServlet provides the licenses plugin that browses through the bundles, |
| * searching for common license files. |
| * |
| */ |
| public final class LicenseServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin |
| { |
| public static final class Entry { |
| String url; |
| String path; |
| String jar; |
| } |
| |
| // common names (without extension) of the license files. |
| static final String LICENSE_FILES[] = |
| { "README", "DISCLAIMER", "LICENSE", "NOTICE", "DEPENDENCIES" }; |
| |
| static final String LABEL = "licenses"; |
| static final String TITLE = "%licenses.pluginTitle"; |
| static final String CSS[] = { "/res/ui/license.css" }; |
| |
| // templates |
| private final String TEMPLATE; |
| |
| /** |
| * Default constructor |
| */ |
| public LicenseServlet() |
| { |
| super(LABEL, TITLE, CATEGORY_OSGI_MANAGER, CSS); |
| |
| // load templates |
| TEMPLATE = readTemplateFile( "/templates/license.html" ); |
| } |
| |
| /** |
| * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| protected void doGet(HttpServletRequest request, HttpServletResponse response) |
| throws ServletException, IOException |
| { |
| final PathInfo pathInfo = PathInfo.parse( request.getPathInfo() ); |
| if ( pathInfo != null ) |
| { |
| if ( !sendResource( pathInfo, response ) ) |
| { |
| response.sendError( HttpServletResponse.SC_NOT_FOUND, "Cannot send data .." ); |
| } |
| } |
| else |
| { |
| super.doGet( request, response ); |
| } |
| } |
| |
| |
| /** |
| * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| protected void renderContent( HttpServletRequest request, HttpServletResponse res ) throws IOException |
| { |
| Bundle[] bundles = getBundleContext().getBundles(); |
| Util.sort( bundles, request.getLocale() ); |
| |
| // prepare variables |
| DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) ); |
| vars.put( "__data__", getBundleData( bundles, request.getLocale() )); |
| |
| res.getWriter().print(TEMPLATE); |
| } |
| |
| private static final String getBundleData(Bundle[] bundles, Locale locale) throws IOException |
| { |
| final StringWriter json = new StringWriter(); |
| final JSONWriter jw = new JSONWriter(json); |
| jw.array(); |
| |
| for (int i = 0; i < bundles.length; i++) |
| { |
| Bundle bundle = bundles[i]; |
| |
| List files = findResource(bundle, LICENSE_FILES); |
| addLicensesFromHeader(bundle, files); |
| if (!files.isEmpty()) |
| { // has resources |
| jw.object(); |
| jw.key( "bid").value( bundle.getBundleId() ); |
| jw.key( "title").value( Util.getName( bundle, locale ) ); |
| jw.key( "files"); |
| jw.object(); |
| jw.key("__res__"); |
| jw.array(); |
| Iterator iter = files.iterator(); |
| while ( iter.hasNext() ) { |
| jw.object(); |
| Entry entry = (Entry) iter.next(); |
| jw.key("path").value(entry.path); |
| jw.key("url").value(entry.url); |
| if ( entry.jar != null ) |
| { |
| jw.key("jar").value(entry.jar); |
| } |
| jw.endObject(); |
| } |
| jw.endArray(); |
| jw.endObject(); |
| jw.endObject(); |
| } |
| } |
| |
| jw.endArray(); |
| return json.toString(); |
| } |
| |
| |
| private static final String getName( String path ) |
| { |
| return path.substring( path.lastIndexOf( '/' ) + 1 ); |
| } |
| |
| private static final void addLicensesFromHeader(Bundle bundle, List files) |
| { |
| String target = (String) bundle.getHeaders("").get("Bundle-License"); |
| if (target != null) |
| { |
| Clause[] licenses = Parser.parseHeader(target); |
| for (int i = 0; licenses != null && i < licenses.length; i++) |
| { |
| final String name = licenses[i].getName(); |
| if (!"<<EXTERNAL>>".equals(name)) |
| { |
| final String link = licenses[i].getAttribute("link"); |
| final String path; |
| final String url; |
| if (link == null) |
| { |
| path = name; |
| url = getName(name); |
| } |
| else |
| { |
| path = link; |
| url = name; |
| } |
| |
| // skip entry URL is bundle resources, but doesn't exists |
| if (path.indexOf("://") == -1 && null == bundle.getEntry(path)) |
| continue; |
| |
| Entry entry = new Entry(); |
| entry.path = path; |
| entry.url = url; |
| |
| files.add(entry); |
| } |
| } |
| } |
| } |
| |
| private static final List findResource( Bundle bundle, String[] patterns ) throws IOException |
| { |
| final List files = new ArrayList(); |
| |
| for ( int i = 0; i < patterns.length; i++ ) |
| { |
| Enumeration entries = bundle.findEntries( "/", patterns[i] + "*", true ); |
| if ( entries != null ) |
| { |
| while ( entries.hasMoreElements() ) |
| { |
| URL url = ( URL ) entries.nextElement(); |
| Entry entry = new Entry(); |
| entry.path = url.getPath(); |
| entry.url = getName( url.getPath() ) ; |
| files.add(entry); |
| } |
| } |
| } |
| |
| Enumeration entries = bundle.findEntries( "/", "*.jar", true ); |
| if ( entries != null ) |
| { |
| while ( entries.hasMoreElements() ) |
| { |
| URL url = ( URL ) entries.nextElement(); |
| |
| InputStream ins = null; |
| try |
| { |
| ins = url.openStream(); |
| ZipInputStream zin = new ZipInputStream( ins ); |
| for ( ZipEntry zentry = zin.getNextEntry(); zentry != null; zentry = zin.getNextEntry() ) |
| { |
| String name = zentry.getName(); |
| |
| // ignore directory entries |
| if ( name.endsWith( "/" ) ) |
| { |
| continue; |
| } |
| |
| // cut off path and use file name for checking against patterns |
| name = name.substring( name.lastIndexOf( '/' ) + 1 ); |
| for ( int i = 0; i < patterns.length; i++ ) |
| { |
| if ( name.startsWith( patterns[i] ) ) |
| { |
| Entry entry = new Entry(); |
| entry.path = zentry.getName(); |
| entry.url = getName( name ) ; |
| entry.jar = url.getPath(); |
| files.add(entry); |
| } |
| } |
| } |
| } |
| finally |
| { |
| IOUtils.closeQuietly( ins ); |
| } |
| |
| } |
| } |
| |
| return files; |
| } |
| |
| |
| private boolean sendResource( final PathInfo pathInfo, final HttpServletResponse response ) throws IOException |
| { |
| |
| final String name = pathInfo.licenseFile.substring( pathInfo.licenseFile.lastIndexOf( '/' ) + 1 ); |
| boolean isLicense = false; |
| for ( int i = 0; !isLicense && i < LICENSE_FILES.length; i++ ) |
| { |
| isLicense = name.startsWith( LICENSE_FILES[i] ); |
| } |
| |
| final Bundle bundle = getBundleContext().getBundle( pathInfo.bundleId ); |
| if ( bundle == null ) |
| { |
| return false; |
| } |
| |
| // prepare the response |
| WebConsoleUtil.setNoCache( response ); |
| response.setContentType( "text/plain" ); |
| |
| if ( pathInfo.innerJar == null ) |
| { |
| URL resource = bundle.getEntry( pathInfo.licenseFile ); |
| if ( resource == null) |
| { |
| resource = bundle.getResource( pathInfo.licenseFile ); |
| } |
| |
| |
| if ( resource != null ) |
| { |
| final InputStream input = resource.openStream(); |
| try |
| { |
| IOUtils.copy( input, response.getWriter() ); |
| return true; |
| } |
| finally |
| { |
| IOUtils.closeQuietly( input ); |
| } |
| } |
| } |
| else |
| { |
| // license is in a nested JAR |
| final URL zipResource = bundle.getResource( pathInfo.innerJar ); |
| if ( zipResource != null ) |
| { |
| final InputStream input = zipResource.openStream(); |
| ZipInputStream zin = null; |
| try |
| { |
| zin = new ZipInputStream( input ); |
| for ( ZipEntry zentry = zin.getNextEntry(); zentry != null; zentry = zin.getNextEntry() ) |
| { |
| if ( pathInfo.licenseFile.equals( zentry.getName() ) ) |
| { |
| IOUtils.copy( zin, response.getWriter() ); |
| return true; |
| } |
| } |
| } |
| finally |
| { |
| |
| IOUtils.closeQuietly( zin ); |
| IOUtils.closeQuietly( input ); |
| } |
| } |
| } |
| |
| // throw new ServletException("License file:" + url + " not found!"); |
| return false; |
| } |
| |
| // package private for unit testing of the parse method |
| static class PathInfo |
| { |
| final long bundleId; |
| final String innerJar; |
| final String licenseFile; |
| |
| |
| static PathInfo parse( final String pathInfo ) |
| { |
| if ( pathInfo == null || pathInfo.length() == 0 || !pathInfo.startsWith( "/" + LABEL + "/" ) ) |
| { |
| return null; |
| } |
| |
| // cut off label prefix including slashes around the label |
| final String parts = pathInfo.substring( LABEL.length() + 2 ); |
| |
| int slash = parts.indexOf( '/' ); |
| if ( slash <= 0 ) |
| { |
| return null; |
| } |
| |
| final long bundleId; |
| try |
| { |
| bundleId = Long.parseLong( parts.substring( 0, slash ) ); |
| if ( bundleId < 0 ) |
| { |
| return null; |
| } |
| } |
| catch ( NumberFormatException nfe ) |
| { |
| // illegal bundle id |
| return null; |
| } |
| |
| final String innerJar; |
| int jarSep = parts.indexOf( "!/", slash ); |
| if ( jarSep < 0 ) |
| { |
| innerJar = null; |
| } |
| else |
| { |
| innerJar = parts.substring( slash, jarSep ); |
| slash = jarSep + 2; // ignore bang-slash |
| } |
| |
| final String licenseFile = parts.substring( slash ); |
| |
| return new PathInfo( bundleId, innerJar, licenseFile ); |
| } |
| |
| |
| private PathInfo( final long bundleId, final String innerJar, final String licenseFile ) |
| { |
| this.bundleId = bundleId; |
| this.innerJar = innerJar; |
| this.licenseFile = licenseFile; |
| } |
| } |
| } |