Package org.activiti.osgi

Source Code of org.activiti.osgi.Extender$BundleScriptEngineResolver

/* 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.activiti.osgi;

import static org.activiti.osgi.Constants.BUNDLE_ACTIVITI_HEADER;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.osgi.HeaderParser.PathElement;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

/**
* @author <a href="gnodet@gmail.com">Guillaume Nodet</a>
*/
public class Extender implements BundleTrackerCustomizer, ServiceTrackerCustomizer {

  private static final Logger LOGGER = Logger.getLogger(Extender.class.getName());
  private static final String META_INF_SERVICES_DIR = "META-INF/services";
  private static final String SCRIPT_ENGINE_SERVICE_FILE = "javax.script.ScriptEngineFactory";

  private static BundleContext context;
  private final BundleTracker bundleTracker;
  private final ServiceTracker engineServiceTracker;
  private long timeout = 5000;
  private Map<Long, List<BundleScriptEngineResolver>> resolvers = new ConcurrentHashMap<Long, List<BundleScriptEngineResolver>>();

  public Extender(BundleContext context) {
    Extender.context = context;
    this.engineServiceTracker = new ServiceTracker(context, ProcessEngine.class.getName(), this);
    this.bundleTracker = new BundleTracker(context, Bundle.RESOLVED | Bundle.STARTING | Bundle.ACTIVE, this);
  }
 
  public static BundleContext getBundleContext() {
    return context;
  }

  public void open() {
    engineServiceTracker.open();
  }

  public void close() {
    engineServiceTracker.close();
  }

  public Object addingService(ServiceReference reference) {
    new Thread() {
      public void run() {
          bundleTracker.open();
      }
    }.start();
    return context.getService(reference);
  }

  public void modifiedService(ServiceReference reference, Object service) {
  }

  public void removedService(ServiceReference reference, Object service) {
    context.ungetService(reference);
    if (engineServiceTracker.size() == 0) {
      bundleTracker.close();
    }
  }

  public Object addingBundle(Bundle bundle, BundleEvent event) {
    if (event == null) {
      // existing bundles first added to the tracker with no event change
      checkInitialBundle(bundle);
    } else {
      bundleChanged(event);
    }

    List<BundleScriptEngineResolver> r = new ArrayList<BundleScriptEngineResolver>();
    registerScriptEngines(bundle, r);
    for (BundleScriptEngineResolver service : r) {
        service.register();
    }
    resolvers.put(bundle.getBundleId(), r);
   
    return bundle;
  }

  public void modifiedBundle(Bundle bundle, BundleEvent event, Object arg2) {
    if (event == null) {
      // cannot think of why we would be interested in a modified bundle with no bundle event
      return;
    }
    bundleChanged(event);

  }

  // don't think we would be interested in removedBundle, as that is
  // called when bundle is removed from the tracker
  public void removedBundle(Bundle bundle, BundleEvent event, Object arg2) {
    List<BundleScriptEngineResolver> r = resolvers.remove(bundle.getBundleId());
    if (r != null) {
      for (BundleScriptEngineResolver service : r) {
        service.unregister();
      }
    }
  }



