Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / DevAppServerFactory.java
blobc879d8cbbc65a43bf4668b529b12919303e7d834
1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.development;
5 import com.google.appengine.tools.development.agent.AppEngineDevAgent;
6 import com.google.apphosting.utils.security.SecurityManagerInstaller;
8 import sun.security.util.SecurityConstants;
10 import java.io.File;
11 import java.lang.reflect.Constructor;
12 import java.lang.reflect.InvocationTargetException;
13 import java.net.SocketPermission;
14 import java.net.URL;
15 import java.security.Permission;
16 import java.util.Collection;
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.PropertyPermission;
21 /**
22 * Creates new {@link DevAppServer DevAppServers} which can be used to launch
23 * web applications.
24 * TODO(user): Describe the difference between standalone and testing servers.
27 public class DevAppServerFactory {
29 static final String DEV_APP_SERVER_CLASS =
30 "com.google.appengine.tools.development.DevAppServerImpl";
32 private static final Class[] DEV_APPSERVER_CTOR_ARG_TYPES = {File.class, File.class, File.class,
33 File.class, String.class, Integer.TYPE, Boolean.TYPE, Map.class};
35 private static final String USER_CODE_CLASSPATH_MANAGER_PROP =
36 "devappserver.userCodeClasspathManager";
37 private static final String USER_CODE_CLASSPATH = USER_CODE_CLASSPATH_MANAGER_PROP + ".classpath";
38 private static final String USER_CODE_REQUIRES_WEB_INF =
39 USER_CODE_CLASSPATH_MANAGER_PROP + ".requiresWebInf";
41 /**
42 * Creates a new {@link DevAppServer} ready to start serving
44 * @param appDir The top-level directory of the web application to be run
45 * @param address Address to bind to
46 * @param port Port to bind to
48 * @return a {@code DevAppServer}
50 public DevAppServer createDevAppServer(File appDir, String address, int port) {
51 return createDevAppServer(appDir, null, address, port);
54 /**
55 * Creates a new {@link DevAppServer} ready to start serving
57 * @param appDir The top-level directory of the web application to be run
58 * @param externalResourceDir If not {@code null}, a resource directory external to the appDir.
59 * This will be searched before appDir when looking for resources.
60 * @param address Address to bind to
61 * @param port Port to bind to
63 * @return a {@code DevAppServer}
65 public DevAppServer createDevAppServer(File appDir, File externalResourceDir, String address,
66 int port) {
67 return createDevAppServer(appDir, externalResourceDir, null, null, address, port, true,
68 true, new HashMap<String, Object>());
71 /**
72 * Creates a new {@link DevAppServer} with a custom classpath for the web
73 * app..
75 * @param appDir The top-level directory of the web application to be run
76 * @param webXmlLocation The location of a file whose format complies with
77 * http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd. If {@code null},
78 * defaults to <appDir>/WEB-INF/web.xml
79 * @param appEngineWebXmlLocation The name of the app engine config file. If
80 * {@code null}, defaults to <appDir>/WEB-INF/appengine-web.xml.
81 * @param address Address to bind to
82 * @param port Port to bind to
83 * @param useCustomStreamHandler If {@code true}, install
84 * {@link StreamHandlerFactory}. This is "normal" behavior for the dev
85 * app server but tests may want to disable this since there are some
86 * compatibility issues with our custom handler and Selenium.
87 * @param installSecurityManager Whether or not to install the dev appserver
88 * security manager. It is strongly recommended you pass {@code true} unless
89 * there is something in your test environment that prevents you from
90 * installing a security manager.
91 * @param classpath The classpath of the test and all its dependencies
92 * (possibly the entire app)..
94 * @return a {@code DevAppServer}
96 public DevAppServer createDevAppServer(File appDir, File webXmlLocation,
97 File appEngineWebXmlLocation, String address, int port,
98 boolean useCustomStreamHandler, boolean installSecurityManager, Collection<URL> classpath) {
99 Map<String, Object> containerConfigProps = newContainerConfigPropertiesForTest(classpath);
100 return createDevAppServer(appDir, null, webXmlLocation, appEngineWebXmlLocation,
101 address, port, useCustomStreamHandler, installSecurityManager, containerConfigProps);
105 * Creates a new {@link DevAppServer} ready to start serving. Only exposed
106 * to clients that can access it via reflection to keep it out of the public
107 * api.
109 * @param appDir The top-level directory of the web application to be run
110 * @param webXmlLocation The location of a file whose format complies with
111 * http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd. If {@code null},
112 * defaults to <appDir>/WEB-INF/web.xml
113 * @param appEngineWebXmlLocation The location of the app engine config file. If
114 * {@code null}, defaults to <appDir>/WEB-INF/appengine-web.xml.
115 * @param address Address to bind to
116 * @param port Port to bind to
117 * @param useCustomStreamHandler If {@code true}, install
118 * {@link StreamHandlerFactory}. This is "normal" behavior for the dev
119 * app server but tests may want to disable this since there are some
120 * compatibility issues with our custom handler and Selenium.
122 * @return a {@code DevAppServer}
124 @SuppressWarnings("unused")
125 private DevAppServer createDevAppServer(File appDir,File webXmlLocation,
126 File appEngineWebXmlLocation, String address, int port, boolean useCustomStreamHandler) {
127 return createDevAppServer(appDir, null, webXmlLocation, appEngineWebXmlLocation,
128 address, port, useCustomStreamHandler, true, new HashMap<String, Object>());
131 @SuppressWarnings("unused")
132 private DevAppServer createDevAppServer(File appDir, File webXmlLocation,
133 File appEngineWebXmlLocation, String address, int port, boolean useCustomStreamHandler,
134 boolean installSecurityManager, Map<String, Object> containerConfigProperties) {
135 return createDevAppServer(appDir, null, webXmlLocation, appEngineWebXmlLocation, address, port,
136 useCustomStreamHandler,installSecurityManager, containerConfigProperties);
139 private DevAppServer createDevAppServer(File appDir, File externalResourceDir,
140 File webXmlLocation, File appEngineWebXmlLocation, String address, int port,
141 boolean useCustomStreamHandler, boolean installSecurityManager,
142 Map<String, Object> containerConfigProperties) {
143 if (installSecurityManager) {
144 SecurityManagerInstaller.install();
147 DevAppServerClassLoader loader = DevAppServerClassLoader.newClassLoader(
148 DevAppServerFactory.class.getClassLoader());
150 testAgentIsInstalled();
152 DevAppServer devAppServer;
153 try {
154 Class<?> devAppServerClass = Class.forName(DEV_APP_SERVER_CLASS, true, loader);
155 Constructor cons = devAppServerClass.getConstructor(DEV_APPSERVER_CTOR_ARG_TYPES);
156 cons.setAccessible(true);
157 devAppServer = (DevAppServer) cons.newInstance(
158 appDir, externalResourceDir, webXmlLocation, appEngineWebXmlLocation, address, port,
159 useCustomStreamHandler, containerConfigProperties);
160 } catch (Exception e) {
161 Throwable t = e;
162 if (e instanceof InvocationTargetException) {
163 t = e.getCause();
165 throw new RuntimeException("Unable to create a DevAppServer", t);
167 if (installSecurityManager) {
168 System.setSecurityManager(new CustomSecurityManager(devAppServer));
170 return devAppServer;
174 * Build a {@link Map} that contains settings that will allow us to inject
175 * our own classpath and to not require a WEB-INF directory. This map will
176 * travel across classloader boundaries so all values in the map must be jre
177 * classes.
179 private Map<String, Object> newContainerConfigPropertiesForTest(Collection<URL> classpath) {
180 Map<String, Object> containerConfigProps = new HashMap<String, Object>();
181 Map<String, Object> userCodeClasspathManagerProps = new HashMap<String, Object>();
182 userCodeClasspathManagerProps.put(USER_CODE_CLASSPATH, classpath);
183 userCodeClasspathManagerProps.put(USER_CODE_REQUIRES_WEB_INF, false);
184 containerConfigProps.put(USER_CODE_CLASSPATH_MANAGER_PROP, userCodeClasspathManagerProps);
185 return containerConfigProps;
188 private void testAgentIsInstalled() {
189 try {
190 AppEngineDevAgent.getAgent();
191 } catch (Throwable t) {
192 String msg = "Unable to locate the App Engine agent. Please use dev_appserver, KickStart, "
193 + " or set the jvm flag: \"-javaagent:<sdk_root>/lib/agent/appengine-agent.jar\"";
194 throw new RuntimeException(msg, t);
199 * Implements custom security behavior. This SecurityManager only applies
200 * checks when code is running in the context of a DevAppServer thread
201 * handling an http request.
203 private static class CustomSecurityManager extends SecurityManager {
205 private static final RuntimePermission PERMISSION_MODIFY_THREAD_GROUP =
206 new RuntimePermission("modifyThreadGroup");
208 private static final RuntimePermission PERMISSION_MODIFY_THREAD =
209 new RuntimePermission("modifyThread");
211 private static final String KEYCHAIN_JNILIB = "/libkeychain.jnilib";
213 private static final Object PERMISSION_LOCK = new Object();
215 private final DevAppServer devAppServer;
217 public CustomSecurityManager(DevAppServer devAppServer) {
218 this.devAppServer = devAppServer;
221 private synchronized boolean appHasPermission(Permission perm) {
222 synchronized (PERMISSION_LOCK) {
223 AppContext context = devAppServer.getCurrentAppContext();
224 if (context.getUserPermissions().implies(perm) ||
225 context.getApplicationPermissions().implies(perm)) {
226 return true;
230 if (PERMISSION_MODIFY_THREAD.equals(perm)) {
231 StackTraceElement frame = getCallerFrame();
232 if ("java.util.concurrent.ThreadPoolExecutor".equals(frame.getClassName()) ||
233 ("java.lang.Thread".equals(frame.getClassName()) &&
234 "interrupt".equals(frame.getMethodName())) ||
235 ("java.lang.Thread".equals(frame.getClassName()) &&
236 "setUncaughtExceptionHandler".equals(frame.getMethodName()))) {
237 return true;
241 if (perm instanceof SocketPermission) {
242 return true;
245 return SecurityConstants.FILE_READ_ACTION.equals(perm.getActions()) &&
246 perm.getName().endsWith(KEYCHAIN_JNILIB);
249 @Override
250 public void checkPermission(Permission perm) {
251 if (perm instanceof PropertyPermission) {
252 return;
255 if (isDevAppServerThread()) {
256 if (appHasPermission(perm)) {
257 return;
260 super.checkPermission(perm);
264 @Override
265 public void checkPermission(Permission perm, Object context) {
266 if (isDevAppServerThread()) {
267 if (appHasPermission(perm)) {
268 return;
270 super.checkPermission(perm, context);
275 * Don't allow user code permission to muck with Threads.
276 * Normally the JDK only enforces this for the root ThreadGroup, but
277 * we enforce it at all times.
279 @Override
280 public void checkAccess(ThreadGroup g) {
281 if (g == null) {
282 throw new NullPointerException("thread group can't be null");
285 checkPermission(PERMISSION_MODIFY_THREAD_GROUP);
289 * Enforces the same thread policy as {@link #checkAccess(ThreadGroup)}.
291 @Override
292 public void checkAccess(Thread t) {
293 if (t == null) {
294 throw new NullPointerException("thread can't be null");
297 checkPermission(PERMISSION_MODIFY_THREAD);
300 private boolean isDevAppServerThread() {
302 AppContext context = devAppServer.getCurrentAppContext();
303 return Boolean.getBoolean("devappserver-thread-" + Thread.currentThread().getName())
304 && context != null;
308 * Find the first {@link StackTraceElement} on the current thread
309 * which does not come from this class or from a method named
310 * {@code checkAccess} (e.g. {@link Thread#checkAccess}).
312 private StackTraceElement getCallerFrame() {
313 StackTraceElement[] frames = Thread.currentThread().getStackTrace();
314 for (int i = 1; i < frames.length; i++) {
315 if ("checkAccess".equals(frames[i].getMethodName())) {
316 } else if (!getClass().getName().equals(frames[i].getClassName())) {
317 return frames[i];
320 throw new IllegalStateException("Unable to determine calling frame.");