001/* 002 * (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu 018 */ 019package org.nuxeo.ecm.webengine.loader.store; 020 021import java.io.IOException; 022import java.net.URL; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.Enumeration; 026import java.util.LinkedHashSet; 027import java.util.List; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032/** 033 * The class loader allows modifying the stores (adding/removing). Mutable operations are thread safe. 034 * 035 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 036 */ 037public class ResourceStoreClassLoader extends ClassLoader implements Cloneable { 038 039 private final Log log = LogFactory.getLog(ResourceStoreClassLoader.class); 040 041 private volatile ResourceStore[] stores; 042 043 private final LinkedHashSet<ResourceStore> cp; // class path 044 045 public ResourceStoreClassLoader(final ClassLoader pParent) { 046 this(pParent, new LinkedHashSet<>()); 047 } 048 049 protected ResourceStoreClassLoader(final ClassLoader pParent, LinkedHashSet<ResourceStore> cp) { 050 super(pParent); 051 this.cp = cp; 052 if (!cp.isEmpty()) { 053 stores = cp.toArray(new ResourceStore[0]); 054 } 055 } 056 057 public synchronized boolean addStore(ResourceStore store) { 058 if (cp.add(store)) { 059 stores = cp.toArray(new ResourceStore[0]); 060 return true; 061 } 062 return false; 063 } 064 065 public synchronized boolean removeStore(ResourceStore store) { 066 if (cp.remove(store)) { 067 stores = cp.toArray(new ResourceStore[0]); 068 return true; 069 } 070 return false; 071 } 072 073 @Override 074 public synchronized ResourceStoreClassLoader clone() { 075 return new ResourceStoreClassLoader(getParent(), new LinkedHashSet<>(cp)); 076 } 077 078 public ResourceStore[] getStores() { 079 return stores; 080 } 081 082 protected Class<?> fastFindClass(final String name) { 083 ResourceStore[] _stores = stores; // use a local variable 084 if (_stores != null) { 085 for (final ResourceStore store : _stores) { 086 final byte[] clazzBytes = store.getBytes(convertClassToResourcePath(name)); 087 if (clazzBytes != null) { 088 if (log.isTraceEnabled()) { 089 log.trace(getId() + " found class: " + name + " (" + clazzBytes.length + " bytes)"); 090 } 091 doDefinePackage(name); 092 return defineClass(name, clazzBytes, 0, clazzBytes.length); 093 } 094 } 095 } 096 return null; 097 } 098 099 /** 100 * Without this method getPackage() returns null 101 */ 102 protected void doDefinePackage(String name) { 103 int i = name.lastIndexOf('.'); 104 if (i > -1) { 105 String pkgname = name.substring(0, i); 106 Package pkg = getPackage(pkgname); 107 if (pkg == null) { 108 definePackage(pkgname, null, null, null, null, null, null, null); 109 } 110 } 111 } 112 113 @Override 114 protected URL findResource(String name) { 115 ResourceStore[] _stores = stores; // use a local variable 116 if (_stores != null) { 117 for (final ResourceStore store : _stores) { 118 final URL url = store.getURL(name); 119 if (url != null) { 120 if (log.isTraceEnabled()) { 121 log.trace(getId() + " found resource: " + name); 122 } 123 return url; 124 } 125 } 126 } 127 return null; 128 } 129 130 @Override 131 protected Enumeration<URL> findResources(String name) throws IOException { 132 ResourceStore[] _stores = stores; // use a local variable 133 if (_stores != null) { 134 List<URL> result = new ArrayList<>(); 135 for (final ResourceStore store : _stores) { 136 final URL url = store.getURL(name); 137 if (url != null) { 138 if (log.isTraceEnabled()) { 139 log.trace(getId() + " found resource: " + name); 140 } 141 result.add(url); 142 } 143 } 144 return Collections.enumeration(result); 145 } 146 return null; 147 } 148 149 @Override 150 public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 151 // log.debug(getId() + " looking for: " + name); 152 Class<?> clazz = findLoadedClass(name); 153 154 if (clazz == null) { 155 clazz = fastFindClass(name); 156 157 if (clazz == null) { 158 159 final ClassLoader parent = getParent(); 160 if (parent != null) { 161 clazz = parent.loadClass(name); 162 // log.debug(getId() + " delegating loading to parent: " + name); 163 } else { 164 throw new ClassNotFoundException(name); 165 } 166 167 } else { 168 if (log.isDebugEnabled()) { 169 log.debug(getId() + " loaded from store: " + name); 170 } 171 } 172 } 173 174 if (resolve) { 175 resolveClass(clazz); 176 } 177 178 return clazz; 179 } 180 181 @Override 182 protected Class<?> findClass(final String name) throws ClassNotFoundException { 183 final Class<?> clazz = fastFindClass(name); 184 if (clazz == null) { 185 throw new ClassNotFoundException(name); 186 } 187 return clazz; 188 } 189 190 @Override 191 public Enumeration<URL> getResources(String name) throws IOException { 192 Enumeration<URL> urls = findResources(name); 193 if (urls == null) { 194 final ClassLoader parent = getParent(); 195 if (parent != null) { 196 urls = parent.getResources(name); 197 } 198 } 199 return urls; 200 } 201 202 @Override 203 public URL getResource(String name) { 204 URL url = findResource(name); 205 if (url == null) { 206 final ClassLoader parent = getParent(); 207 if (parent != null) { 208 url = parent.getResource(name); 209 } 210 } 211 return url; 212 } 213 214 protected String getId() { 215 return "" + this + "[" + this.getClass().getClassLoader() + "]"; 216 } 217 218 /** 219 * org.my.Class -> org/my/Class.class 220 */ 221 public static String convertClassToResourcePath(final String pName) { 222 return pName.replace('.', '/') + ".class"; 223 } 224 225}