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    
019    package org.apache.hadoop.lib.servlet;
020    
021    import com.google.common.annotations.VisibleForTesting;
022    import org.apache.hadoop.classification.InterfaceAudience;
023    import org.apache.hadoop.conf.Configuration;
024    import org.apache.hadoop.lib.server.Server;
025    import org.apache.hadoop.lib.server.ServerException;
026    
027    import javax.servlet.ServletContextEvent;
028    import javax.servlet.ServletContextListener;
029    import java.net.InetAddress;
030    import java.net.InetSocketAddress;
031    import java.net.UnknownHostException;
032    import 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
039    public 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    
048      private static ThreadLocal<String> HOME_DIR_TL = new ThreadLocal<String>();
049    
050      private InetSocketAddress authority;
051    
052      /**
053       * Method for testing purposes.
054       */
055      public static void setHomeDirForCurrentThread(String homeDir) {
056        HOME_DIR_TL.set(homeDir);
057      }
058    
059      /**
060       * Constructor for testing purposes.
061       */
062      protected ServerWebApp(String name, String homeDir, String configDir, String logDir, String tempDir,
063                             Configuration config) {
064        super(name, homeDir, configDir, logDir, tempDir, config);
065      }
066    
067      /**
068       * Constructor for testing purposes.
069       */
070      protected ServerWebApp(String name, String homeDir, Configuration config) {
071        super(name, homeDir, config);
072      }
073    
074      /**
075       * Constructor. Subclasses must have a default constructor specifying
076       * the server name.
077       * <p/>
078       * The server name is used to resolve the Java System properties that define
079       * the server home, config, log and temp directories.
080       * <p/>
081       * The home directory is looked in the Java System property
082       * <code>#SERVER_NAME#.home.dir</code>.
083       * <p/>
084       * The config directory is looked in the Java System property
085       * <code>#SERVER_NAME#.config.dir</code>, if not defined it resolves to
086       * the <code>#SERVER_HOME_DIR#/conf</code> directory.
087       * <p/>
088       * The log directory is looked in the Java System property
089       * <code>#SERVER_NAME#.log.dir</code>, if not defined it resolves to
090       * the <code>#SERVER_HOME_DIR#/log</code> directory.
091       * <p/>
092       * The temp directory is looked in the Java System property
093       * <code>#SERVER_NAME#.temp.dir</code>, if not defined it resolves to
094       * the <code>#SERVER_HOME_DIR#/temp</code> directory.
095       *
096       * @param name server name.
097       */
098      public ServerWebApp(String name) {
099        super(name, getHomeDir(name),
100              getDir(name, CONFIG_DIR, getHomeDir(name) + "/conf"),
101              getDir(name, LOG_DIR, getHomeDir(name) + "/log"),
102              getDir(name, TEMP_DIR, getHomeDir(name) + "/temp"), null);
103      }
104    
105      /**
106       * Returns the server home directory.
107       * <p/>
108       * It is looked up in the Java System property
109       * <code>#SERVER_NAME#.home.dir</code>.
110       *
111       * @param name the server home directory.
112       *
113       * @return the server home directory.
114       */
115      static String getHomeDir(String name) {
116        String homeDir = HOME_DIR_TL.get();
117        if (homeDir == null) {
118          String sysProp = name + HOME_DIR;
119          homeDir = System.getProperty(sysProp);
120          if (homeDir == null) {
121            throw new IllegalArgumentException(MessageFormat.format("System property [{0}] not defined", sysProp));
122          }
123        }
124        return homeDir;
125      }
126    
127      /**
128       * Convenience method that looks for Java System property defining a
129       * diretory and if not present defaults to the specified directory.
130       *
131       * @param name server name, used as prefix of the Java System property.
132       * @param dirType dir type, use as postfix of the Java System property.
133       * @param defaultDir the default directory to return if the Java System
134       * property <code>name + dirType</code> is not defined.
135       *
136       * @return the directory defined in the Java System property or the
137       *         the default directory if the Java System property is not defined.
138       */
139      static String getDir(String name, String dirType, String defaultDir) {
140        String sysProp = name + dirType;
141        return System.getProperty(sysProp, defaultDir);
142      }
143    
144      /**
145       * Initializes the <code>ServletContextListener</code> which initializes
146       * the Server.
147       *
148       * @param event servelt context event.
149       */
150      public void contextInitialized(ServletContextEvent event) {
151        try {
152          init();
153        } catch (ServerException ex) {
154          event.getServletContext().log("ERROR: " + ex.getMessage());
155          throw new RuntimeException(ex);
156        }
157      }
158    
159      /**
160       * Resolves the host & port InetSocketAddress the web server is listening to.
161       * <p/>
162       * This implementation looks for the following 2 properties:
163       * <ul>
164       *   <li>#SERVER_NAME#.http.hostname</li>
165       *   <li>#SERVER_NAME#.http.port</li>
166       * </ul>
167       *
168       * @return the host & port InetSocketAddress the web server is listening to.
169       * @throws ServerException thrown if any of the above 2 properties is not defined.
170       */
171      protected InetSocketAddress resolveAuthority() throws ServerException {
172        String hostnameKey = getName() + HTTP_HOSTNAME;
173        String portKey = getName() + HTTP_PORT;
174        String host = System.getProperty(hostnameKey);
175        String port = System.getProperty(portKey);
176        if (host == null) {
177          throw new ServerException(ServerException.ERROR.S13, hostnameKey);
178        }
179        if (port == null) {
180          throw new ServerException(ServerException.ERROR.S13, portKey);
181        }
182        try {
183          InetAddress add = InetAddress.getByName(host);
184          int portNum = Integer.parseInt(port);
185          return new InetSocketAddress(add, portNum);
186        } catch (UnknownHostException ex) {
187          throw new ServerException(ServerException.ERROR.S14, ex.toString(), ex);
188        }
189      }
190    
191      /**
192       * Destroys the <code>ServletContextListener</code> which destroys
193       * the Server.
194       *
195       * @param event servelt context event.
196       */
197      public void contextDestroyed(ServletContextEvent event) {
198        destroy();
199      }
200    
201      /**
202       * Returns the hostname:port InetSocketAddress the webserver is listening to.
203       *
204       * @return the hostname:port InetSocketAddress the webserver is listening to.
205       */
206      public InetSocketAddress getAuthority() throws ServerException {
207        synchronized (this) {
208          if (authority == null) {
209              authority = resolveAuthority();
210          }
211        }
212        return authority;
213      }
214    
215      /**
216       * Sets an alternate hostname:port InetSocketAddress to use.
217       * <p/>
218       * For testing purposes.
219       * 
220       * @param authority alterante authority.
221       */
222      @VisibleForTesting
223      public void setAuthority(InetSocketAddress authority) {
224        this.authority = authority;
225      }
226    }