/*
* Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.quercus.module;
import com.caucho.config.ConfigException;
import com.caucho.quercus.QuercusRuntimeException;
import com.caucho.quercus.env.*;
import com.caucho.quercus.expr.ExprFactory;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.marshal.Marshal;
import com.caucho.quercus.marshal.MarshalFactory;
import com.caucho.quercus.program.ClassDef;
import com.caucho.quercus.program.InterpretedClassDef;
import com.caucho.quercus.program.JavaClassDef;
import com.caucho.quercus.program.JavaArrayClassDef;
import com.caucho.util.L10N;
import com.caucho.vfs.*;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.tdr.bootstrap.OSGiJarClassLoader;
/**
* Class-loader specific context for loaded PHP.
*/
public class ModuleContext
{
private static L10N L = new L10N(ModuleContext.class);
private static final Logger log
= Logger.getLogger(ModuleContext.class.getName());
private ClassLoader _loader;
private ModuleContext _parent;
private HashSet<URL> _serviceClassUrls = new HashSet<URL>();
private HashSet<URL> _serviceModuleUrls = new HashSet<URL>();
private HashMap<String, ModuleInfo> _moduleInfoMap
= new HashMap<String, ModuleInfo>();
private HashSet<String> _extensionSet
= new HashSet<String>();
private ClassDef _stdClassDef;
private QuercusClass _stdClass;
private HashMap<String, ClassDef> _staticClasses
= new HashMap<String, ClassDef>();
private HashMap<String, JavaClassDef> _javaClassWrappers
= new HashMap<String, JavaClassDef>();
private HashMap<String, HashSet<String>> _extensionClasses
= new HashMap<String, HashSet<String>>();
protected MarshalFactory _marshalFactory;
protected ExprFactory _exprFactory;
/**
* Constructor.
*/
private ModuleContext(ClassLoader loader)
{
_loader = loader;
_marshalFactory = new MarshalFactory(this);
_exprFactory = new ExprFactory();
_stdClassDef = new InterpretedClassDef("stdClass", null, new String[0]);
_stdClass = new QuercusClass(this, _stdClassDef, null);
_staticClasses.put(_stdClass.getName(), _stdClassDef);
}
/**
* Constructor.
*/
public ModuleContext(ModuleContext parent, ClassLoader loader)
{
this(loader);
_parent = parent;
if (parent != null) {
_serviceClassUrls.addAll(parent._serviceClassUrls);
_serviceModuleUrls.addAll(parent._serviceModuleUrls);
_moduleInfoMap.putAll(parent._moduleInfoMap);
_extensionSet.addAll(parent._extensionSet);
_staticClasses.putAll(parent._staticClasses);
_javaClassWrappers.putAll(parent._javaClassWrappers);
_extensionClasses.putAll(parent._extensionClasses);
}
}
public static ModuleContext getLocalContext(ClassLoader loader)
{
throw new UnsupportedOperationException();
/*
ModuleContext context = _localModuleContext.getLevel(loader);
if (context == null) {
context = new ModuleContext(loader);
_localModuleContext.set(context, loader);
}
return context;
*/
}
/**
* Tests if the URL has already been loaded for the context classes
*/
public boolean hasServiceClass(URL url)
{
return _serviceClassUrls.contains(url);
}
/**
* Adds a URL for the context classes
*/
public void addServiceClass(URL url)
{
_serviceClassUrls.add(url);
}
/**
* Tests if the URL has already been loaded for the context module
*/
public boolean hasServiceModule(URL url)
{
return _serviceModuleUrls.contains(url);
}
/**
* Adds a URL for the context module
*/
public void addServiceModule(URL url)
{
_serviceModuleUrls.add(url);
}
/**
* Adds module info.
*/
public ModuleInfo addModule(String name, QuercusModule module)
throws ConfigException
{
synchronized (this) {
ModuleInfo info = _moduleInfoMap.get(name);
if (info == null) {
info = new ModuleInfo(this, name, module);
_moduleInfoMap.put(name, info);
}
return info;
}
}
public JavaClassDef addClass(String name, Class<?> type,
String extension, Class<?> javaClassDefClass)
throws NoSuchMethodException,
InvocationTargetException,
IllegalAccessException,
InstantiationException
{
synchronized (_javaClassWrappers) {
JavaClassDef def = _javaClassWrappers.get(name);
if (def == null) {
if (log.isLoggable(Level.FINEST)) {
if (extension == null)
log.finest(L.l("PHP loading class {0} with type {1}",
name,
type.getName()));
else
log.finest(L.l(
"PHP loading class {0} with type {1} providing extension {2}",
name,
type.getName(),
extension));
}
if (javaClassDefClass != null) {
Constructor<?> constructor
= javaClassDefClass.getConstructor(ModuleContext.class,
String.class,
Class.class);
def = (JavaClassDef) constructor.newInstance(this, name, type);
}
else {
def = JavaClassDef.create(this, name, type);
if (def == null)
def = createDefaultJavaClassDef(name, type, extension);
}
def.setPhpClass(true);
_javaClassWrappers.put(name, def);
// _lowerJavaClassWrappers.put(name.toLowerCase(Locale.ENGLISH), def);
_staticClasses.put(name, def);
// _lowerStaticClasses.put(name.toLowerCase(Locale.ENGLISH), def);
// def.introspect();
if (extension != null)
_extensionSet.add(extension);
}
return def;
}
}
/**
* Gets or creates a JavaClassDef for the given class name.
*/
public JavaClassDef getJavaClassDefinition(Class<?> type, String className)
{
JavaClassDef def;
synchronized (_javaClassWrappers) {
def = _javaClassWrappers.get(className);
if (def != null && def.getType() == type)
return def;
def = JavaClassDef.create(this, className, type);
if (def == null)
def = createDefaultJavaClassDef(className, type);
_javaClassWrappers.put(className, def);
_javaClassWrappers.put(type.getName(), def);
}
return def;
}
/**
* Adds a java class
*/
public JavaClassDef getJavaClassDefinition(String className)
{
// Note, this method must not trigger an introspection to avoid
// any race conditions. It is only responsible for creating the
// wrapper around the class, i.e. it's a leaf node, not a recursive not
synchronized (_javaClassWrappers) {
JavaClassDef def = _javaClassWrappers.get(className);
// boolean isClassActive = true;
// ClassLoader cl = def.getType().getClassLoader();
// if ( cl instanceof OSGiJarClassLoader ) {
// isClassActive = ((OSGiJarClassLoader) cl).isEnabled();
// }
if (def != null) // && isClassActive)
return def;
try {
Class<?> type;
try {
type = Class.forName(className, false, _loader);
}
catch (ClassNotFoundException e) {
throw new ClassNotFoundException(L.l("'{0}' is not a known Java class: {1}",
className,
e.toString()), e);
} catch (NoClassDefFoundError e) {
throw new ClassNotFoundException(L.l("'{0}' cannot be as a Java class: {1}",
className,
e.toString()), e);
}
def = JavaClassDef.create(this, className, type);
if (def == null)
def = createDefaultJavaClassDef(className, type);
_javaClassWrappers.put(className, def);
_javaClassWrappers.put(type.getName(), def);
// def.introspect();
return def;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new QuercusRuntimeException(e);
}
}
}
/**
* Returns a javaClassDef for the given class or null if there is not one.
*/
public JavaClassDef getJavaClassDefinition(Class javaClass)
{
synchronized (_javaClassWrappers) {
return _javaClassWrappers.get(javaClass.getName());
}
}
protected JavaClassDef createDefaultJavaClassDef(String className,
Class type)
{
if (type.isArray())
return new JavaArrayClassDef(this, className, type);
else
return new JavaClassDef(this, className, type);
}
protected JavaClassDef createDefaultJavaClassDef(String className,
Class type,
String extension)
{
if (type.isArray())
return new JavaArrayClassDef(this, className, type, extension);
else
return new JavaClassDef(this, className, type, extension);
}
/**
* Finds the java class wrapper.
*/
/*
public ClassDef findJavaClassWrapper(String name)
{
synchronized (_javaClassWrappers) {
ClassDef def = _javaClassWrappers.get(name);
if (def != null)
return def;
return _lowerJavaClassWrappers.get(name.toLowerCase(Locale.ENGLISH));
}
}
*/
public MarshalFactory getMarshalFactory()
{
return _marshalFactory;
}
public ExprFactory getExprFactory()
{
return _exprFactory;
}
public Marshal createMarshal(Class type,
boolean isNotNull,
boolean isNullAsFalse)
{
return getMarshalFactory().create(type, isNotNull, isNullAsFalse);
}
/**
* Returns an array of the defined functions.
*/
/*
public ArrayValue getDefinedFunctions()
{
ArrayValue internal = new ArrayValueImpl();
synchronized (_staticFunctions) {
for (String name : _staticFunctions.keySet()) {
internal.put(name);
}
}
return internal;
}
*/
/**
* Returns the stdClass definition.
*/
public QuercusClass getStdClass()
{
return _stdClass;
}
/**
* Returns the class with the given name.
*/
/*
public ClassDef findClass(String name)
{
synchronized (_staticClasses) {
ClassDef def = _staticClasses.get(name);
if (def == null)
def = _lowerStaticClasses.get(name.toLowerCase(Locale.ENGLISH));
return def;
}
}
*/
/**
* Returns the class maps.
*/
public HashMap<String, ClassDef> getClassMap()
{
synchronized (_staticClasses) {
return new HashMap<String,ClassDef>(_staticClasses);
}
}
/**
* Returns the class maps.
*/
public HashMap<String, JavaClassDef> getWrapperMap()
{
synchronized (_javaClassWrappers) {
return new HashMap<String,JavaClassDef>(_javaClassWrappers);
}
}
/**
* Returns the module with the given name.
*/
public QuercusModule findModule(String name)
{
ModuleInfo info = _moduleInfoMap.get(name);
if (info != null)
return info.getModule();
else
return null;
}
/**
* Returns true if an extension is loaded.
*/
public boolean isExtensionLoaded(String name)
{
return _extensionSet.contains(name);
}
/**
* Returns true if an extension is loaded.
*/
public HashSet<String> getLoadedExtensions()
{
return _extensionSet;
}
/*
* Adds a class to the extension's list of classes.
*/
public void addExtensionClass(String ext, String clsName)
{
HashSet<String> list = _extensionClasses.get(ext);
if (list == null) {
list = new HashSet<String>();
_extensionClasses.put(ext, list);
}
list.add(clsName);
}
/*
* Returns the list of the classes that are part of this extension.
*/
public HashSet<String> getExtensionClasses(String ext)
{
return _extensionClasses.get(ext);
}
/**
* Creates a static function.
*/
public StaticFunction createStaticFunction(QuercusModule module,
Method method)
{
return new StaticFunction(this, module, method);
}
public void init()
{
initStaticFunctions();
initStaticClassServices();
//initStaticClasses();
}
/**
* Scans the classpath for META-INF/services/com.caucho.quercus.QuercusModule
*/
private void initStaticFunctions()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
setContextClassLoader(_loader);
String quercusModule
= "META-INF/services/com.caucho.quercus.QuercusModule";
Enumeration<URL> urls = _loader.getResources(quercusModule);
HashSet<URL> urlSet = new HashSet<URL>();
// gets rid of duplicate entries found by different classloaders
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (! hasServiceModule(url)) {
addServiceModule(url);
urlSet.add(url);
}
}
for (URL url : urlSet) {
InputStream is = null;
ReadStream rs = null;
try {
is = url.openStream();
rs = new ReadStream(new VfsStream(is, null));
parseServicesModule(rs);
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
} finally {
if (rs != null)
rs.close();
if (is != null)
is.close();
}
}
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
} finally {
setContextClassLoader(oldLoader);
}
}
/**
* Parses the services file, looking for PHP services.
*/
private void parseServicesModule(ReadStream in)
throws IOException, ClassNotFoundException,
IllegalAccessException, InstantiationException
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String line;
while ((line = in.readLine()) != null) {
int p = line.indexOf('#');
if (p >= 0)
line = line.substring(0, p);
line = line.trim();
if (line.length() > 0) {
String className = line;
try {
Class<?> cl;
try {
cl = Class.forName(className, false, loader);
}
catch (ClassNotFoundException e) {
throw new ClassNotFoundException(L.l(
"'{0}' not valid {1}", className, e.toString()));
}
introspectPhpModuleClass(cl);
} catch (Throwable e) {
log.fine("Failed loading " + className + "\n" + e.toString());
log.log(Level.FINE, e.toString(), e);
}
}
}
}
/**
* Encapsulate setContextClassLoader for contexts where the
* security manager is set.
*/
protected void setContextClassLoader(ClassLoader loader)
{
Thread thread = Thread.currentThread();
ClassLoader currentLoader = thread.getContextClassLoader();
// to avoid security manager in GoogleAppEngine, skip the setting
// if the loader is the current loader
if (loader != currentLoader)
thread.setContextClassLoader(loader);
}
/**
* Returns the configured modules
*/
public ArrayList<ModuleInfo> getModules()
{
synchronized (_moduleInfoMap) {
return new ArrayList<ModuleInfo>(_moduleInfoMap.values());
}
}
/**
* Introspects the module class for functions.
*
* @param cl the class to introspect.
*/
private void introspectPhpModuleClass(Class<?> cl)
throws IllegalAccessException, InstantiationException, ConfigException
{
synchronized (_moduleInfoMap) {
if (_moduleInfoMap.get(cl.getName()) != null)
return;
log.finest(getClass().getSimpleName()
+ " loading module "
+ cl.getName());
QuercusModule module = (QuercusModule) cl.newInstance();
ModuleInfo info = addModule(cl.getName(), module);
/*
_modules.put(cl.getName(), info);
if (info.getModule() instanceof ModuleStartupListener)
_moduleStartupListeners.add((ModuleStartupListener)info.getModule());
for (String ext : info.getLoadedExtensions())
_extensionSet.add(ext);
Map<String, Value> map = info.getConstMap();
if (map != null)
_constMap.putAll(map);
_iniDefinitions.addAll(info.getIniDefinitions());
synchronized (_staticFunctionMap) {
for (Map.Entry<String, AbstractFunction> entry
: info.getFunctions().entrySet()) {
String funName = entry.getKey();
AbstractFunction fun = entry.getValue();
_staticFunctionMap.put(funName, fun);
// _lowerFunMap.put(funName.toLowerCase(Locale.ENGLISH), fun);
int id = getFunctionId(funName);
_functionMap[id] = fun;
}
}
*/
}
}
/**
* Scans the classpath for META-INF/services/com.caucho.quercus.QuercusClass
*/
private void initStaticClassServices()
{
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
try {
String quercusModule
= "META-INF/services/com.caucho.quercus.QuercusClass";
Enumeration<URL> urls = loader.getResources(quercusModule);
HashSet<URL> urlSet = new HashSet<URL>();
// gets rid of duplicate entries found by different classloaders
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (! hasServiceClass(url)) {
addServiceClass(url);
urlSet.add(url);
}
}
for (URL url : urlSet) {
InputStream is = null;
ReadStream rs = null;
try {
is = url.openStream();
rs = new ReadStream(new VfsStream(is, null));
parseClassServicesModule(rs);
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
} finally {
if (rs != null)
rs.close();
if (is != null)
is.close();
}
}
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
}
/**
* Parses the services file, looking for PHP services.
*/
private void parseClassServicesModule(ReadStream in)
throws IOException, ClassNotFoundException,
IllegalAccessException, InstantiationException,
ConfigException, NoSuchMethodException, InvocationTargetException
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String line;
while ((line = in.readLine()) != null) {
int p = line.indexOf('#');
if (p >= 0)
line = line.substring(0, p);
line = line.trim();
if (line.length() == 0)
continue;
String[] args = line.split(" ");
String className = args[0];
Class cl;
try {
cl = Class.forName(className, false, loader);
String phpClassName = null;
String extension = null;
String definedBy = null;
for (int i = 1; i < args.length; i++) {
if ("as".equals(args[i])) {
i++;
if (i >= args.length)
throw new IOException(
L.l(
"expecting Quercus class name after '{0}' "
+ "in definition for class {1}",
"as",
className));
phpClassName = args[i];
}
else if ("provides".equals(args[i])) {
i++;
if (i >= args.length)
throw new IOException(
L.l(
"expecting name of extension after '{0}' "
+ "in definition for class {1}",
"extension",
className));
extension = args[i];
}
else if ("definedBy".equals(args[i])) {
i++;
if (i >= args.length)
throw new IOException(L.l(
"expecting name of class implementing JavaClassDef after '{0}' "
+ "in definition for class {1}",
"definedBy",
className));
definedBy = args[i];
}
else {
throw new IOException(L.l(
"unknown token '{0}' in definition for class {1} ",
args[i],
className));
}
}
if (phpClassName == null)
phpClassName = className.substring(className.lastIndexOf('.') + 1);
Class javaClassDefClass;
if (definedBy != null) {
javaClassDefClass = Class.forName(definedBy, false, loader);
}
else
javaClassDefClass = null;
introspectJavaClass(phpClassName, cl, extension, javaClassDefClass);
} catch (Exception e) {
log.fine("Failed loading " + className + "\n" + e.toString());
log.log(Level.FINE, e.toString(), e);
}
}
}
/**
* Introspects the module class for functions.
*
* @param name the php class name
* @param type the class to introspect.
* @param extension the extension provided by the class, or null
* @param javaClassDefClass
*/
public void introspectJavaClass(String name, Class type, String extension,
Class javaClassDefClass)
throws IllegalAccessException, InstantiationException, ConfigException,
NoSuchMethodException, InvocationTargetException
{
JavaClassDef def = addClass(name, type, extension, javaClassDefClass);
synchronized (_javaClassWrappers) {
_javaClassWrappers.put(name, def);
_javaClassWrappers.put(type.getName(), def);
// _lowerJavaClassWrappers.put(name.toLowerCase(Locale.ENGLISH), def);
}
if (extension != null)
_extensionSet.add(extension);
}
/**
* Introspects the module class for functions.
*
* @param name the php class name
* @param type the class to introspect.
* @param extension the extension provided by the class, or null
*/
public void introspectJavaImplClass(String name,
Class type,
String extension)
throws IllegalAccessException, InstantiationException, ConfigException
{
if (log.isLoggable(Level.FINEST)) {
if (extension == null)
log.finest(L.l("Quercus loading class {0} with type {1}",
name,
type.getName()));
else
log.finest(
L.l("Quercus loading class {0} with type {1} providing extension {2}",
name,
type.getName(),
extension));
}
try {
JavaClassDef def = addClass(name, type, extension, null);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw ConfigException.create(e);
}
}
/**
* Scans the classpath for META-INF/services/com.caucho.quercus.QuercusClass
*/
private void initStaticClasses()
{
/*
_stdClassDef = new InterpretedClassDef("stdClass", null, new String[0]);
_stdClass = new QuercusClass(_stdClassDef, null);
_staticClasses.put(_stdClass.getName(), _stdClassDef);
_lowerStaticClasses.put(_stdClass.getName().toLowerCase(Locale.ENGLISH), _stdClassDef);
InterpretedClassDef exn = new InterpretedClassDef("Exception",
null,
new String[0]);
try {
exn.setConstructor(new StaticFunction(_moduleContext,
null,
Quercus.class.getMethod(
"exnConstructor",
new Class[]{ Env.class,
Value.class,
String.class })));
}
catch (Exception e) {
throw new QuercusException(e);
}
// QuercusClass exnCl = new QuercusClass(exn, null);
_staticClasses.put(exn.getName(), exn);
_lowerStaticClasses.put(exn.getName().toLowerCase(Locale.ENGLISH), exn);
*/
}
public static Value objectToValue(Object obj)
{
if (obj == null)
return NullValue.NULL;
else if (Byte.class.equals(obj.getClass())
|| Short.class.equals(obj.getClass())
|| Integer.class.equals(obj.getClass())
|| Long.class.equals(obj.getClass())) {
return LongValue.create(((Number) obj).longValue());
} else if (Float.class.equals(obj.getClass())
|| Double.class.equals(obj.getClass())) {
return DoubleValue.create(((Number) obj).doubleValue());
} else if (String.class.equals(obj.getClass())) {
// XXX: i18n
return new StringBuilderValue((String) obj);
} else {
// XXX: unknown types, e.g. Character?
return null;
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _loader + "]";
}
}