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;
  
  import java.io.IOException;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.Proxy;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Enumeration;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.NoSuchElementException;
  import java.util.Set;
  import java.util.concurrent.Callable;
  
  import org.apache.aries.proxy.InvocationListener;
  import org.apache.aries.proxy.ProxyManager;
  import org.apache.aries.proxy.UnableToProxyException;
  import org.apache.aries.proxy.impl.gen.ProxySubclassGenerator;
  import org.apache.aries.proxy.impl.interfaces.InterfaceProxyGenerator;
  import org.apache.aries.proxy.weaving.WovenProxy;
  import org.osgi.framework.Bundle;
  
  public final class AsmProxyManager extends AbstractProxyManager implements ProxyManager
  {
    static final ClassLoader bootClassLoader = new ClassLoader(Object.class.getClassLoader()) { /* boot class loader */};
  	
    public Object createNewProxy(Bundle clientBundle, Collection<Class<?>> classes, 
        Callable<Object> dispatcher, InvocationListener listener) throws UnableToProxyException
    {
      Object proxyObject = null;
      
      // if we just have interfaces and no classes we default to using
      // the interface proxy because we can't dynamically
      // subclass more than one interface
      // unless we have a class
      // that implements all of them
  
      // loop through the classes checking if they are java interfaces
      // if we find any class that isn't an interface we need to use
      // the subclass proxy
      Set<Class<?>> notInterfaces = new HashSet<Class<?>>();
      Set<Class<?>> interfaces = new HashSet<Class<?>>();
      
      for (Class<?> clazz : classes) {
        if (!clazz.isInterface()) {
          notInterfaces.add(clazz);
        } else {
          interfaces.add(clazz);
        }
      }
  
      // if we just have no classes we default to using
      // the interface proxy because we can't dynamically
      // subclass more than one interface
      // unless we have a class
      // that implements all of them
      if (notInterfaces.isEmpty()) {
        proxyObject = InterfaceProxyGenerator.getProxyInstance(clientBundle, null, interfaces, dispatcher, listener);
      } else {
        // if we need to use the subclass proxy then we need to find
        // the most specific class
        Class<?> classToProxy = getLowestSubclass(notInterfaces);
        if(WovenProxy.class.isAssignableFrom(classToProxy)) {
          
          if(isConcrete(classToProxy) && implementsAll(classToProxy, interfaces)) {
            try {
              Constructor<?> c = classToProxy.getDeclaredConstructor(Callable.class, 
                  InvocationListener.class);
              c.setAccessible(true);
              proxyObject = c.newInstance(dispatcher, listener);
            } catch (Exception e) {
              //We will have to subclass this one, but we should always have a constructor
              //to use
              //TODO log that performance would be improved by using a non-null template
            }
          } else {
            //We need to generate a class that implements the interfaces (if any) and
            //has the classToProxy as a superclass
            if((classToProxy.getModifiers() & Modifier.FINAL) != 0) {
              throw new UnableToProxyException(classToProxy, "The class " + classToProxy
                  + " does not implement all of the interfaces " + interfaces + 
                  " and is final. This means that we cannot create a proxy for both the class and all of the requested interfaces.");
            }
            proxyObject = InterfaceProxyGenerator.getProxyInstance(clientBundle, 
                classToProxy, interfaces, dispatcher, listener);
          }
        } 
        if(proxyObject == null){
          // ARIES-1216 : in some cases, some class can not be visible from the root class classloader.
          // If that's the case, we need to build a custom classloader that has visibility on all those classes
          // If we could generate a proper constructor this would not be necessary, but since we have to rely
          // on the generated serialization constructor to bypass the JVM verifier, we don't have much choice
          ClassLoader classLoader = classToProxy.getClassLoader();
          if (classLoader == null) {
          	classLoader = bootClassLoader;
          }
          boolean allVisible = true;
          for (Class<?> clazz : classes) {
            try {
              if (classLoader.loadClass(clazz.getName()) != clazz) {
                throw new UnableToProxyException(classToProxy, "The requested class " + clazz + " is different from the one seen by by " + classToProxy);
              }
            } catch (ClassNotFoundException e) {
              allVisible = false;
              break;
            }
          }
          if (!allVisible) {
            List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
            for (Class<?> clazz : classes) {
              ClassLoader cl = clazz.getClassLoader();
              if (cl != null && !classLoaders.contains(cl)) {
                classLoaders.add(cl);
              }
            }
            classLoader = new MultiClassLoader(classLoaders);
          }
          proxyObject = ProxySubclassGenerator.newProxySubclassInstance(classToProxy, classLoader, new ProxyHandler(this, dispatcher, listener));
        }
      }
  
      return proxyObject;
    }
  
    private static class MultiClassLoader extends ClassLoader {
      private final List<ClassLoader> parents;
      private MultiClassLoader(List<ClassLoader> parents) {
        this.parents = parents;
      }
  
      @Override
      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        for (ClassLoader cl : parents) {
           try {
             return cl.loadClass(name);
           } catch (ClassNotFoundException e) {
             // Ignore
           }
          }
          try {
            // use bootClassLoader as last fallback
            return bootClassLoader.loadClass(name);
          } catch (ClassNotFoundException e) {
            // Ignore
          }
          throw new ClassNotFoundException(name);
      }
  
      @Override
      public URL getResource(String name) {
        for (ClassLoader cl : parents) {
           URL url = cl.getResource(name);
           if (url != null) {
             return url;
           }
         }
         return null;
      }
  
      @Override
      public Enumeration<URL> getResources(String name) throws IOException {
        final List<Enumeration<URL>> tmp = new ArrayList<Enumeration<URL>>();
        for (ClassLoader cl : parents) {
          tmp.add(cl.getResources(name));
        }
        return new Enumeration<URL>() {
          int index = 0;
          @Override
          public boolean hasMoreElements() {
            return next();
          }
          @Override
          public URL nextElement() {
            if (!next()) {
              throw new NoSuchElementException();
            }
            return tmp.get(index).nextElement();
          }
          private boolean next() {
            while (index < tmp.size()) {
              if (tmp.get(index) != null && tmp.get(index).hasMoreElements()) {
                return true;
              }
              index++;
            }
            return false;
          }
        };
      }
    }
  
    private Class<?> getLowestSubclass(Set<Class<?>> notInterfaces) throws
         UnableToProxyException {
      
      Iterator<Class<?>> it = notInterfaces.iterator();
      
      Class<?> classToProxy = it.next();
      
      while(it.hasNext()) {
        Class<?> potential = it.next();
        if(classToProxy.isAssignableFrom(potential)) {
          //potential can be widened to classToProxy, and is therefore
          //a lower subclass
          classToProxy = potential;
        } else if (!potential.isAssignableFrom(classToProxy)){
          //classToProxy is not a subclass of potential - This is
          //an error, we can't be part of two hierarchies at once!
          throw new UnableToProxyException(classToProxy, "The requested classes "
              + classToProxy + " and " + potential + " are not in the same type hierarchy");
        }
      }
      return classToProxy;
    }
    
    private boolean isConcrete(Class<?> classToProxy) {
      
      return (classToProxy.getModifiers() & Modifier.ABSTRACT) == 0;
    }
  
    private boolean implementsAll(Class<?> classToProxy, Set<Class<?>> interfaces) {
      //If we can't widen to one of the interfaces then we need to do some more work
      for(Class<?> iface : interfaces) {
        if(!iface.isAssignableFrom(classToProxy))
          return false;
      }
      return true;
    }
  
    @Override
    protected boolean isProxyClass(Class<?> clazz)
    {
      return WovenProxy.class.isAssignableFrom(clazz) || ProxySubclassGenerator.isProxySubclass(clazz) || Proxy.isProxyClass(clazz);
    }
  
    @Override
    protected InvocationHandler getInvocationHandler(Object proxy) 
    {
      Class<?> type = proxy.getClass();
      InvocationHandler ih = null;
      
      if (ProxySubclassGenerator.isProxySubclass(type)) {
        ih = ProxySubclassGenerator.getInvocationHandler(proxy);
      } else if (Proxy.isProxyClass(type)) {
        ih = Proxy.getInvocationHandler(proxy);
      }
      
      return ih;
    }
  }