/* * Copyright 2012 The Apache Software Foundation. * * Licensed 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.stanbol.commons.web.resources; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; import org.osgi.framework.Bundle; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleListener; import org.osgi.service.component.ComponentContext; /** * Serves resource in META-INF/services of any active bundle */ @Component @Service(Filter.class) @Properties(value = { @Property(name = "pattern", value = ".*"), @Property(name = "service.ranking", intValue = 500) }) public class ResourceServingFilter implements Filter, BundleListener { public static final String RESOURCE_PREFIX = "META-INF/resources"; public static final int RESOURCE_PREFIX_LENGTH = RESOURCE_PREFIX.length(); private Set<Bundle> resourceProvidingBundles; private Map<String, Bundle> path2Bundle; @Activate protected void activate(final ComponentContext context) { resourceProvidingBundles = new HashSet<Bundle>(); path2Bundle = new HashMap<String, Bundle>(); final Bundle[] registeredBundles = context.getBundleContext().getBundles(); for (int i = 0; i < registeredBundles.length; i++) { if (registeredBundles[i].getState() == Bundle.ACTIVE) { registerResources(registeredBundles[i]); } } context.getBundleContext().addBundleListener(this); } @Deactivate protected void deactivate(final ComponentContext context) { context.getBundleContext().removeBundleListener(this); resourceProvidingBundles = null; path2Bundle = null; } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilterHttp((HttpServletRequest) request, (HttpServletResponse) response, chain); } @Override public void destroy() { } private synchronized void registerResources(Bundle bundle) { //TODO maybe bundle.getLastModified() could be used for modification data in http-headers, and for if-modified since negotiation registerResourcesWithPathPrefix(bundle, RESOURCE_PREFIX); } private void registerResourcesWithPathPrefix(Bundle bundle, String prefix) { final Enumeration<String> resourceEnum = bundle.getEntryPaths(prefix); if (resourceEnum != null && resourceEnum.hasMoreElements()) { resourceProvidingBundles.add(bundle); while (resourceEnum.hasMoreElements()) { String resourcePath = resourceEnum.nextElement(); if (resourcePath.endsWith("/")) { registerResourcesWithPathPrefix(bundle, resourcePath); } else { path2Bundle.put(resourcePath.substring(RESOURCE_PREFIX_LENGTH), bundle); } } } } private void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { final String requestPath = request.getRequestURI(); String contextPath = request.getContextPath(); String resourcePatch = requestPath.substring(contextPath.length()); Bundle resourceBundle = path2Bundle.get(resourcePatch); if (resourceBundle != null) { if (request.getMethod().equals("GET") || request.getMethod().equals("HEAD")) { URL url = resourceBundle.getEntry(RESOURCE_PREFIX + resourcePatch); String mediaType = URLConnection.guessContentTypeFromName(url.getFile()); response.setContentType(mediaType); //TODO can we get the length of a resource without //TODO handle caching related headers if (!request.getMethod().equals("HEAD")) { OutputStream os = response.getOutputStream(); byte[] ba = new byte[1024]; InputStream is = url.openStream(); int i = is.read(ba); while (i != -1) { os.write(ba, 0, i); i = is.read(ba); } os.flush(); } } else { //TODO handle OPTIONS response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } } else { chain.doFilter(request, response); } } @Override public void bundleChanged(BundleEvent event) { final Bundle bundle = event.getBundle(); if (event.getType() == BundleEvent.STARTED) { registerResources(bundle); } else { if (resourceProvidingBundles.contains(bundle)) { synchronized (this) { Iterator<Map.Entry<String, Bundle>> entryIter = path2Bundle.entrySet().iterator(); while (entryIter.hasNext()) { Map.Entry<String, Bundle> entry = entryIter.next(); if (entry.getValue().equals(bundle)) { entryIter.remove(); } } } } } } }