| /** |
| * 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 |
| * <p> |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * <p> |
| * 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.winegrower.deployer; |
| |
| import static java.util.Collections.enumeration; |
| import static java.util.Collections.list; |
| import static java.util.Optional.ofNullable; |
| import static java.util.stream.Collectors.toList; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.stream.Collector; |
| import java.util.stream.Stream; |
| import java.util.zip.ZipEntry; |
| |
| import org.apache.winegrower.Ripener; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleEvent; |
| import org.osgi.framework.BundleListener; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Filter; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.wiring.BundleWiring; |
| |
| public class BundleImpl implements Bundle { |
| private final File file; |
| private final ClassLoader loader; |
| private final long id; |
| private final BundleContextImpl context; |
| private final Version version; |
| private final String symbolicName; |
| private final Dictionary<String, String> headers; |
| private final File dataFileBase; |
| private final Collection<String> includedResources; |
| private int state = Bundle.UNINSTALLED; |
| |
| BundleImpl(final Manifest manifest, final File file, final BundleContextImpl context, |
| final Ripener.Configuration configuration, final long id, |
| final Collection<String> includedResources) { |
| this.file = file; |
| this.dataFileBase = new File(configuration.getWorkDir(), |
| file == null ? Long.toString(System.identityHashCode(manifest)) : file.getName()); |
| this.context = context; |
| this.id = id; |
| this.loader = Thread.currentThread().getContextClassLoader(); |
| this.includedResources = includedResources; |
| this.version = ofNullable(manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION)) |
| .map(Version::new) |
| .orElse(Version.emptyVersion); |
| this.symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); |
| this.headers = manifest.getMainAttributes().entrySet().stream() |
| .collect(Collector.of( |
| Hashtable::new, |
| (t, e) -> t.put(Attributes.Name.class.cast(e.getKey()).toString(), e.getValue().toString()), |
| (t1, t2) -> { |
| t1.putAll(t2); |
| return t1; |
| })); |
| } |
| |
| ClassLoader getLoader() { |
| return loader; |
| } |
| |
| private Stream<BundleListener> allBundleListeners() { |
| return context.getRegistry().getBundles().values().stream() |
| .flatMap(it -> BundleContextImpl.class.cast(it.getBundle().getBundleContext()).getBundleListeners().stream()); |
| } |
| |
| void onStart() { |
| start(); |
| final BundleEvent event = new BundleEvent(BundleEvent.STARTED, this); |
| allBundleListeners() |
| .forEach(listener -> listener.bundleChanged(event)); |
| } |
| |
| void onStop() { |
| stop(); |
| final BundleEvent event = new BundleEvent(BundleEvent.STOPPED, this); |
| allBundleListeners().forEach(listener -> listener.bundleChanged(event)); |
| } |
| |
| @Override |
| public int getState() { |
| return state; |
| } |
| |
| @Override |
| public void start(final int options) { |
| state = options; |
| } |
| |
| @Override |
| public void start() { |
| start(Bundle.ACTIVE); |
| } |
| |
| @Override |
| public void stop(final int options) { |
| state = options; |
| } |
| |
| @Override |
| public void stop() { |
| stop(Bundle.UNINSTALLED); |
| } |
| |
| @Override |
| public void update(final InputStream input) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void update() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void uninstall() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public Dictionary<String, String> getHeaders() { |
| return headers; |
| } |
| |
| @Override |
| public long getBundleId() { |
| return id; |
| } |
| |
| @Override |
| public String getLocation() { |
| return includedResources != null || file == null ? "" : file.getAbsolutePath(); |
| } |
| |
| @Override |
| public ServiceReference<?>[] getRegisteredServices() { |
| return context.getServices().getServices().stream() |
| .filter(it -> it.getReference().getBundle() == this) |
| .map(ServiceRegistration::getReference) |
| .toArray(ServiceReference[]::new); |
| } |
| |
| @Override |
| public ServiceReference<?>[] getServicesInUse() { |
| return new ServiceReference[0]; |
| } |
| |
| @Override |
| public boolean hasPermission(final Object permission) { |
| return true; |
| } |
| |
| @Override |
| public URL getResource(final String name) { |
| return loader.getResource(name); |
| } |
| |
| @Override |
| public Dictionary<String, String> getHeaders(final String locale) { |
| return headers; // ignore the locale for now |
| } |
| |
| @Override |
| public String getSymbolicName() { |
| return symbolicName; |
| } |
| |
| @Override |
| public Class<?> loadClass(final String name) throws ClassNotFoundException { |
| return loader.loadClass(name); |
| } |
| |
| @Override |
| public Enumeration<URL> getResources(final String name) throws IOException { |
| return loader.getResources(name); |
| } |
| |
| @Override |
| public Enumeration<String> getEntryPaths(final String path) { |
| if (includedResources != null) { |
| return enumeration(includedResources.stream() |
| .filter(it -> it.startsWith(path)) |
| .collect(toList())); |
| } |
| if (file.isDirectory()) { |
| final Path base = file.toPath().toAbsolutePath(); |
| final Path subPath = new File(file, path == null ? "" : (path.startsWith("/") ? path.substring(1) : path)).toPath(); |
| final Collection<String> paths = new ArrayList<>(); |
| try { |
| Files.walkFileTree(subPath, new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { |
| if (file.toAbsolutePath().toString().startsWith(base.toString())) { |
| paths.add(base.relativize(file).toString()); |
| } |
| return super.visitFile(file, attrs); |
| } |
| }); |
| } catch (final IOException e) { |
| throw new IllegalStateException(e); |
| } |
| return enumeration(paths); |
| } |
| try (final JarFile jar = new JarFile(file)) { |
| return enumeration(list(jar.entries()).stream() |
| .filter(it -> it.getName().startsWith(path)) |
| .map(ZipEntry::getName) |
| .collect(toList())); |
| } catch (final IOException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| |
| @Override |
| public URL getEntry(final String path) { |
| return loader.getResource(path); |
| } |
| |
| @Override |
| public long getLastModified() { |
| return file == null ? -1 : file.lastModified(); |
| } |
| |
| @Override |
| public Enumeration<URL> findEntries(final String path, final String filePattern, final boolean recurse) { |
| final Filter filter = filePattern == null ? |
| null : context.createFilter("(filename=" + filePattern + ")"); |
| final String prefix = path == null ? "" : (path.startsWith("/") ? path.substring(1) : path); |
| |
| if (includedResources != null) { |
| if (!recurse) { |
| return enumeration(includedResources.stream() |
| .filter(it -> doFilterEntry(filter, prefix, it)) |
| .map(loader::getResource) |
| .collect(toList())); |
| } |
| } |
| |
| final File baseFile = new File(file, prefix); |
| final Path base = baseFile.toPath(); |
| final Path filePath = this.file.toPath(); |
| if (file.isDirectory()) { |
| if (!recurse) { |
| return enumeration(ofNullable(baseFile.listFiles()) |
| .map(Stream::of) |
| .orElseGet(Stream::empty) |
| .filter(file -> doFilterEntry(filter, prefix, filePath.relativize(file.toPath()).toString())) |
| .map(f -> { |
| try { |
| return f.getAbsoluteFile().toURI().toURL(); |
| } catch (final MalformedURLException e) { |
| throw new IllegalStateException(e); |
| } |
| }) |
| .collect(toList())); |
| } else { |
| final Collection<URL> files = new ArrayList<>(); |
| try { |
| Files.walkFileTree(base, new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { |
| if (doFilterEntry(filter, prefix, filePath.relativize(file).toString())) { |
| files.add(file.toAbsolutePath().toUri().toURL()); |
| } |
| return super.visitFile(file, attrs); |
| } |
| }); |
| } catch (final IOException e) { |
| throw new IllegalStateException(e); |
| } |
| return enumeration(files); |
| } |
| } else { |
| try (final JarFile jar = new JarFile(file)) { |
| return enumeration(list(jar.entries()).stream().filter(it -> it.getName().startsWith(prefix)) |
| .map(ZipEntry::getName).filter(name -> !name.endsWith("/")) // folders |
| .filter(name -> doFilterEntry(filter, prefix, name)).map(name -> { |
| try { |
| return new URL("jar", null, file.toURI().toURL().toExternalForm() + "!/" + name); |
| } catch (final MalformedURLException e) { |
| throw new IllegalArgumentException(e); |
| } |
| }).collect(toList())); |
| } catch (final IOException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| } |
| |
| private boolean doFilterEntry(final Filter filter, final String prefix, final String name) { |
| final String path = name.replace(File.separatorChar, '/'); |
| if (prefix != null && !path.startsWith(prefix)) { |
| return false; |
| } |
| if (filter == null) { |
| return true; |
| } |
| final Hashtable<String, Object> props = new Hashtable<>(); |
| props.put("filename", path.substring(path.lastIndexOf('/') + 1)); |
| return filter.matches(props); |
| } |
| |
| @Override |
| public BundleContext getBundleContext() { |
| return context; |
| } |
| |
| @Override |
| public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(final int signersType) { |
| return null; |
| } |
| |
| @Override |
| public Version getVersion() { |
| return version; |
| } |
| |
| @Override |
| public <A> A adapt(final Class<A> type) { |
| if (BundleWiring.class == type) { |
| return type.cast(new BundleWiringImpl(this)); |
| } |
| return null; |
| } |
| |
| @Override |
| public File getDataFile(final String filename) { |
| final File file = new File(dataFileBase, filename); |
| file.getParentFile().mkdirs(); |
| return file; |
| } |
| |
| @Override |
| public int compareTo(final Bundle o) { |
| return (int) (id - o.getBundleId()); |
| } |
| |
| @Override |
| public String toString() { |
| return "BundleImpl{file=" + file + ", id=" + id + '}'; |
| } |
| } |