/* * Copyright 2016 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.apphosting.utils.config; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; /** * Creates an {@link AppEngineWebXml} instance from * <appdir>WEB-INF/appengine-web.xml. If you want to read the configuration * from something that isn't a file, subclass and override * {@link #getInputStream()}. * */ public class AppEngineWebXmlReader { private static final Logger logger = Logger.getLogger(AppEngineWebXmlReader.class.getName()); private static final String CONCURRENT_REQUESTS_URL = "http://code.google.com/appengine/docs/java/config/appconfig.html#Using_Concurrent_Requests"; private static final String DATASTORE_AUTO_IDS_URL = "http://developers.google.com/appengine/docs/java/datastore/entities#Kinds_and_Identifiers"; private static final String APPCFG_AUTO_IDS_URL = "http://developers.google.com/appengine/docs/java/config/appconfig#auto_id_policy"; public static final String DEFAULT_RELATIVE_FILENAME = "WEB-INF/appengine-web.xml"; // Absolute location of the config file private final String filename; /** * Creates a reader for appengine-web.xml. * * @param appDir The directory in which the config file resides. * @param relativeFilename The path to the config file, relative to * {@code appDir}. */ public AppEngineWebXmlReader(String appDir, String relativeFilename) { // Be friendly, make sure we end with a slash. if (appDir.length() > 0 && appDir.charAt(appDir.length() - 1) != File.separatorChar) { appDir += File.separatorChar; } this.filename = appDir + relativeFilename; } /** * Creates a reader for appengine-web.xml. * * @param appDir The directory in which the config file resides. The * path to the config file relative to the directory is assumed to be * {@link #DEFAULT_RELATIVE_FILENAME}. */ public AppEngineWebXmlReader(String appDir) { this(appDir, DEFAULT_RELATIVE_FILENAME); } /** * @return A {@link AppEngineWebXml} config object derived from the * contents of <appdir>WEB-INF/appengine-web.xml. * * @throws AppEngineConfigException If <appdir>WEB-INF/appengine-web.xml does * not exist. Also thrown if we are unable to parse the xml. */ public AppEngineWebXml readAppEngineWebXml() { InputStream is = null; AppEngineWebXml appEngineWebXml; try { is = getInputStream(); appEngineWebXml = processXml(is); logger.fine("Successfully processed " + getFilename()); if (!appEngineWebXml.getThreadsafeValueProvided()) { if (allowMissingThreadsafeElement()) { // make some noise if there is no <threadsafe> element. logger.warning( "appengine-web.xml does not contain a <threadsafe> element. This will " + "be treated as an error the next time you deploy.\nSee " + CONCURRENT_REQUESTS_URL + " for more information.\nYou probably want to enable concurrent requests."); } else { throw new AppEngineConfigException( "appengine-web.xml does not contain a <threadsafe> " + "element.\nSee " + CONCURRENT_REQUESTS_URL + " for more information.\nYou probably " + "want to enable concurrent requests."); } } if ("legacy".equals(appEngineWebXml.getAutoIdPolicy())) { logger.warning( "You have set the datastore auto id policy to 'legacy'. It is recommended " + "that you select 'default' instead.\nLegacy auto ids are deprecated. You can " + "continue to allocate legacy ids manually using the allocateIds() API functions." + "\nFor more information see:\n" + APPCFG_AUTO_IDS_URL + "\n" + DATASTORE_AUTO_IDS_URL + "\n"); } } catch (Exception e) { String msg = "Received exception processing " + getFilename(); logger.log(Level.SEVERE, msg, e); // Guarantee that the only exceptions thrown from this method are of // type AppEngineConfigException. if (e instanceof AppEngineConfigException) { throw (AppEngineConfigException) e; } throw new AppEngineConfigException(msg, e); } finally { close(is); } return appEngineWebXml; } protected boolean allowMissingThreadsafeElement() { return false; } public String getFilename() { return filename; } private void close(InputStream is) { if (is != null) { try { is.close(); } catch (IOException e) { throw new AppEngineConfigException(e); } } } // broken out for testing protected AppEngineWebXml processXml(InputStream is) { return new AppEngineWebXmlProcessor().processXml(is); } protected InputStream getInputStream() { try { return new FileInputStream(getFilename()); } catch (FileNotFoundException fnfe) { // Having the full path in the exception is helpful for debugging. throw new AppEngineConfigException( "Could not locate " + new File(getFilename()).getAbsolutePath(), fnfe); } } }