001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.lib.servlet;
020
021import com.google.common.annotations.VisibleForTesting;
022import org.apache.hadoop.classification.InterfaceAudience;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.lib.server.Server;
025import org.apache.hadoop.lib.server.ServerException;
026
027import javax.servlet.ServletContextEvent;
028import javax.servlet.ServletContextListener;
029import java.net.InetAddress;
030import java.net.InetSocketAddress;
031import java.net.UnknownHostException;
032import java.text.MessageFormat;
033
034/**
035 * {@link Server} subclass that implements <code>ServletContextListener</code>
036 * and uses its lifecycle to start and stop the server.
037 */
038@InterfaceAudience.Private
039public abstract class ServerWebApp extends Server implements ServletContextListener {
040
041  private static final String HOME_DIR = ".home.dir";
042  private static final String CONFIG_DIR = ".config.dir";
043  private static final String LOG_DIR = ".log.dir";
044  private static final String TEMP_DIR = ".temp.dir";
045  private static final String HTTP_HOSTNAME = ".http.hostname";
046  private static final String HTTP_PORT = ".http.port";
047  public static final String SSL_ENABLED = ".ssl.enabled";
048
049  private static final ThreadLocal<String> HOME_DIR_TL =
050      new ThreadLocal<String>();
051
052  private InetSocketAddress authority;
053
054  /**
055   * Method for testing purposes.
056   */
057  public static void setHomeDirForCurrentThread(String homeDir) {
058    HOME_DIR_TL.set(homeDir);
059  }
060
061  /**
062   * Constructor for testing purposes.
063   */
064  protected ServerWebApp(String name, String homeDir, String configDir, String logDir, String tempDir,
065                         Configuration config) {
066    super(name, homeDir, configDir, logDir, tempDir, config);
067  }
068
069  /**
070   * Constructor for testing purposes.
071   */
072  protected ServerWebApp(String name, String homeDir, Configuration config) {
073    super(name, homeDir, config);
074  }
075
076  /**
077   * Constructor. Subclasses must have a default constructor specifying
078   * the server name.
079   * <p/>
080   * The server name is used to resolve the Java System properties that define
081   * the server home, config, log and temp directories.
082   * <p/>
083   * The home directory is looked in the Java System property
084   * <code>#SERVER_NAME#.home.dir</code>.
085   * <p/>
086   * The config directory is looked in the Java System property
087   * <code>#SERVER_NAME#.config.dir</code>, if not defined it resolves to
088   * the <code>#SERVER_HOME_DIR#/conf</code> directory.
089   * <p/>
090   * The log directory is looked in the Java System property
091   * <code>#SERVER_NAME#.log.dir</code>, if not defined it resolves to
092   * the <code>#SERVER_HOME_DIR#/log</code> directory.
093   * <p/>
094   * The temp directory is looked in the Java System property
095   * <code>#SERVER_NAME#.temp.dir</code>, if not defined it resolves to
096   * the <code>#SERVER_HOME_DIR#/temp</code> directory.
097   *
098   * @param name server name.
099   */
100  public ServerWebApp(String name) {
101    super(name, getHomeDir(name),
102          getDir(name, CONFIG_DIR, getHomeDir(name) + "/conf"),
103          getDir(name, LOG_DIR, getHomeDir(name) + "/log"),
104          getDir(name, TEMP_DIR, getHomeDir(name) + "/temp"), null);
105  }
106
107  /**
108   * Returns the server home directory.
109   * <p/>
110   * It is looked up in the Java System property
111   * <code>#SERVER_NAME#.home.dir</code>.
112   *
113   * @param name the server home directory.
114   *
115   * @return the server home directory.
116   */
117  static String getHomeDir(String name) {
118    String homeDir = HOME_DIR_TL.get();
119    if (homeDir == null) {
120      String sysProp = name + HOME_DIR;
121      homeDir = System.getProperty(sysProp);
122      if (homeDir == null) {
123        throw new IllegalArgumentException(MessageFormat.format("System property [{0}] not defined", sysProp));
124      }
125    }
126    return homeDir;
127  }
128
129  /**
130   * Convenience method that looks for Java System property defining a
131   * diretory and if not present defaults to the specified directory.
132   *
133   * @param name server name, used as prefix of the Java System property.
134   * @param dirType dir type, use as postfix of the Java System property.
135   * @param defaultDir the default directory to return if the Java System
136   * property <code>name + dirType</code> is not defined.
137   *
138   * @return the directory defined in the Java System property or the
139   *         the default directory if the Java System property is not defined.
140   */
141  static String getDir(String name, String dirType, String defaultDir) {
142    String sysProp = name + dirType;
143    return System.getProperty(sysProp, defaultDir);
144  }
145
146  /**
147   * Initializes the <code>ServletContextListener</code> which initializes
148   * the Server.
149   *
150   * @param event servelt context event.
151   */
152  @Override
153  public void contextInitialized(ServletContextEvent event) {
154    try {
155      init();
156    } catch (ServerException ex) {
157      event.getServletContext().log("ERROR: " + ex.getMessage());
158      throw new RuntimeException(ex);
159    }
160  }
161
162  /**
163   * Resolves the host & port InetSocketAddress the web server is listening to.
164   * <p/>
165   * This implementation looks for the following 2 properties:
166   * <ul>
167   *   <li>#SERVER_NAME#.http.hostname</li>
168   *   <li>#SERVER_NAME#.http.port</li>
169   * </ul>
170   *
171   * @return the host & port InetSocketAddress the web server is listening to.
172   * @throws ServerException thrown if any of the above 2 properties is not defined.
173   */
174  protected InetSocketAddress resolveAuthority() throws ServerException {
175    String hostnameKey = getName() + HTTP_HOSTNAME;
176    String portKey = getName() + HTTP_PORT;
177    String host = System.getProperty(hostnameKey);
178    String port = System.getProperty(portKey);
179    if (host == null) {
180      throw new ServerException(ServerException.ERROR.S13, hostnameKey);
181    }
182    if (port == null) {
183      throw new ServerException(ServerException.ERROR.S13, portKey);
184    }
185    try {
186      InetAddress add = InetAddress.getByName(host);
187      int portNum = Integer.parseInt(port);
188      return new InetSocketAddress(add, portNum);
189    } catch (UnknownHostException ex) {
190      throw new ServerException(ServerException.ERROR.S14, ex.toString(), ex);
191    }
192  }
193
194  /**
195   * Destroys the <code>ServletContextListener</code> which destroys
196   * the Server.
197   *
198   * @param event servelt context event.
199   */
200  @Override
201  public void contextDestroyed(ServletContextEvent event) {
202    destroy();
203  }
204
205  /**
206   * Returns the hostname:port InetSocketAddress the webserver is listening to.
207   *
208   * @return the hostname:port InetSocketAddress the webserver is listening to.
209   */
210  public InetSocketAddress getAuthority() throws ServerException {
211    synchronized (this) {
212      if (authority == null) {
213          authority = resolveAuthority();
214      }
215    }
216    return authority;
217  }
218
219  /**
220   * Sets an alternate hostname:port InetSocketAddress to use.
221   * <p/>
222   * For testing purposes.
223   * 
224   * @param authority alterante authority.
225   */
226  @VisibleForTesting
227  public void setAuthority(InetSocketAddress authority) {
228    this.authority = authority;
229  }
230
231
232  /**
233   *
234   */
235  public boolean isSslEnabled() {
236    return Boolean.valueOf(System.getProperty(getName() + SSL_ENABLED, "false"));
237  }
238}