  /**
   * this method checks the initial bundle that are installed/active before
   * bundle tracker is opened.
   *
   * @param b the bundle to check
   */
  private void checkInitialBundle(Bundle b) {
    // If the bundle is active, check it
    if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING
            || b.getState() == Bundle.ACTIVE) {
      checkBundle(b);
    }
  }

  public void bundleChanged(BundleEvent event) {
    Bundle bundle = event.getBundle();
    if (event.getType() == BundleEvent.RESOLVED) {
      checkBundle(bundle);
    }
  }

  private void checkBundle(Bundle bundle) {
    LOGGER.log(Level.FINE, "Scanning bundle {} for activiti process", bundle.getSymbolicName());
    try {
      List<URL> pathList = new ArrayList<URL>();
      String activitiHeader = (String) bundle.getHeaders().get(BUNDLE_ACTIVITI_HEADER);
      if (activitiHeader == null) {
        activitiHeader = "OSGI-INF/activiti/";
      }
      List<PathElement> paths = HeaderParser.parseHeader(activitiHeader);
      for (PathElement path : paths) {
        String name = path.getName();
        if (name.endsWith("/")) {
          addEntries(bundle, name, "*.*", pathList);
        } else {
          String baseName;
          String filePattern;
          int pos = name.lastIndexOf('/');
          if (pos < 0) {
            baseName = "/";
            filePattern = name;
          } else {
            baseName = name.substring(0, pos + 1);
            filePattern = name.substring(pos + 1);
          }
          if (hasWildcards(filePattern)) {
            addEntries(bundle, baseName, filePattern, pathList);
          } else {
            addEntry(bundle, name, pathList);
          }
        }
      }

      if (!pathList.isEmpty()) {
        LOGGER.log(Level.FINE, "Found activiti process in bundle " + bundle.getSymbolicName()
                + " with paths: " +  pathList);

        ProcessEngine engine = (ProcessEngine) engineServiceTracker.waitForService(timeout);
        if (engine == null) {
          throw new IllegalStateException("Unable to find a ProcessEngine service");
        }

        RepositoryService service = engine.getRepositoryService();
        DeploymentBuilder builder = service.createDeployment();
        builder.name(bundle.getSymbolicName());
        for (URL url : pathList) {
          InputStream is = url.openStream();
          if (is == null) {
              throw new IOException("Error opening url: " + url);
          }
          try {
              builder.addInputStream(getPath(url), is);
          } finally {
              is.close();
          }
        }
        builder.enableDuplicateFiltering();
        builder.deploy();
      } else {
        LOGGER.log(Level.FINE, "No activiti process found in bundle {}", bundle.getSymbolicName());
      }
    } catch (Throwable t) {
      LOGGER.log(Level.SEVERE, "Unable to deploy activiti bundle", t);
    }
  }

  private void addEntry(Bundle bundle, String path, List<URL> pathList) {
    URL override = getOverrideURL(bundle, path);
    if(override == null) {
      URL url = bundle.getEntry(path);
      pathList.add(url);
    } else {
      pathList.add(override);
    }
  }

  @SuppressWarnings({ "rawtypes" })
  private void addEntries(Bundle bundle, String path, String filePattern, List<URL> pathList) {
    Enumeration e = bundle.findEntries(path, filePattern, false);
    while (e != null && e.hasMoreElements()) {
      URL u = (URL) e.nextElement();
      URL override = getOverrideURL(bundle, u, path);
      if(override == null) {
          pathList.add(u);
      } else {
          pathList.add(override);
      }
    }
  }

  private boolean hasWildcards(String path) {
      return path.indexOf("*") >= 0;
  }

  private String getFilePart(URL url) {
      String path = url.getPath();
      int index = path.lastIndexOf('/');
      return path.substring(index + 1);
  }

  private String cachePath(Bundle bundle, String filePath) {
    return Integer.toHexString(bundle.hashCode()) + "/" + filePath;
  }

  private URL getOverrideURLForCachePath(String privatePath){
    URL override = null;
    File privateDataVersion = context.getDataFile(privatePath);
    if (privateDataVersion != null
            && privateDataVersion.exists()) {
      try {
          override = privateDataVersion.toURI().toURL();
      } catch (MalformedURLException e) {
          LOGGER.log(Level.SEVERE, "Unexpected URL Conversion Issue", e);
      }
    }
    return override;
  }

  private URL getOverrideURL(Bundle bundle, String path){
      String cachePath = cachePath(bundle, path);
      return getOverrideURLForCachePath(cachePath);
  }

  private URL getOverrideURL(Bundle bundle, URL path, String basePath){
      String cachePath = cachePath(bundle, basePath + getFilePart(path));
      return getOverrideURLForCachePath(cachePath);
  }

  //remove bundle protocol specific part, so that resource can be accessed by path relative to bundle root
  private static String getPath(URL url) {
      String path = url.toExternalForm();
      return path.replaceAll("bundle://[^/]*/","");
  }

  // script engine part
 
  public static ScriptEngine resolveScriptEngine(String scriptEngineName) throws InvalidSyntaxException {
    ServiceReference[] refs = context.getServiceReferences(ScriptEngineResolver.class.getName(), null);
    if (refs == null) {
      LOGGER.info("No OSGi script engine resolvers available!");
      return null;
    }
   
    LOGGER.fine("Found " + refs.length + " OSGi ScriptEngineResolver services");
   
    for (ServiceReference ref : refs) {
      ScriptEngineResolver resolver = (ScriptEngineResolver) context.getService(ref);
      ScriptEngine engine = resolver.resolveScriptEngine(scriptEngineName);
      context.ungetService(ref);
      LOGGER.fine("OSGi resolver " + resolver + " produced " + scriptEngineName + " engine " + engine);
      if (engine != null) {
        return engine;
      }
    }
    return null;
  }

  @SuppressWarnings({ "rawtypes" })
  protected void registerScriptEngines(Bundle bundle, List<BundleScriptEngineResolver> resolvers) {
    URL configURL = null;
    for (Enumeration e = bundle.findEntries(META_INF_SERVICES_DIR, SCRIPT_ENGINE_SERVICE_FILE, false); e != null && e.hasMoreElements();) {
      configURL = (URL) e.nextElement();
    }
    if (configURL != null) {
      LOGGER.info("Found ScriptEngineFactory in " + bundle.getSymbolicName());
      resolvers.add(new BundleScriptEngineResolver(bundle, configURL));
    }
  }

  public static interface ScriptEngineResolver {
    ScriptEngine resolveScriptEngine(String name);
  }

  protected static class BundleScriptEngineResolver implements ScriptEngineResolver {
    protected final Bundle bundle;
    private ServiceRegistration reg;
    private final URL configFile;

    public BundleScriptEngineResolver(Bundle bundle, URL configFile) {
      this.bundle = bundle;
      this.configFile = configFile;
    }
    public void register() {
      if(bundle.getBundleContext() != null) {
        reg = bundle.getBundleContext().registerService(ScriptEngineResolver.class.getName(),
                                                        this, null);
      }
    }
    public void unregister() {
      if(reg != null) {
        reg.unregister();
      }
    }
   
    @SuppressWarnings({ "rawtypes" })
    public ScriptEngine resolveScriptEngine(String name) {
      try {
        BufferedReader in = new BufferedReader(new InputStreamReader(configFile.openStream()));
        String className = in.readLine();
        in.close();
        Class cls = bundle.loadClass(className);
        if (!ScriptEngineFactory.class.isAssignableFrom(cls)) {
            throw new IllegalStateException("Invalid ScriptEngineFactory: " + cls.getName());
        }
        ScriptEngineFactory factory = (ScriptEngineFactory) cls.newInstance();
        List<String> names = factory.getNames();
        for (String test : names) {
          if (test.equals(name)) {
            ClassLoader old = Thread.currentThread().getContextClassLoader();
            ScriptEngine engine;
            try {
              // JRuby seems to require the correct TCCL to call getScriptEngine
              Thread.currentThread().setContextClassLoader(factory.getClass().getClassLoader());
              engine = factory.getScriptEngine();
            } finally {
              Thread.currentThread().setContextClassLoader(old);
            }
            LOGGER.finest("Resolved ScriptEngineFactory: " + engine + " for expected name: " + name);
            return engine;
          }
        }
        LOGGER.fine("ScriptEngineFactory: " + factory.getEngineName() + " does not match expected name: " + name);
        return null;
      } catch (Exception e) {
        LOGGER.log(Level.WARNING, "Cannot create ScriptEngineFactory: " + e.getClass().getName(), e);
        return null;
      }
    }

    @Override
    public String toString() {
      return "OSGi script engine resolver for " + bundle.getSymbolicName();
    }
  }

}
TOP

Related Classes of org.activiti.osgi.Extender$BundleScriptEngineResolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.