io.soluble.pjb.servlet.ContextLoaderListener Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of php-java-bridge Show documentation
Show all versions of php-java-bridge Show documentation
PHPJavaBridge server (soluble fork)
/*-*- mode: Java; tab-width:8 -*-*/
package io.soluble.pjb.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import io.soluble.pjb.bridge.ILogger;
import io.soluble.pjb.bridge.ThreadPool;
import io.soluble.pjb.bridge.Util;
import io.soluble.pjb.bridge.http.ContextServer;
import io.soluble.pjb.bridge.http.FCGIConnectException;
import io.soluble.pjb.bridge.http.FCGIConnection;
import io.soluble.pjb.bridge.http.FCGIConnectionFactory;
import io.soluble.pjb.bridge.http.FCGIConnectionPool;
import io.soluble.pjb.bridge.http.FCGIIOFactory;
import io.soluble.pjb.bridge.http.FCGIInputStream;
import io.soluble.pjb.bridge.http.FCGIOutputStream;
import io.soluble.pjb.bridge.http.FCGIUtil;
import io.soluble.pjb.bridge.http.IFCGIProcess;
import io.soluble.pjb.bridge.http.IFCGIProcessFactory;
import io.soluble.pjb.servlet.fastcgi.FCGIProcess;
/*
* Copyright (C) 2003-2007 Jost Boekemeier
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Register the PHP/Java Bridge when the web context starts. Used by java_context()->onShutdown(proc). The
* WEB-INF/web.xml contains a listener attribute:
*
*
* <listener>
* <listener-class>io.soluble.pjb.servlet.ContextLoaderListener</listener-class>
* </listener>
*
*
*/
public class ContextLoaderListener implements javax.servlet.ServletContextListener, IFCGIProcessFactory {
private LinkedList closeables = new LinkedList();
public static final String PEAR_DIR = "/WEB-INF/pear";
public static final String CGI_DIR = "/WEB-INF/cgi";
public static final String WEB_INF_DIR = "/WEB-INF";
protected ServletContext context;
protected FCGIConnectionFactory channelName;
protected String php = null;
/**
* default: true. Switched off when fcgi is not configured
*/
protected boolean fcgiIsConfigured;
protected boolean canStartFCGI = false;
protected boolean override_hosts = true;
protected String php_fcgi_connection_pool_size = FCGIUtil.PHP_FCGI_CONNECTION_POOL_SIZE;
protected String php_fcgi_connection_pool_timeout = FCGIUtil.PHP_FCGI_CONNECTION_POOL_TIMEOUT;
protected boolean php_include_java;
protected int php_fcgi_connection_pool_size_number = Integer.parseInt(FCGIUtil.PHP_FCGI_CONNECTION_POOL_SIZE);
protected long php_fcgi_connection_pool_timeout_number = Long.parseLong(FCGIUtil.PHP_FCGI_CONNECTION_POOL_TIMEOUT);
protected String php_fcgi_max_requests = FCGIUtil.PHP_FCGI_MAX_REQUESTS;
protected int php_fcgi_max_requests_number = Integer.parseInt(FCGIUtil.PHP_FCGI_MAX_REQUESTS);
protected ILogger logger;
protected boolean promiscuous = true;
protected ContextLoaderListener listener;
private FCGIConnectionPool fcgiConnectionPool = null;
// workaround for a bug in jboss server, which uses the log4j port 4445 for its internal purposes(!)
private boolean isJBoss;
private ContextServer contextServer; // shared with FastCGIServlet
private ThreadPool fcgiThreadPool;
/**
* The key used to store the ContextLoaderListener in the servlet context
*/
public static final String CONTEXT_LOADER_LISTENER = ContextLoaderListener.class.getName() + ".ROOT";
/**
* Only for internal use
*
* @param ctx The servlet context
*/
public void destroyCloseables(ServletContext ctx) {
List list = closeables;
if (list == null) return;
try {
for (Iterator ii = list.iterator(); ii.hasNext(); ) {
Object c = ii.next();
try {
Method close = c.getClass().getMethod("close", Util.ZERO_PARAM);
close.setAccessible(true);
close.invoke(c, Util.ZERO_ARG);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
closeables.clear();
}
}
/**
* {@inheritDoc}
*/
public void contextDestroyed(ServletContextEvent event) {
ServletContext ctx = event.getServletContext();
try {
destroyCloseables(ctx);
} catch (Exception e) {
e.printStackTrace();
}
if (fcgiConnectionPool != null) fcgiConnectionPool.destroy();
if (fcgiThreadPool != null) fcgiThreadPool.destroy();
if (channelName != null) channelName.destroy();
fcgiConnectionPool = null;
if (contextServer != null) contextServer.destroy();
Util.destroy();
}
/**
* {@inheritDoc}
*/
public void contextInitialized(ServletContextEvent event) {
ServletContext ctx = event.getServletContext();
ctx.setAttribute(CONTEXT_LOADER_LISTENER, this);
this.context = ctx;
init();
boolean promiscuous = true;
try {
String value = ctx.getInitParameter("promiscuous");
if (value == null) value = "";
value = value.trim();
value = value.toLowerCase();
if (value.equals("off") || value.equals("false")) promiscuous = false;
} catch (Exception t) {
t.printStackTrace();
}
String name = context.getServerInfo();
if (name != null && (name.startsWith("JBoss"))) isJBoss = true;
logger = new Util.Logger(!isJBoss, new Logger());
Util.setDefaultLogger(logger);
String servletContextName = ServletUtil.getRealPath(context, "");
if (servletContextName == null) servletContextName = "";
contextServer = new ContextServer(servletContextName, promiscuous);
channelName = FCGIConnectionFactory.createChannelFactory(this, promiscuous);
channelName.findFreePort(canStartFCGI);
try {
fcgiConnectionPool = createConnectionPool(php_fcgi_connection_pool_size_number);
} catch (FCGIConnectException e) {
Util.printStackTrace(e);
}
fcgiThreadPool = createThreadPool(php_fcgi_connection_pool_size_number);
}
private void init() {
String value;
try {
value = context.getInitParameter("prefer_system_php_exec");
if (value == null) value = "";
value = value.trim();
value = value.toLowerCase();
if (value.equals("on") || value.equals("true")) preferSystemPhp = true;
} catch (Throwable t) {
t.printStackTrace();
}
String val = null;
try {
val = context.getInitParameter("php_fcgi_children");
if (val == null) val = context.getInitParameter("PHP_FCGI_CHILDREN");
if (val == null) val = System.getProperty("io.soluble.pjb.bridge.php_fcgi_children");
if (val == null) val = context.getInitParameter("php_fcgi_connection_pool_size");
if (val == null) val = System.getProperty("io.soluble.pjb.bridge.php_fcgi_connection_pool_size");
if (val != null) php_fcgi_connection_pool_size_number = Integer.parseInt(val);
} catch (Throwable t) {/*ignore*/}
if (val != null) php_fcgi_connection_pool_size = val;
val = null;
try {
val = context.getInitParameter("php_fcgi_connection_pool_timeout");
if (val == null) val = System.getProperty("io.soluble.pjb.bridge.php_fcgi_connection_pool_timeout");
if (val != null) php_fcgi_connection_pool_timeout_number = Integer.parseInt(val);
} catch (Throwable t) {/*ignore*/}
if (val != null) php_fcgi_connection_pool_timeout = val;
val = null;
php_include_java = false;
try {
val = context.getInitParameter("php_include_java");
if (val == null) val = context.getInitParameter("PHP_INCLUDE_JAVA");
if (val == null) val = System.getProperty("io.soluble.pjb.bridge.php_include_java");
if (val != null && (val.equalsIgnoreCase("on") || val.equalsIgnoreCase("true")))
php_include_java = true;
} catch (Throwable t) {/*ignore*/}
val = null;
try {
val = context.getInitParameter("php_fcgi_max_requests");
if (val == null) val = System.getProperty("io.soluble.pjb.bridge.php_fcgi_max_requests");
if (val != null) {
php_fcgi_max_requests_number = Integer.parseInt(val);
php_fcgi_max_requests = val;
}
} catch (Throwable t) {/*ignore*/}
checkCgiBinary();
createPhpFiles();
}
private void checkCgiBinary() {
String value;
if (php == null) {
try {
value = context.getInitParameter("php_exec");
if (value == null || value.trim().length() == 0) {
value = "php-cgi";
phpTryOtherLocations = true;
}
File f = new File(value);
if (!f.isAbsolute()) {
value = ServletUtil.getRealPath(context, CGI_DIR) + File.separator + value;
}
php = value;
} catch (Throwable t) {
Util.printStackTrace(t);
}
}
fcgiIsConfigured = true;
try {
value = context.getInitParameter("use_fast_cgi");
if (value == null) try {
value = System.getProperty("io.soluble.pjb.bridge.use_fast_cgi");
} catch (Exception e) {/*ignore*/}
if ("false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value)) fcgiIsConfigured = false;
else {
value = context.getInitParameter("use_fast_cgi");
if (value == null) value = "auto";
value = value.trim();
value = value.toLowerCase();
boolean autostart = value.startsWith("auto");
boolean notAvailable = value.equals("false") || value.equals("off");
if (notAvailable) fcgiIsConfigured = false;
if (autostart) canStartFCGI = true;
}
} catch (Throwable t) {
Util.printStackTrace(t);
}
}
private boolean useSystemPhp(File f) {
// path hard coded in web.xml
if (!phpTryOtherLocations) return true;
// no local php exists
if (!f.exists()) return true;
// local exists
if (!preferSystemPhp) return false;
// check default locations for preferred system php
for (String loc : Util.DEFAULT_CGI_LOCATIONS) {
File location = new File(loc);
if (location.exists()) return true;
}
return false;
}
static final HashMap PROCESS_ENVIRONMENT = getProcessEnvironment();
private static void updateProcessEnvironment(File conf) {
try {
PROCESS_ENVIRONMENT.put("PHP_INI_SCAN_DIR", conf.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
PROCESS_ENVIRONMENT.put("PHP_INI_SCAN_DIR", conf.getAbsolutePath());
}
}
private static HashMap getProcessEnvironment() {
HashMap map = new HashMap(Util.COMMON_ENVIRONMENT);
return map;
}
private void createPhpFiles() {
String javaDir = ServletUtil.getRealPath(context, "java");
if (javaDir != null) {
File javaDirFile = new File(javaDir);
try {
if (!javaDirFile.exists()) {
javaDirFile.mkdir();
}
} catch (Exception e) {/*ignore*/}
File javaIncFile = new File(javaDir, "Java.inc");
try {
if (!javaIncFile.exists()) {
Field f = Util.JAVA_INC.getField("bytes");
byte[] buf = (byte[]) f.get(Util.JAVA_INC);
try (OutputStream out = new FileOutputStream(javaIncFile)) {
out.write(buf);
}
}
} catch (Exception e) {
e.printStackTrace();
}
File javaProxyFile = new File(javaDir, "JavaProxy.php");
try {
if (!javaProxyFile.exists()) {
Field f = Util.JAVA_PROXY.getField("bytes");
byte[] buf = (byte[]) f.get(Util.JAVA_PROXY);
try (OutputStream out = new FileOutputStream(javaProxyFile)) {
out.write(buf);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
String pearDir = ServletUtil.getRealPath(context, PEAR_DIR);
if (pearDir != null) {
File pearDirFile = new File(pearDir);
try {
if (!pearDirFile.exists()) {
pearDirFile.mkdir();
}
} catch (Exception e) {
e.printStackTrace();
}
}
String cgiDir = ServletUtil.getRealPath(context, CGI_DIR);
File cgiOsDir = new File(cgiDir, Util.osArch + "-" + Util.osName);
File conf = new File(cgiOsDir, "conf.d");
File ext = new File(cgiOsDir, "ext");
File cgiDirFile = new File(cgiDir);
try {
if (!cgiDirFile.exists()) {
cgiDirFile.mkdirs();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (!conf.exists()) {
conf.mkdirs();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (!ext.exists()) {
ext.mkdir();
}
} catch (Exception e) {
e.printStackTrace();
}
File javaIncFile = new File(cgiOsDir, "launcher.sh");
if (Util.USE_SH_WRAPPER) {
try {
if (!javaIncFile.exists()) {
Field f = Util.LAUNCHER_UNIX.getField("bytes");
byte[] buf = (byte[]) f.get(Util.LAUNCHER_UNIX);
try (OutputStream out = new FileOutputStream(javaIncFile)) {
out.write(buf);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
File javaProxyFile = new File(cgiOsDir, "launcher.exe");
if (!Util.USE_SH_WRAPPER) {
try {
if (!javaProxyFile.exists()) {
Field f = Util.LAUNCHER_WINDOWS.getField("bytes");
Field f2 = Util.LAUNCHER_WINDOWS2.getField("bytes");
Field f3 = Util.LAUNCHER_WINDOWS3.getField("bytes");
Field f4 = Util.LAUNCHER_WINDOWS4.getField("bytes");
byte[] buf = (byte[]) f.get(Util.LAUNCHER_WINDOWS);
byte[] buf2 = (byte[]) f2.get(Util.LAUNCHER_WINDOWS2);
byte[] buf3 = (byte[]) f3.get(Util.LAUNCHER_WINDOWS3);
byte[] buf4 = (byte[]) f4.get(Util.LAUNCHER_WINDOWS4);
try (OutputStream out = new FileOutputStream(javaProxyFile)) {
out.write(buf);
out.write(buf2);
out.write(buf3);
out.write(buf4);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
boolean exeExists = true;
if (Util.USE_SH_WRAPPER) {
try {
File phpCgi = new File(cgiOsDir, "php-cgi");
if (!useSystemPhp(phpCgi)) {
updateProcessEnvironment(conf);
File wrapper = new File(cgiOsDir, "php-cgi.sh");
if (!wrapper.exists()) {
byte[] data = ("#!/bin/sh\nchmod +x ./" + Util.osArch + "-" + Util.osName + "/php-cgi\n" +
"exec ./" + Util.osArch + "-" + Util.osName + "/php-cgi -c ./" + Util.osArch + "-" + Util.osName + "/php-cgi.ini \"$@\"").getBytes();
try (OutputStream out = new FileOutputStream(wrapper)) {
out.write(data);
}
}
File ini = new File(cgiOsDir, "php-cgi.ini");
if (!ini.exists()) {
byte[] data = (";; -*- mode: Scheme; tab-width:4 -*-\n;; A simple php.ini\n" +
";; DO NOT EDIT THIS FILE!\n" +
";; Add your configuration files to the " + conf + " instead.\n" +
";; PHP extensions go to " + ext + ". Please see phpinfo() for ABI version details.\n" +
"extension_dir=\"" + ext + "\"\n" +
"include_path=\"" + pearDir + ":/usr/share/pear:.\"\n").getBytes();
try (OutputStream out = new FileOutputStream(ini)) {
out.write(data);
}
}
} else {
exeExists = false;
File readme = new File(cgiOsDir, "php-cgi.MISSING.README.txt");
if (!readme.exists()) {
byte[] data = ("You can add \"php-cgi\" to this directory and re-deploy your web application.\n").getBytes();
try (OutputStream out = new FileOutputStream(readme)) {
out.write(data);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
File phpCgi = new File(cgiOsDir, "php-cgi.exe");
if (!useSystemPhp(phpCgi)) {
updateProcessEnvironment(conf);
File ini = new File(cgiOsDir, "php.ini");
if (!ini.exists()) {
byte[] data = (";; -*- mode: Scheme; tab-width:4 -*-\r\n;; A simple php.ini\r\n" +
";; DO NOT EDIT THIS FILE!\r\n" +
";; Add your configuration files to the " + conf + " instead.\r\n" +
";; PHP extensions go to " + ext + ". Please see phpinfo() for ABI version details.\r\n" +
"extension_dir=\"" + ext + "\"\r\n" +
"include_path=\"" + pearDir + ";.\"\r\n").getBytes();
OutputStream out = new FileOutputStream(ini);
out.write(data);
out.close();
}
} else {
exeExists = false;
File readme = new File(cgiOsDir, "php-cgi.exe.MISSING.README.txt");
if (!readme.exists()) {
byte[] data = ("You can add \"php-cgi.exe\" to this directory and re-deploy your web application.\r\n").getBytes();
OutputStream out = new FileOutputStream(readme);
out.write(data);
out.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
File tmpl = new File(conf, "mysql.ini");
if (exeExists && !tmpl.exists()) {
String str;
if (Util.USE_SH_WRAPPER) {
str = ";; -*- mode: Scheme; tab-width:4 -*-\n" +
";; Example extension.ini file: mysql.ini.\n" +
";; Copy the correct version (see phpinfo()) of the PHP extension \"mysql.so\" to the ./../ext directory and uncomment the following line\n" +
"; extension = mysql.so\n";
} else {
str = ";; -*- mode: Scheme; tab-width:4 -*-\r\n" +
";; Example extension.ini file: mysql.ini.\r\n" +
";; Copy the correct version (see phpinfo()) of the PHP extension \"php_mysql.dll\" to the .\\..\\ext directory and uncomment the following line\r\n" +
"; extension = php_mysql.dll\r\n";
}
byte[] data = str.getBytes();
try {
OutputStream out = new FileOutputStream(tmpl);
out.write(data);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private final FCGIIOFactory defaultPoolFactory = new FCGIIOFactory() {
public InputStream createInputStream() {
return new FCGIInputStream(ContextLoaderListener.this);
}
public OutputStream createOutputStream() {
return new FCGIOutputStream();
}
public FCGIConnection connect(FCGIConnectionFactory name) throws FCGIConnectException {
return name.connect();
}
};
private FCGIConnectionPool createConnectionPool(int children) throws FCGIConnectException {
channelName.initialize();
// Start the launcher.exe or launcher.sh
channelName.startServer(logger);
return new FCGIConnectionPool(channelName, children, php_fcgi_max_requests_number, defaultPoolFactory, php_fcgi_connection_pool_timeout_number);
}
private ThreadPool createThreadPool(int children) {
return new ThreadPool("JavaBridgeServletScriptEngineProxy", children);
}
public ThreadPool getThreadPool() {
return fcgiThreadPool;
}
public ContextServer getContextServer() {
return contextServer;
}
public ILogger getLogger() {
return logger;
}
public FCGIConnectionFactory getChannelName() {
return channelName;
}
public List getCloseables() {
return closeables;
}
public FCGIConnectionPool getConnectionPool() {
return fcgiConnectionPool;
}
public static ContextLoaderListener getContextLoaderListener(ServletContext ctx) {
return (ContextLoaderListener) ctx.getAttribute(ContextLoaderListener.CONTEXT_LOADER_LISTENER);
}
/**
* required by IFCGIProcessFactory
*/
protected boolean preferSystemPhp = false; // prefer /usr/bin/php-cgi over WEB-INF/cgi/php-cgi?
protected boolean phpTryOtherLocations = false;
/**
* {@inheritDoc}
*/
public IFCGIProcess createFCGIProcess(String[] args, boolean includeJava, File home, Map env)
throws IOException {
return new FCGIProcess(args, includeJava, getCgiDir(), getPearDir(), getWebInfDir(), home, env, getCgiDir(), phpTryOtherLocations, preferSystemPhp);
}
/**
* {@inheritDoc}
*/
public String getPhpConnectionPoolSize() {
return php_fcgi_connection_pool_size;
}
/**
* {@inheritDoc}
*/
public String getPhpMaxRequests() {
return php_fcgi_max_requests;
}
/**
* {@inheritDoc}
*/
public String getPhp() {
return php;
}
/**
* {@inheritDoc}
*/
public boolean getPhpIncludeJava() {
return php_include_java;
}
/**
* {@inheritDoc}
*/
public HashMap getEnvironment() {
return getProcessEnvironment();
}
/**
* {@inheritDoc}
*/
public boolean canStartFCGI() {
return canStartFCGI;
}
private String cgiDir;
/**
* {@inheritDoc}
*/
public String getCgiDir() {
if (cgiDir != null) return cgiDir;
return cgiDir = ServletUtil.getRealPath(context, CGI_DIR);
}
private String pearDir;
/**
* {@inheritDoc}
*/
public String getPearDir() {
if (pearDir != null) return pearDir;
return pearDir = ServletUtil.getRealPath(context, PEAR_DIR);
}
private String webInfDir;
/**
* {@inheritDoc}
*/
public String getWebInfDir() {
if (webInfDir != null) return webInfDir;
return webInfDir = ServletUtil.getRealPath(context, WEB_INF_DIR);
}
/**
* {@inheritDoc}
*/
public void log(String msg) {
logger.log(Logger.INFO, msg);
}
}