--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.services.reflect.UpdateLoader\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.services.reflect;\r
+\r
+import org.apache.derby.iapi.services.context.ContextService;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.stream.HeaderPrintWriter;\r
+import org.apache.derby.iapi.util.IdUtil;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.services.locks.ShExLockable;\r
+import org.apache.derby.iapi.services.locks.ShExQual;\r
+import org.apache.derby.iapi.services.locks.LockFactory;\r
+import org.apache.derby.iapi.services.locks.Latch;\r
+import org.apache.derby.iapi.services.locks.C_LockFactory;\r
+import org.apache.derby.iapi.services.loader.ClassFactoryContext;\r
+import org.apache.derby.iapi.services.loader.JarReader;\r
+import org.apache.derby.iapi.services.property.PersistentSet;\r
+\r
+import org.apache.derby.iapi.services.property.PropertyUtil;\r
+import org.apache.derby.iapi.reference.Property;\r
+\r
+import java.io.InputStream;\r
+import java.security.AccessController;\r
+\r
+import org.apache.derby.iapi.reference.MessageId;\r
+import org.apache.derby.iapi.reference.Module;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.iapi.services.locks.CompatibilitySpace;\r
+\r
+/**\r
+ * UpdateLoader implements then functionality of\r
+ * derby.database.classpath. It manages the ClassLoaders\r
+ * (instances of JarLoader) for each installed jar file.\r
+ * Jar files are installed through the sqlj.install_jar procedure.\r
+ * <BR>\r
+ * Each JarLoader delegates any request through standard mechanisms\r
+ * to load a class to this object, which will then ask each jarLoader in order of\r
+ * derby.database.classpath to load the class through an internal api.\r
+ * This means if the third jar in derby.database.classpath tries to load\r
+ * a class, say from the class for a procedure's method making some\r
+ * reference to it, then the request is delegated to UpdateLoader.\r
+ * UpdateLoader will then try to load the class from each of the jars\r
+ * in order of derby.database.classpath using the jar's installed JarLoader.\r
+ */\r
+final class UpdateLoader {\r
+ \r
+ /**\r
+ * List of packages that Derby will not support being loaded\r
+ * from an installed jar file.\r
+ */\r
+ private static final String[] RESTRICTED_PACKAGES = {\r
+ // While loading java. classes is blocked by the standard\r
+ // class loading mechanism, javax. ones are not. However\r
+ // allowing database applications to override jvm classes\r
+ // seems a bad idea.\r
+ "javax.",\r
+ \r
+ // Allowing an application to possible override the engine's\r
+ // own classes also seems dangerous.\r
+ "org.apache.derby.",\r
+ };\r
+\r
+ private JarLoader[] jarList;\r
+ private HeaderPrintWriter vs;\r
+ private final ClassLoader myLoader;\r
+ private boolean initDone;\r
+ private String thisClasspath;\r
+ private final LockFactory lf;\r
+ private final ShExLockable classLoaderLock;\r
+ private int version;\r
+ private boolean normalizeToUpper;\r
+ private DatabaseClasses parent;\r
+ private final CompatibilitySpace compat;\r
+\r
+ private boolean needReload;\r
+ private JarReader jarReader;\r
+\r
+ UpdateLoader(String classpath, DatabaseClasses parent, boolean verbose, boolean normalizeToUpper) \r
+ throws StandardException {\r
+\r
+ this.normalizeToUpper = normalizeToUpper;\r
+ this.parent = parent;\r
+ lf = (LockFactory) Monitor.getServiceModule(parent, Module.LockFactory);\r
+ compat = lf.createCompatibilitySpace(this);\r
+\r
+ if (verbose) {\r
+ vs = Monitor.getStream();\r
+ }\r
+ \r
+ myLoader = getClass().getClassLoader();\r
+\r
+ this.classLoaderLock = new ClassLoaderLock(this);\r
+\r
+ initializeFromClassPath(classpath);\r
+ }\r
+\r
+ private void initializeFromClassPath(String classpath) throws StandardException {\r
+\r
+ final String[][] elements = IdUtil.parseDbClassPath(classpath);\r
+ \r
+ final int jarCount = elements.length;\r
+ jarList = new JarLoader[jarCount];\r
+ \r
+ if (jarCount != 0) {\r
+ // Creating class loaders is a restricted operation\r
+ // so we need to use a privileged block.\r
+ AccessController.doPrivileged\r
+ (new java.security.PrivilegedAction(){\r
+ \r
+ public Object run(){ \r
+ for (int i = 0; i < jarCount; i++) {\r
+ jarList[i] = new JarLoader(UpdateLoader.this, elements[i], vs);\r
+ }\r
+ return null;\r
+ }\r
+ });\r
+ }\r
+ if (vs != null) {\r
+ vs.println(MessageService.getTextMessage(MessageId.CM_CLASS_LOADER_START, classpath));\r
+ }\r
+ \r
+ thisClasspath = classpath;\r
+ initDone = false;\r
+ }\r
+\r
+ /**\r
+ Load the class from the class path. Called by JarLoader\r
+ when it has a request to load a class to fulfill\r
+ the sematics of derby.database.classpath.\r
+ <P>\r
+ Enforces two restrictions:\r
+ <UL>\r
+ <LI> Do not allow classes in certain name spaces to be loaded\r
+ from installed jars, see RESTRICTED_PACKAGES for the list.\r
+ <LI> Referencing Derby's internal classes (those outside the\r
+ public api) from installed is disallowed. This is to stop\r
+ user defined routines bypassing security or taking advantage\r
+ of security holes in Derby. E.g. allowing a routine to\r
+ call a public method in derby would allow such routines\r
+ to call public static methods for system procedures without\r
+ having been granted permission on them, such as setting database\r
+ properties.\r
+ </UL>\r
+\r
+ @exception ClassNotFoundException Class can not be found or\r
+ the installed jar is restricted from loading it.\r
+ */\r
+ Class loadClass(String className, boolean resolve) \r
+ throws ClassNotFoundException {\r
+\r
+ JarLoader jl = null;\r
+\r
+ boolean unlockLoader = false;\r
+ try {\r
+ unlockLoader = lockClassLoader(ShExQual.SH);\r
+\r
+ synchronized (this) {\r
+\r
+ if (needReload) {\r
+ reload();\r
+ }\r
+ \r
+ Class clazz = checkLoaded(className, resolve);\r
+ if (clazz != null)\r
+ return clazz;\r
+ \r
+ // Refuse to load classes from restricted name spaces\r
+ // That is classes in those name spaces can be not\r
+ // loaded from installed jar files.\r
+ for (int i = 0; i < RESTRICTED_PACKAGES.length; i++)\r
+ {\r
+ if (className.startsWith(RESTRICTED_PACKAGES[i]))\r
+ throw new ClassNotFoundException(className);\r
+ }\r
+\r
+ String jvmClassName = className.replace('.', '/').concat(".class");\r
+\r
+ if (!initDone)\r
+ initLoaders();\r
+\r
+ for (int i = 0; i < jarList.length; i++) {\r
+\r
+ jl = jarList[i];\r
+\r
+ Class c = jl.loadClassData(className, jvmClassName, resolve);\r
+ if (c != null) {\r
+ if (vs != null)\r
+ vs.println(MessageService.getTextMessage(MessageId.CM_CLASS_LOAD, className, jl.getJarName()));\r
+\r
+ return c;\r
+ }\r
+ }\r
+ }\r
+\r
+ return null;\r
+\r
+\r
+ } catch (StandardException se) {\r
+ throw new ClassNotFoundException(MessageService.getTextMessage(MessageId.CM_CLASS_LOAD_EXCEPTION, className, jl == null ? null : jl.getJarName(), se));\r
+ } finally {\r
+ if (unlockLoader) {\r
+ lf.unlock(compat, this, classLoaderLock, ShExQual.SH);\r
+ }\r
+ }\r
+ }\r
+\r
+ InputStream getResourceAsStream(String name) {\r
+\r
+ InputStream is = (myLoader == null) ?\r
+ ClassLoader.getSystemResourceAsStream(name) :\r
+ myLoader.getResourceAsStream(name);\r
+\r
+ if (is != null)\r
+ return is;\r
+\r
+ // match behaviour of standard class loaders. \r
+ if (name.endsWith(".class"))\r
+ return null;\r
+\r
+ boolean unlockLoader = false;\r
+ try {\r
+ unlockLoader = lockClassLoader(ShExQual.SH);\r
+\r
+ synchronized (this) {\r
+\r
+ if (needReload) {\r
+ reload(); \r
+ }\r
+\r
+ if (!initDone)\r
+ initLoaders();\r
+\r
+ for (int i = 0; i < jarList.length; i++) {\r
+\r
+ JarLoader jl = jarList[i];\r
+\r
+ is = jl.getStream(name);\r
+ if (is != null) {\r
+ return is;\r
+ }\r
+ }\r
+ }\r
+ return null;\r
+\r
+ } catch (StandardException se) {\r
+ return null;\r
+ } finally {\r
+ if (unlockLoader) {\r
+ lf.unlock(compat, this, classLoaderLock, ShExQual.SH);\r
+ }\r
+ }\r
+ }\r
+\r
+ synchronized void modifyClasspath(String classpath)\r
+ throws StandardException {\r
+\r
+ // lock transaction classloader exclusively\r
+ lockClassLoader(ShExQual.EX);\r
+ version++;\r
+\r
+\r
+ modifyJar(false);\r
+ initializeFromClassPath(classpath);\r
+ }\r
+\r
+\r
+ synchronized void modifyJar(boolean reload) throws StandardException {\r
+\r
+ // lock transaction classloader exclusively\r
+ lockClassLoader(ShExQual.EX);\r
+ version++;\r
+\r
+ if (!initDone)\r
+ return;\r
+ \r
+ // first close the existing jar file opens\r
+ close();\r
+\r
+ if (reload) {\r
+ initializeFromClassPath(thisClasspath);\r
+ }\r
+ }\r
+\r
+ private boolean lockClassLoader(ShExQual qualifier)\r
+ throws StandardException {\r
+\r
+ if (lf == null)\r
+ return false;\r
+\r
+ ClassFactoryContext cfc = (ClassFactoryContext) ContextService.getContextOrNull(ClassFactoryContext.CONTEXT_ID);\r
+\r
+ // This method can be called from outside of the database\r
+ // engine, in which case tc will be null. In that case\r
+ // we lock the class loader only for the duration of\r
+ // the loadClass().\r
+ CompatibilitySpace lockSpace = null;\r
+ \r
+ if (cfc != null) {\r
+ lockSpace = cfc.getLockSpace();\r
+ }\r
+ if (lockSpace == null)\r
+ lockSpace = compat;\r
+\r
+ Object lockGroup = lockSpace.getOwner();\r
+\r
+ lf.lockObject(lockSpace, lockGroup, classLoaderLock, qualifier,\r
+ C_LockFactory.TIMED_WAIT);\r
+\r
+ return (lockGroup == this);\r
+ }\r
+\r
+ Class checkLoaded(String className, boolean resolve) {\r
+\r
+ for (int i = 0; i < jarList.length; i++) {\r
+ Class c = jarList[i].checkLoaded(className, resolve);\r
+ if (c != null)\r
+ return c;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ void close() {\r
+\r
+ for (int i = 0; i < jarList.length; i++) {\r
+ jarList[i].setInvalid();\r
+ }\r
+\r
+ }\r
+\r
+ private void initLoaders() {\r
+\r
+ if (initDone)\r
+ return;\r
+\r
+ for (int i = 0; i < jarList.length; i++) {\r
+ jarList[i].initialize();\r
+ }\r
+ initDone = true;\r
+ }\r
+\r
+ int getClassLoaderVersion() {\r
+ return version;\r
+ }\r
+\r
+ synchronized void needReload() {\r
+ version++;\r
+ needReload = true;\r
+ }\r
+\r
+ private void reload() throws StandardException {\r
+ thisClasspath = getClasspath();\r
+ // first close the existing jar file opens\r
+ close();\r
+ initializeFromClassPath(thisClasspath);\r
+ needReload = false;\r
+ }\r
+\r
+\r
+ private String getClasspath()\r
+ throws StandardException {\r
+\r
+ ClassFactoryContext cfc = (ClassFactoryContext) ContextService.getContextOrNull(ClassFactoryContext.CONTEXT_ID);\r
+\r
+ PersistentSet ps = cfc.getPersistentSet();\r
+ \r
+ String classpath = PropertyUtil.getServiceProperty(ps, Property.DATABASE_CLASSPATH);\r
+\r
+ //\r
+ //In per database mode we must always have a classpath. If we do not\r
+ //yet have one we make one up.\r
+ if (classpath==null)\r
+ classpath="";\r
+\r
+\r
+ return classpath;\r
+ }\r
+\r
+ JarReader getJarReader() {\r
+ if (jarReader == null) {\r
+\r
+ ClassFactoryContext cfc = (ClassFactoryContext) ContextService.getContextOrNull(ClassFactoryContext.CONTEXT_ID);\r
+\r
+ jarReader = cfc.getJarReader(); \r
+ }\r
+ return jarReader;\r
+ }\r
+}\r
+\r
+\r
+class ClassLoaderLock extends ShExLockable {\r
+\r
+ private UpdateLoader myLoader;\r
+\r
+ ClassLoaderLock(UpdateLoader myLoader) {\r
+ this.myLoader = myLoader;\r
+ }\r
+\r
+ public void unlockEvent(Latch lockInfo)\r
+ {\r
+ super.unlockEvent(lockInfo);\r
+\r
+ if (lockInfo.getQualifier().equals(ShExQual.EX)) {\r
+ // how do we tell if we are reverting or not\r
+ myLoader.needReload();\r
+ }\r
+ }\r
+}\r