content: /*
   * 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.aries.proxy.impl.weaving;
  
  import static java.lang.String.format;
  
  import java.util.ArrayList;
  import java.util.Dictionary;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.regex.Pattern;
  
  import org.apache.aries.proxy.UnableToProxyException;
  import org.apache.aries.proxy.weaving.WovenProxy;
  import org.apache.aries.proxy.weavinghook.ProxyWeavingController;
  import org.apache.aries.proxy.weavinghook.WeavingHelper;
  import org.objectweb.asm.ClassReader;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.BundleContext;
  import org.osgi.framework.hooks.weaving.WeavingException;
  import org.osgi.framework.hooks.weaving.WeavingHook;
  import org.osgi.framework.hooks.weaving.WovenClass;
  import org.osgi.framework.wiring.BundleWiring;
  import org.osgi.util.tracker.ServiceTracker;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  public final class ProxyWeavingHook implements WeavingHook, WeavingHelper {
  
    public static final String WEAVING_ENABLED_CLASSES = "org.apache.aries.proxy.weaving.enabled";
    public static final String WEAVING_DISABLED_CLASSES = "org.apache.aries.proxy.weaving.disabled";
  
    public static final String WEAVING_ENABLED_CLASSES_DEFAULT = "*";
    public static final String WEAVING_DISABLED_CLASSES_DEFAULT = "org.objectweb.asm.*,org.slf4j.*,org.apache.log4j.*,javax.*,ch.qos.logback.*";
  
    private static final Logger LOGGER = LoggerFactory.getLogger(ProxyWeavingHook.class);
    /** An import of the WovenProxy package */
    private static final String IMPORT_A = "org.apache.aries.proxy.weaving";
    /** 
     * An import for the InvocationListener class that we will need.
     * This should automatically wire to the right thing because of the uses clause
     * on the impl.weaving package
     */
    private static final String IMPORT_B = "org.apache.aries.proxy";
  
    private final List<Pattern> enabled;
    private final List<Pattern> disabled;
    @SuppressWarnings("rawtypes")
    private final ServiceTracker controllers;
  
    @SuppressWarnings({
   "unchecked", "rawtypes"
  })
  public ProxyWeavingHook(BundleContext context) {
      String enabledProp = context != null ? context.getProperty(WEAVING_ENABLED_CLASSES) : null;
      enabled = parseMatchers(enabledProp, WEAVING_ENABLED_CLASSES_DEFAULT);
      disabled = parseMatchers(context != null ? context.getProperty(WEAVING_DISABLED_CLASSES) : null, WEAVING_DISABLED_CLASSES_DEFAULT);
      controllers = new ServiceTracker(context, ProxyWeavingController.class.getName(), null);
      controllers.open();
      
      if (!"none".equals(enabledProp)) {
          Dictionary<String,String> props = new Hashtable<String,String>();
          // SubsystemResource.java also uses this constant. 
          //   While it could be turned into a static final constant, note that this
          //   is also a non-standard workaround in the absence of a solution in the spec. 
          // See the associated OSGi spec bug. 
          props.put("osgi.woven.packages", "org.apache.aries.proxy.weaving,org.apache.aries.proxy");
          context.registerService("org.osgi.framework.hooks.weaving.WeavingHook", this, props);
      }
    }
  
    public final void weave(WovenClass wovenClass) {
      BundleWiring bw = wovenClass.getBundleWiring();
      
      if (bw != null) {
          Bundle b = bw.getBundle();
          
          if(b.getBundleId() == 0 || 
              b.getSymbolicName().startsWith("org.apache.aries.proxy") ||
              b.getSymbolicName().startsWith("org.apache.aries.util")) {
            return;
          }
      }
  
      if (!isEnabled(wovenClass.getClassName()) || isDisabled(wovenClass.getClassName())) {
          return;
      }
  
      if (shouldWeave(wovenClass)) {
        byte[] bytes = null;
        
        try {
          bytes = WovenProxyGenerator.getWovenProxy(wovenClass.getBytes(),
                  wovenClass.getBundleWiring().getClassLoader());
          
        } catch (Exception e) {
          if(e instanceof RuntimeException && 
              e.getCause() instanceof UnableToProxyException){
            //This is a weaving failure that should be logged, but the class
            //can still be loaded
            LOGGER.trace(String.format("The class %s cannot be woven, it may not be possible for the runtime to proxy this class.",
                                       wovenClass.getClassName()), e);
          } else {
            throw weavingException(wovenClass, e);
          }
        }
        
        if(bytes != null && bytes.length != 0) {
          wovenClass.setBytes(bytes);
          List<String> imports = wovenClass.getDynamicImports();
          imports.add(IMPORT_A);
          imports.add(IMPORT_B);
        }
      }
    }
  
  
      private List<Pattern> parseMatchers(String matchers, String def) {
          String[] strings = (matchers != null ? matchers : def).split(",");
          List<Pattern> patterns = new ArrayList<Pattern>();
          for (String str : strings) {
              str = str.trim();
              if (str.length() != 0) {
                  str = str.replaceAll("\\.", "\\\\.");
                  str = str.replaceAll("\\*", ".*");
                  Pattern p = Pattern.compile(str);
                  patterns.add(p);
              }
          }
          return patterns;
      }
  
      boolean isEnabled(String className) {
          return matches(enabled, className);
      }
  
      boolean isDisabled(String className) {
          return matches(disabled, className);
      }
  
      private boolean matches(List<Pattern> patterns, String className) {
          for (Pattern p : patterns) {
              if (p.matcher(className).matches()) {
                  return true;
              }
          }
          return false;
      }
      
      public boolean isWoven(Class<?> clazz)
      {
        return WovenProxy.class.isAssignableFrom(clazz);
      }
  
      public boolean isSuperClassWoven(WovenClass wovenClass)
      {
        ClassReader cReader = new ClassReader(wovenClass.getBytes());
        try {
            Class<?> superClass = Class.forName(cReader.getSuperName().replace('/', '.'), false,
                    wovenClass.getBundleWiring().getClassLoader());
            return WovenProxy.class.isAssignableFrom(superClass);
        } catch (ClassNotFoundException e) {
            throw weavingException(wovenClass, e);
        }
      }
      
    private boolean shouldWeave(WovenClass wovenClass)
    {
      // assume we weave
      boolean result = true;
      Object[] cs = controllers.getServices();
      
      // if we have at least 1 weaving controller
      if (cs != null && cs.length > 0) {
        // first of all set to false.
        result = false;
        for (Object obj : cs) {
          ProxyWeavingController c = (ProxyWeavingController) obj;
          if (c.shouldWeave(wovenClass, this)) {
            // exit as soon as we get told to weave, otherwise keep going.
            return true;
          }
        }
      } 
      
      return result;
    }
  
    private WeavingException weavingException(WovenClass wovenClass, Exception e) {
      String msg = format("There was a serious error trying to weave the class %s. See the associated exception for more information.",
                              wovenClass.getClassName());
      // This is a failure that should stop the class loading!
      LOGGER.error(msg, e);
      return new WeavingException(msg, e);
    }
  }