1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.development
;
5 import com
.google
.appengine
.tools
.info
.SdkInfo
;
6 import com
.google
.appengine
.tools
.info
.UpdateCheck
;
7 import com
.google
.appengine
.tools
.util
.Action
;
8 import com
.google
.appengine
.tools
.util
.Option
;
9 import com
.google
.appengine
.tools
.util
.Parser
;
10 import com
.google
.appengine
.tools
.util
.Parser
.ParseResult
;
11 import com
.google
.apphosting
.utils
.config
.GenerationDirectory
;
12 import com
.google
.common
.annotations
.VisibleForTesting
;
13 import com
.google
.common
.collect
.ImmutableList
;
16 import java
.io
.PrintStream
;
17 import java
.util
.ArrayList
;
18 import java
.util
.Arrays
;
19 import java
.util
.List
;
23 * The command-line entry point for DevAppServer.
26 public class DevAppServerMain
extends SharedMain
{
28 public static final String EXTERNAL_RESOURCE_DIR_ARG
= "external_resource_dir";
29 public static final String GENERATE_WAR_ARG
= "generate_war";
30 public static final String GENERATED_WAR_DIR_ARG
= "generated_war_dir";
32 private static final String DEFAULT_RDBMS_PROPERTIES_FILE
= ".local.rdbms.properties";
33 private static final String RDBMS_PROPERTIES_FILE_SYSTEM_PROPERTY
= "rdbms.properties.file";
35 private static final String SYSTEM_PROPERTY_STATIC_MODULE_PORT_NUM_PREFIX
=
36 "com.google.appengine.devappserver_module.";
38 private final Action ACTION
= new StartAction();
40 private String versionCheckServer
= SdkInfo
.getDefaultServer();
42 private String address
= DevAppServer
.DEFAULT_HTTP_ADDRESS
;
43 private int port
= DevAppServer
.DEFAULT_HTTP_PORT
;
44 private boolean disableUpdateCheck
;
45 private String generatedDirectory
= null;
46 private String defaultGcsBucketName
= null;
49 * Returns the list of built-in {@link Option Options} for the given instance of
50 * {@link DevAppServerMain}.
52 * @param main The instance of {@code DevAppServerMain} for which the built-in options are being
53 * requested. This may be {@code null} if {@link Option#apply()} will never be invoked on
54 * any of the returned {@code Options}.
55 * @return The list of built-in options
58 List
<Option
> getBuiltInOptions() {
59 List
<Option
> options
= new ArrayList
<>();
60 options
.addAll(getSharedOptions());
61 options
.addAll(Arrays
.asList(
62 new Option("s", "server", false) {
65 versionCheckServer
= getValue();
68 public List
<String
> getHelpLines() {
69 return ImmutableList
.of(
70 " --server=SERVER The server to use to determine the latest",
71 " -s SERVER SDK version.");
74 new Option("a", "address", false) {
80 public List
<String
> getHelpLines() {
81 return ImmutableList
.of(
82 " --address=ADDRESS The address of the interface on the local machine",
83 " -a ADDRESS to bind to (or 0.0.0.0 for all interfaces).");
86 new Option("p", "port", false) {
89 port
= Integer
.valueOf(getValue());
92 public List
<String
> getHelpLines() {
93 return ImmutableList
.of(
94 " --port=PORT The port number to bind to on the local machine.",
98 new Option(null, "disable_update_check", true) {
100 public void apply() {
101 disableUpdateCheck
= true;
104 public List
<String
> getHelpLines() {
105 return ImmutableList
.of(
106 " --disable_update_check Disable the check for newer SDK versions.");
109 new Option(null, "generated_dir", false) {
111 public void apply() {
112 generatedDirectory
= getValue();
115 public List
<String
> getHelpLines() {
116 return ImmutableList
.of(
117 " --generated_dir=DIR Set the directory where generated files are created.");
120 new Option(null, "default_gcs_bucket", false) {
122 public void apply() {
123 defaultGcsBucketName
= getValue();
126 public List
<String
> getHelpLines() {
127 return ImmutableList
.of(
128 " --default_gcs_bucket=NAME Set the default Google Cloud Storage bucket name.");
131 new Option(null, "instance_port", false) {
133 public void apply() {
134 processInstancePorts(getValues());
137 new Option(null, "disable_filesapi_warning", true) {
139 public void apply() {
140 System
.setProperty("appengine.disableFilesApiWarning", "true");
143 new Option(null, "enable_filesapi", true) {
145 public void apply() {
146 System
.setProperty("appengine.enableFilesApi", "true");
153 private static void processInstancePorts(List
<String
> optionValues
) {
154 for (String optionValue
: optionValues
) {
155 String
[] keyAndValue
= optionValue
.split("=", 2);
156 if (keyAndValue
.length
!= 2) {
157 reportBadInstancePortValue(optionValue
);
161 Integer
.parseInt(keyAndValue
[1]);
162 } catch (NumberFormatException nfe
) {
163 reportBadInstancePortValue(optionValue
);
167 SYSTEM_PROPERTY_STATIC_MODULE_PORT_NUM_PREFIX
+ keyAndValue
[0].trim() + ".port",
168 keyAndValue
[1].trim());
172 private static void reportBadInstancePortValue(String optionValue
) {
173 throw new IllegalArgumentException("Invalid instance_port value " + optionValue
);
177 * Builds the complete list of {@link Option Options} for the given instance of
178 * {@link DevAppServerMain}. The list consists of the built-in options.
180 * @param main The instance of {@code DevAppServerMain} for which the options are being requested.
181 * This may be {@code null} if {@link Option#apply()} will never be invoked on any of the
182 * returned {@code Options}.
183 * @return The list of all options
185 private List
<Option
> buildOptions() {
186 List
<Option
> options
= getBuiltInOptions();
190 public static void main(String
[] args
) throws Exception
{
191 SharedMain
.sharedInit();
192 new DevAppServerMain().run(args
);
195 public DevAppServerMain() {
198 public void run(String
[] args
) throws Exception
{
199 Parser parser
= new Parser();
200 ParseResult result
= parser
.parseArgs(ACTION
, buildOptions(), args
);
205 public void printHelp(PrintStream out
) {
206 out
.println("Usage: <dev-appserver> [options] <app directory>");
208 out
.println("Options:");
209 for (Option option
: buildOptions()) {
210 for (String helpString
: option
.getHelpLines()) {
211 out
.println(helpString
);
214 out
.println(" --jvm_flag=FLAG Pass FLAG as a JVM argument. May be repeated to");
215 out
.println(" supply multiple flags.");
218 class StartAction
extends Action
{
224 public void apply() {
225 List
<String
> args
= getArgs();
227 File externalResourceDir
= getExternalResourceDir();
228 if (args
.size() != 1) {
229 printHelp(System
.err
);
232 File appDir
= new File(args
.get(0)).getCanonicalFile();
233 validateWarPath(appDir
);
235 UpdateCheck updateCheck
= new UpdateCheck(versionCheckServer
, appDir
, true);
236 if (updateCheck
.allowedToCheckForUpdates() && !disableUpdateCheck
) {
237 updateCheck
.maybePrintNagScreen(System
.err
);
239 updateCheck
.checkJavaVersion(System
.err
);
241 DevAppServer server
= new DevAppServerFactory().createDevAppServer(appDir
,
242 externalResourceDir
, address
, port
, getNoJavaAgent());
244 Map
<String
, String
> stringProperties
= getSystemProperties();
245 setGeneratedDirectory(stringProperties
);
246 setRdbmsPropertiesFile(stringProperties
, appDir
, externalResourceDir
);
247 postServerActions(stringProperties
);
248 setDefaultGcsBucketName(stringProperties
);
249 addPropertyOptionToProperties(stringProperties
);
250 server
.setServiceProperties(stringProperties
);
253 server
.start().await();
254 } catch (InterruptedException e
) {
257 System
.out
.println("Shutting down.");
259 } catch (Exception ex
) {
260 ex
.printStackTrace();
265 private void setGeneratedDirectory(Map
<String
, String
> stringProperties
) {
266 if (generatedDirectory
!= null) {
267 File dir
= new File(generatedDirectory
);
270 if (!dir
.isDirectory()) {
271 error
= generatedDirectory
+ " is not a directory.";
272 } else if (!dir
.canWrite()) {
273 error
= generatedDirectory
+ " is not writable.";
275 } else if (!dir
.mkdirs()) {
276 error
= "Could not make " + generatedDirectory
;
279 System
.err
.println(error
);
282 stringProperties
.put(GenerationDirectory
.GENERATED_DIR_PROPERTY
, generatedDirectory
);
286 private void setDefaultGcsBucketName(Map
<String
, String
> stringProperties
) {
287 if (defaultGcsBucketName
!= null) {
288 stringProperties
.put("appengine.default.gcs.bucket.name", defaultGcsBucketName
);
293 * Sets the property named {@link #RDBMS_PROPERTIES_FILE_SYSTEM_PROPERTY} to the default value
294 * {@link #DEFAULT_RDBMS_PROPERTIES_FILE} if the property is not already set and if there is a
295 * file by that name in either {@code appDir} or {@code externalResourceDir}.
297 * @param stringProperties The map in which the value will be set
298 * @param appDir The appDir, aka the WAR dir
299 * @param externalResourceDir the external resource dir, or {@code null} if there is not one.
301 private void setRdbmsPropertiesFile(
302 Map
<String
, String
> stringProperties
, File appDir
, File externalResourceDir
) {
303 if (stringProperties
.get(RDBMS_PROPERTIES_FILE_SYSTEM_PROPERTY
) != null) {
306 File file
= findRdbmsPropertiesFile(externalResourceDir
);
308 file
= findRdbmsPropertiesFile(appDir
);
311 String path
= file
.getPath();
312 System
.out
.println("Reading local rdbms properties from " + path
);
313 stringProperties
.put(RDBMS_PROPERTIES_FILE_SYSTEM_PROPERTY
, path
);
318 * Returns the default rdbms properties file in the given dir if it exists.
319 * @param dir The directory in which to look
320 * @return The default rdbs properties file, or {@code null}.
322 private File
findRdbmsPropertiesFile(File dir
) {
323 File candidate
= new File(dir
, DEFAULT_RDBMS_PROPERTIES_FILE
);
324 if (candidate
.isFile() && candidate
.canRead()) {