Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Dispatcher.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Dispatcher.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Dispatcher.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Dispatcher.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,229 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import static com.google.common.base.Preconditions.*;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.yarn.webapp.Controller.RequestContext;
+import org.apache.hadoop.yarn.webapp.Router.Dest;
+import org.apache.hadoop.yarn.webapp.view.ErrorPage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The servlet that dispatch request to various controllers
+ * according to the user defined routes in the router.
+ */
+@Singleton
+public class Dispatcher extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+  static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class);
+  static final String ERROR_COOKIE = "last-error";
+  static final String STATUS_COOKIE = "last-status";
+
+  private transient final Injector injector;
+  private transient final Router router;
+  private transient final WebApp webApp;
+  private volatile boolean devMode = false;
+
+  @Inject
+  Dispatcher(WebApp webApp, Injector injector, Router router) {
+    this.webApp = webApp;
+    this.injector = injector;
+    this.router = router;
+  }
+
+  @Override
+  public void doOptions(HttpServletRequest req, HttpServletResponse res) {
+    // for simplicity
+    res.setHeader("Allow", "GET, POST");
+  }
+
+  @Override
+  public void service(HttpServletRequest req, HttpServletResponse res)
+      throws ServletException, IOException {
+    res.setCharacterEncoding("UTF-8");
+    String uri = req.getRequestURI();
+    if (uri == null) {
+      uri = "/";
+    }
+    if (devMode && uri.equals("/__stop")) {
+      // quick hack to restart servers in dev mode without OS commands
+      res.setStatus(res.SC_NO_CONTENT);
+      LOG.info("dev mode restart requested");
+      prepareToExit();
+      return;
+    }
+    String method = req.getMethod();
+    if (method.equals("OPTIONS")) {
+      doOptions(req, res);
+      return;
+    }
+    if (method.equals("TRACE")) {
+      doTrace(req, res);
+      return;
+    }
+    if (method.equals("HEAD")) {
+      doGet(req, res); // default to bad request
+      return;
+    }
+    String pathInfo = req.getPathInfo();
+    if (pathInfo == null) {
+      pathInfo = "/";
+    }
+    Controller.RequestContext rc =
+        injector.getInstance(Controller.RequestContext.class);
+    if (setCookieParams(rc, req) > 0) {
+      Cookie ec = rc.cookies().get(ERROR_COOKIE);
+      if (ec != null) {
+        rc.setStatus(Integer.parseInt(rc.cookies().
+            get(STATUS_COOKIE).getValue()));
+        removeErrorCookies(res, uri);
+        rc.set(Params.ERROR_DETAILS, ec.getValue());
+        render(ErrorPage.class);
+        return;
+      }
+    }
+    rc.prefix = webApp.name();
+    Router.Dest dest = null;
+    try {
+      dest = router.resolve(method, pathInfo);
+    } catch (WebAppException e) {
+      rc.error = e;
+      if (!e.getMessage().contains("not found")) {
+        rc.setStatus(res.SC_INTERNAL_SERVER_ERROR);
+        render(ErrorPage.class);
+        return;
+      }
+    }
+    if (dest == null) {
+      rc.setStatus(res.SC_NOT_FOUND);
+      render(ErrorPage.class);
+      return;
+    }
+    rc.devMode = devMode;
+    setMoreParams(rc, pathInfo, dest);
+    Controller controller = injector.getInstance(dest.controllerClass);
+    try {
+      // TODO: support args converted from /path/:arg1/...
+      dest.action.invoke(controller, (Object[]) null);
+      if (!rc.rendered) {
+        if (dest.defaultViewClass != null) {
+          render(dest.defaultViewClass);
+        } else if (rc.status == 200) {
+          throw new IllegalStateException("No view rendered for 200");
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("error handling URI: "+ uri, e);
+      // Page could be half rendered (but still not flushed). So redirect.
+      redirectToErrorPage(res, e, uri, devMode);
+    }
+  }
+
+  public static void redirectToErrorPage(HttpServletResponse res, Throwable e,
+                                         String path, boolean devMode) {
+    String st = devMode ? ErrorPage.toStackTrace(e, 1024 * 3) // spec: min 4KB
+                        : "See logs for stack trace";
+    res.setStatus(res.SC_FOUND);
+    Cookie cookie = new Cookie(STATUS_COOKIE, String.valueOf(500));
+    cookie.setPath(path);
+    res.addCookie(cookie);
+    cookie = new Cookie(ERROR_COOKIE, st);
+    cookie.setPath(path);
+    res.addCookie(cookie);
+    res.setHeader("Location", path);
+  }
+
+  public static void removeErrorCookies(HttpServletResponse res, String path) {
+    removeCookie(res, ERROR_COOKIE, path);
+    removeCookie(res, STATUS_COOKIE, path);
+  }
+
+  public static void removeCookie(HttpServletResponse res, String name,
+                                  String path) {
+    LOG.debug("removing cookie {} on {}", name, path);
+    Cookie c = new Cookie(name, "");
+    c.setMaxAge(0);
+    c.setPath(path);
+    res.addCookie(c);
+  }
+
+  private void render(Class<? extends View> cls) {
+    injector.getInstance(cls).render();
+  }
+
+  // /path/foo/bar with /path/:arg1/:arg2 will set {arg1=>foo, arg2=>bar}
+  private void setMoreParams(RequestContext rc, String pathInfo, Dest dest) {
+    checkState(pathInfo.startsWith(dest.prefix), "prefix should match");
+    if (dest.pathParams.size() == 0 ||
+        dest.prefix.length() == pathInfo.length()) {
+      return;
+    }
+    String[] parts = Iterables.toArray(WebApp.pathSplitter.split(
+        pathInfo.substring(dest.prefix.length())), String.class);
+    LOG.debug("parts={}, params={}", parts, dest.pathParams);
+    for (int i = 0; i < dest.pathParams.size() && i < parts.length; ++i) {
+      String key = dest.pathParams.get(i);
+      if (key.charAt(0) == ':') {
+        rc.moreParams().put(key.substring(1), parts[i]);
+      }
+    }
+  }
+
+  private int setCookieParams(RequestContext rc, HttpServletRequest req) {
+    Cookie[] cookies = req.getCookies();
+    if (cookies != null) {
+      for (Cookie cookie : cookies) {
+        rc.cookies().put(cookie.getName(), cookie);
+      }
+      return cookies.length;
+    }
+    return 0;
+  }
+
+  public void setDevMode(boolean choice) {
+    devMode = choice;
+  }
+
+  private void prepareToExit() {
+    checkState(devMode, "only in dev mode");
+    new Timer("webapp exit", true).schedule(new TimerTask() {
+      @Override public void run() {
+        LOG.info("WebAppp /{} exiting...", webApp.name());
+        webApp.stop();
+        System.exit(0); // FINDBUG: this is intended in dev mode
+      }
+    }, 18); // enough time for the last local request to complete
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/MimeType.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/MimeType.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/MimeType.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/MimeType.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,28 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+public interface MimeType {
+
+  public static final String TEXT = "text/plain; charset=UTF-8";
+  public static final String HTML = "text/html; charset=UTF-8";
+  public static final String XML = "text/xml; charset=UTF-8";
+  public static final String HTTP = "message/http; charset=UTF-8";
+  public static final String JSON = "application/json; charset=UTF-8";
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Params.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Params.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Params.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Params.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,30 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+/**
+ * Public static constants for webapp parameters. Do NOT put any
+ * private or application specific constants here as they're part of
+ * the API for users of the controllers and views.
+ */
+public interface Params {
+  static final String TITLE = "title";
+  static final String TITLE_LINK = "title.href";
+  static final String ERROR_DETAILS = "error.details";
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ResponseInfo.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ResponseInfo.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ResponseInfo.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ResponseInfo.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,87 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import com.google.common.collect.Lists;
+import com.google.inject.servlet.RequestScoped;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A class to help passing around request scoped info
+ */
+@RequestScoped
+public class ResponseInfo implements Iterable<ResponseInfo.Item> {
+
+  public static class Item {
+    public final String key;
+    public final String url;
+    public final Object value;
+
+    Item(String key, String url, Object value) {
+      this.key = key;
+      this.url = url;
+      this.value = value;
+    }
+
+    public static Item of(String key, Object value) {
+      return new Item(key, null, value);
+    }
+
+    public static Item of(String key, String url, Object value) {
+      return new Item(key, url, value);
+    }
+  }
+
+  final List<Item> items = Lists.newArrayList();
+  String about = "Info";
+
+  // Do NOT add any constructors here, unless...
+
+  public static ResponseInfo $about(String about) {
+    ResponseInfo info = new ResponseInfo();
+    info.about = about;
+    return info;
+  }
+
+  public ResponseInfo about(String about) {
+    this.about = about;
+    return this;
+  }
+
+  public String about() {
+    return about;
+  }
+
+  public ResponseInfo _(String key, Object value) {
+    items.add(Item.of(key, value));
+    return this;
+  }
+
+  public ResponseInfo _(String key, String url, Object anchor) {
+    items.add(Item.of(key, url, anchor));
+    return this;
+  }
+
+  @Override
+  public Iterator<Item> iterator() {
+    return items.iterator();
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/Router.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,286 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import com.google.common.base.CharMatcher;
+import static com.google.common.base.Preconditions.*;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+
+import static org.apache.hadoop.yarn.util.StringHelper.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages path info to controller#action routing.
+ */
+class Router {
+  static final Logger LOG = LoggerFactory.getLogger(Router.class);
+  static final ImmutableList<String> EMPTY_LIST = ImmutableList.of();
+  static final CharMatcher SLASH = CharMatcher.is('/');
+  static final Pattern controllerRe =
+      Pattern.compile("^/[A-Za-z_]\\w*(?:/.*)?");
+
+  static class Dest {
+    final String prefix;
+    final ImmutableList<String> pathParams;
+    final Method action;
+    final Class<? extends Controller> controllerClass;
+    Class<? extends View> defaultViewClass;
+    final EnumSet<WebApp.HTTP> methods;
+
+    Dest(String path, Method method, Class<? extends Controller> cls,
+         List<String> pathParams, WebApp.HTTP httpMethod) {
+      prefix = checkNotNull(path);
+      action = checkNotNull(method);
+      controllerClass = checkNotNull(cls);
+      this.pathParams = pathParams != null ? ImmutableList.copyOf(pathParams)
+                                           : EMPTY_LIST;
+      methods = EnumSet.of(httpMethod);
+    }
+  }
+
+  Class<?> hostClass; // starting point to look for default classes
+
+  final TreeMap<String, Dest> routes = Maps.newTreeMap(); // path->dest
+
+  /**
+   * Add a route to the router.
+   * e.g., add(GET, "/foo/show", FooController.class, "show", [name...]);
+   * The name list is from /foo/show/:name/...
+   */
+  synchronized Dest add(WebApp.HTTP httpMethod, String path,
+                        Class<? extends Controller> cls,
+                        String action, List<String> names) {
+    LOG.debug("adding {}({})->{}#{}", new Object[]{path, names, cls, action});
+    Dest dest = addController(httpMethod, path, cls, action, names);
+    addDefaultView(dest);
+    return dest;
+  }
+
+  private Dest addController(WebApp.HTTP httpMethod, String path,
+                             Class<? extends Controller> cls,
+                             String action, List<String> names) {
+    for (Method method : cls.getDeclaredMethods()) {
+      if (method.getName().equals(action) &&
+          method.getParameterTypes().length == 0 &&
+          Modifier.isPublic(method.getModifiers())) {
+        // TODO: deal with parameters using the names
+        Dest dest = routes.get(path);
+        if (dest == null) {
+          method.setAccessible(true); // avoid any runtime checks
+          dest = new Dest(path, method, cls, names, httpMethod);
+          routes.put(path, dest);
+          return dest;
+        }
+        dest.methods.add(httpMethod);
+        return dest;
+      }
+    }
+    throw new WebAppException(action + "() not found in " + cls);
+  }
+
+  private void addDefaultView(Dest dest) {
+    String controllerName = dest.controllerClass.getSimpleName();
+    if (controllerName.endsWith("Controller")) {
+      controllerName = controllerName.substring(0,
+          controllerName.length() - 10);
+    }
+    dest.defaultViewClass = find(View.class,
+                                 dest.controllerClass.getPackage().getName(),
+                                 join(controllerName + "View"));
+  }
+
+  void setHostClass(Class<?> cls) {
+    hostClass = cls;
+  }
+
+  /**
+   * Resolve a path to a destination.
+   */
+  synchronized Dest resolve(String httpMethod, String path) {
+    WebApp.HTTP method = WebApp.HTTP.valueOf(httpMethod); // can throw
+    Dest dest = lookupRoute(method, path);
+    if (dest == null) {
+      return resolveDefault(method, path);
+    }
+    return dest;
+  }
+
+  private Dest lookupRoute(WebApp.HTTP method, String path) {
+    String key = path;
+    do {
+      Dest dest = routes.get(key);
+      if (dest != null && methodAllowed(method, dest)) {
+        if ((Object)key == path) { // shut up warnings
+          LOG.debug("exact match for {}: {}", key, dest.action);
+          return dest;
+        } else if (isGoodMatch(dest, path)) {
+          LOG.debug("prefix match2 for {}: {}", key, dest.action);
+          return dest;
+        }
+        return resolveAction(method, dest, path);
+      }
+      Map.Entry<String, Dest> lower = routes.lowerEntry(key);
+      if (lower == null) {
+        return null;
+      }
+      dest = lower.getValue();
+      if (prefixMatches(dest, path)) {
+        if (methodAllowed(method, dest)) {
+          if (isGoodMatch(dest, path)) {
+            LOG.debug("prefix match for {}: {}", lower.getKey(), dest.action);
+            return dest;
+          }
+          return resolveAction(method, dest, path);
+        }
+        // check other candidates
+        int slashPos = key.lastIndexOf('/');
+        key = slashPos > 0 ? path.substring(0, slashPos) : "/";
+      } else {
+        key = "/";
+      }
+    } while (true);
+  }
+
+  static boolean methodAllowed(WebApp.HTTP method, Dest dest) {
+    // Accept all methods by default, unless explicity configured otherwise.
+    return dest.methods.contains(method) || (dest.methods.size() == 1 &&
+           dest.methods.contains(WebApp.HTTP.GET));
+  }
+
+  static boolean prefixMatches(Dest dest, String path) {
+    LOG.debug("checking prefix {}{} for path: {}", new Object[]{dest.prefix,
+              dest.pathParams, path});
+    if (!path.startsWith(dest.prefix)) {
+      return false;
+    }
+    int prefixLen = dest.prefix.length();
+    if (prefixLen > 1 && path.length() > prefixLen &&
+        path.charAt(prefixLen) != '/') {
+      return false;
+    }
+    // prefix is / or prefix is path or prefix/...
+    return true;
+  }
+
+  static boolean isGoodMatch(Dest dest, String path) {
+    if (SLASH.countIn(dest.prefix) > 1) {
+      return true;
+    }
+    // We want to match (/foo, :a) for /foo/bar/blah and (/, :a) for /123
+    // but NOT / for /foo or (/, :a) for /foo or /foo/ because default route
+    // (FooController#index) for /foo and /foo/ takes precedence.
+    if (dest.prefix.length() == 1) {
+      return dest.pathParams.size() > 0 && !maybeController(path);
+    }
+    return dest.pathParams.size() > 0 || // /foo should match /foo/
+        (path.endsWith("/") && SLASH.countIn(path) == 2);
+  }
+
+  static boolean maybeController(String path) {
+    return controllerRe.matcher(path).matches();
+  }
+
+  // Assume /controller/action style path
+  private Dest resolveDefault(WebApp.HTTP method, String path) {
+    List<String> parts = WebApp.parseRoute(path);
+    String controller = parts.get(WebApp.R_CONTROLLER);
+    String action = parts.get(WebApp.R_ACTION);
+    // NameController is encouraged default
+    Class<? extends Controller> cls = find(Controller.class,
+                                           join(controller, "Controller"));
+    if (cls == null) {
+      cls = find(Controller.class, controller);
+    }
+    if (cls == null) {
+      throw new WebAppException(join(path, ": controller for ", controller,
+                                " not found"));
+    }
+    return add(method, defaultPrefix(controller, action), cls, action, null);
+  }
+
+  private String defaultPrefix(String controller, String action) {
+    if (controller.equals("default") && action.equals("index")) {
+      return "/";
+    }
+    if (action.equals("index")) {
+      return join('/', controller);
+    }
+    return pjoin("", controller, action);
+  }
+
+  private <T> Class<? extends T> find(Class<T> cls, String cname) {
+    String pkg = hostClass.getPackage().getName();
+    return find(cls, pkg, cname);
+  }
+
+  private <T> Class<? extends T> find(Class<T> cls, String pkg, String cname) {
+    String name = StringUtils.capitalize(cname);
+    Class<? extends T> found = load(cls, djoin(pkg, name));
+    if (found == null) {
+      found = load(cls, djoin(pkg, "webapp", name));
+    }
+    if (found == null) {
+      found = load(cls, join(hostClass.getName(), '$', name));
+    }
+    return found;
+  }
+
+  @SuppressWarnings("unchecked")
+  private <T> Class<? extends T> load(Class<T> cls, String className) {
+    LOG.debug("trying: {}", className);
+    try {
+      Class<?> found = Class.forName(className);
+      if (cls.isAssignableFrom(found)) {
+        LOG.debug("found {}", className);
+        return (Class<? extends T>) found;
+      }
+      LOG.warn("found a {} but it's not a {}", className, cls.getName());
+    } catch (ClassNotFoundException e) {
+      // OK in this case.
+    }
+    return null;
+  }
+
+  // Dest may contain a candidate controller
+  private Dest resolveAction(WebApp.HTTP method, Dest dest, String path) {
+    if (dest.prefix.length() == 1) {
+      return null;
+    }
+    checkState(!isGoodMatch(dest, path), dest.prefix);
+    checkState(SLASH.countIn(path) > 1, path);
+    List<String> parts = WebApp.parseRoute(path);
+    String controller = parts.get(WebApp.R_CONTROLLER);
+    String action = parts.get(WebApp.R_ACTION);
+    return add(method, pjoin("", controller, action), dest.controllerClass,
+               action, null);
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/SubView.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/SubView.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/SubView.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/SubView.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,29 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+/**
+ * Interface for SubView to avoid top-level inclusion
+ */
+public interface SubView {
+  /**
+   * render the sub-view
+   */
+  void renderPartial();
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ToJSON.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ToJSON.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ToJSON.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/ToJSON.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,28 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import java.io.PrintWriter;
+
+/**
+ * A light-weight JSON rendering interface
+ */
+public interface ToJSON {
+  void toJSON(PrintWriter out);
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/View.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/View.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/View.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/View.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,211 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.servlet.RequestScoped;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.apache.hadoop.yarn.util.StringHelper.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class for all views
+ */
+public abstract class View implements Params {
+  public static final Logger LOG = LoggerFactory.getLogger(View.class);
+
+  @RequestScoped
+  public static class ViewContext {
+    final Controller.RequestContext rc;
+    int nestLevel = 0;
+    boolean wasInline;
+
+    @Inject ViewContext(Controller.RequestContext ctx) {
+      rc = ctx;
+    }
+
+    public int nestLevel() { return nestLevel; }
+    public boolean wasInline() { return wasInline; }
+
+    public void set(int nestLevel, boolean wasInline) {
+      this.nestLevel = nestLevel;
+      this.wasInline = wasInline;
+    }
+
+    public Controller.RequestContext requestContext() { return rc; }
+  }
+
+  private ViewContext vc;
+  @Inject Injector injector;
+
+  public View() {
+    // Makes injection in subclasses optional.
+    // Time will tell if this buy us more than the NPEs :)
+  }
+
+  public View(ViewContext ctx) {
+    vc = ctx;
+  }
+
+  /**
+   * The API to render the view
+   */
+  public abstract void render();
+
+  public ViewContext context() {
+    if (vc == null) {
+      if (injector == null) {
+        // One downside of making the injection in subclasses optional
+        throw new WebAppException(join("Error accessing ViewContext from a\n",
+            "child constructor, either move the usage of the View methods\n",
+            "out of the constructor or inject the ViewContext into the\n",
+            "constructor"));
+      }
+      vc = injector.getInstance(ViewContext.class);
+    }
+    return vc;
+  }
+
+  public Throwable error() { return context().rc.error; }
+
+  public int status() { return context().rc.status; }
+
+  public boolean inDevMode() { return context().rc.devMode; }
+
+  public Injector injector() { return context().rc.injector; }
+
+  public HttpServletRequest request() {
+    return context().rc.request;
+  }
+
+  public HttpServletResponse response() {
+    return context().rc.response;
+  }
+
+  public Map<String, String> moreParams() {
+    return context().rc.moreParams();
+  }
+
+  /**
+   * Get the cookies
+   * @return the cookies map
+   */
+  public Map<String, Cookie> cookies() {
+    return context().rc.cookies();
+  }
+
+  public ServletOutputStream outputStream() {
+    try {
+      return response().getOutputStream();
+    } catch (IOException e) {
+      throw new WebAppException(e);
+    }
+  }
+
+  public PrintWriter writer() {
+    try {
+      return response().getWriter();
+    } catch (IOException e) {
+      throw new WebAppException(e);
+    }
+  }
+
+  /**
+   * Lookup a value from the current context.
+   * @param key to lookup
+   * @param defaultValue if key is missing
+   * @return the value of the key or the default value
+   */
+  public String $(String key, String defaultValue) {
+    // moreParams take precedence
+    String value = moreParams().get(key);
+    if (value == null) {
+      value = request().getParameter(key);
+    }
+    return value == null ? defaultValue : value;
+  }
+
+  /**
+   * Lookup a value from the current context
+   * @param key to lookup
+   * @return the value of the key or empty string
+   */
+  public String $(String key) {
+    return $(key, "");
+  }
+
+  /**
+   * Set a context value. (e.g. UI properties for sub views.)
+   * Try to avoid any application (vs view/ui) logic.
+   * @param key to set
+   * @param value to set
+   */
+  public void set(String key, String value) {
+    moreParams().put(key, value);
+  }
+
+  public String prefix() {
+    return context().rc.prefix;
+  }
+
+  public void setTitle(String title) {
+    set(TITLE, title);
+  }
+
+  public void setTitle(String title, String url) {
+    setTitle(title);
+    set(TITLE_LINK, url);
+  }
+
+  /**
+   * Create an url from url components
+   * @param parts components to join
+   * @return an url string
+   */
+  public String url(String... parts) {
+    return ujoin(prefix(), parts);
+  }
+
+  public ResponseInfo info(String about) {
+    return injector().getInstance(ResponseInfo.class).about(about);
+  }
+
+  /**
+   * Render a sub-view
+   * @param cls the class of the sub-view
+   */
+  public void render(Class<? extends SubView> cls) {
+    int saved = context().nestLevel;
+    injector().getInstance(cls).renderPartial();
+    if (context().nestLevel != saved) {
+      throw new WebAppException("View "+ cls.getSimpleName() +" not complete");
+    }
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApp.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,202 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import com.google.common.base.CharMatcher;
+import static com.google.common.base.Preconditions.*;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import com.google.inject.Provides;
+import com.google.inject.servlet.GuiceFilter;
+import com.google.inject.servlet.ServletModule;
+
+import java.util.List;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.HttpServer;
+import org.apache.hadoop.yarn.util.StringHelper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @see WebApps for a usage example
+ */
+public abstract class WebApp extends ServletModule {
+  private static final Logger LOG = LoggerFactory.getLogger(WebApp.class);
+
+  public enum HTTP { GET, POST, HEAD, PUT, DELETE };
+
+  private volatile String name;
+  private volatile Configuration conf;
+  private volatile HttpServer httpServer;
+  private volatile GuiceFilter guiceFilter;
+  private final Router router = new Router();
+
+  // index for the parsed route result
+  static final int R_PATH = 0;
+  static final int R_CONTROLLER = 1;
+  static final int R_ACTION = 2;
+  static final int R_PARAMS = 3;
+
+  static final Splitter pathSplitter =
+      Splitter.on('/').trimResults().omitEmptyStrings();
+
+  void setHttpServer(HttpServer server) {
+    httpServer = checkNotNull(server, "http server");
+  }
+
+  @Provides public HttpServer httpServer() { return httpServer; }
+
+  public int port() {
+    return checkNotNull(httpServer, "httpServer").getPort();
+  }
+
+  public void stop() {
+    try {
+      checkNotNull(httpServer, "httpServer").stop();
+      checkNotNull(guiceFilter, "guiceFilter").destroy();
+    }
+    catch (Exception e) {
+      throw new WebAppException(e);
+    }
+  }
+
+  public void joinThread() {
+    try {
+      checkNotNull(httpServer, "httpServer").join();
+    } catch (InterruptedException e) {
+      LOG.info("interrupted", e);
+    }
+  }
+
+  void setConf(Configuration conf) { this.conf = conf; }
+
+  @Provides public Configuration conf() { return conf; }
+
+  @Provides Router router() { return router; }
+
+  @Provides WebApp webApp() { return this; }
+
+  void setName(String name) { this.name = name; }
+
+  public String name() { return this.name; }
+
+  void setHostClass(Class<?> cls) {
+    router.setHostClass(cls);
+  }
+
+  void setGuiceFilter(GuiceFilter instance) {
+    guiceFilter = instance;
+  }
+
+  @Override
+  public void configureServlets() {
+    setup();
+    serve("/", "/__stop", StringHelper.join('/', name, 
'*')).with(Dispatcher.class);
+  }
+
+  /**
+   * Setup of a webapp serving route.
+   * @param method  the http method for the route
+   * @param pathSpec  the path spec in the form of /controller/action/:args 
etc.
+   * @param cls the controller class
+   * @param action the controller method
+   */
+  public void route(HTTP method, String pathSpec,
+                    Class<? extends Controller> cls, String action) {
+    List<String> res = parseRoute(pathSpec);
+    router.add(method, res.get(R_PATH), cls, action,
+               res.subList(R_PARAMS, res.size()));
+  }
+
+  public void route(String pathSpec, Class<? extends Controller> cls,
+                    String action) {
+    route(HTTP.GET, pathSpec, cls, action);
+  }
+
+  public void route(String pathSpec, Class<? extends Controller> cls) {
+    List<String> res = parseRoute(pathSpec);
+    router.add(HTTP.GET, res.get(R_PATH), cls, res.get(R_ACTION),
+               res.subList(R_PARAMS, res.size()));
+  }
+
+
+  /**
+   * /controller/action/:args => [/controller/action, controller, action, args]
+   * /controller/:args => [/controller, controller, index, args]
+   */
+  static List<String> parseRoute(String pathSpec) {
+    List<String> result = Lists.newArrayList();
+    result.add(getPrefix(checkNotNull(pathSpec, "pathSpec")));
+    Iterable<String> parts = pathSplitter.split(pathSpec);
+    String controller = null, action = null;
+    for (String s : parts) {
+      if (controller == null) {
+        if (s.charAt(0) == ':') {
+          controller = "default";
+          result.add(controller);
+          action = "index";
+          result.add(action);
+        } else {
+          controller = s;
+        }
+      } else if (action == null) {
+        if (s.charAt(0) == ':') {
+          action = "index";
+          result.add(action);
+        } else {
+          action = s;
+        }
+      }
+      result.add(s);
+    }
+    if (controller == null) {
+      result.add("default");
+    }
+    if (action == null) {
+      result.add("index");
+    }
+    return result;
+  }
+
+  static String getPrefix(String pathSpec) {
+    int start = 0;
+    while (CharMatcher.WHITESPACE.matches(pathSpec.charAt(start))) {
+      ++start;
+    }
+    if (pathSpec.charAt(start) != '/') {
+      throw new WebAppException("Path spec syntax error: "+ pathSpec);
+    }
+    int ci = pathSpec.indexOf(':');
+    if (ci == -1) {
+      ci = pathSpec.length();
+    }
+    if (ci == 1) {
+      return "/";
+    }
+    char c;
+    do {
+      c = pathSpec.charAt(--ci);
+    } while (c == '/' || CharMatcher.WHITESPACE.matches(c));
+    return pathSpec.substring(start, ci + 1);
+  }
+
+  public abstract void setup();
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebAppException.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebAppException.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebAppException.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebAppException.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,38 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import org.apache.hadoop.yarn.YarnException;
+
+public class WebAppException extends YarnException {
+
+  private static final long serialVersionUID = 1L;
+
+  public WebAppException(String msg) {
+    super(msg);
+  }
+
+  public WebAppException(Throwable cause) {
+    super(cause);
+  }
+
+  public WebAppException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,222 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp;
+
+import static com.google.common.base.Preconditions.*;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.servlet.GuiceFilter;
+
+import java.net.ConnectException;
+import java.net.URL;
+import org.apache.commons.lang.StringUtils;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.HttpServer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helpers to create an embedded webapp.
+ *
+ * <h4>Quick start:</h4>
+ * <pre>
+ *   WebApp wa = WebApps.$for(myApp).start();</pre>
+ * Starts a webapp with default routes binds to 0.0.0.0 (all network 
interfaces)
+ * on an ephemeral port, which can be obtained with:<pre>
+ *   int port = wa.port();</pre>
+ * <h4>With more options:</h4>
+ * <pre>
+ *   WebApp wa = WebApps.$for(myApp).at(address, port).
+ *                        with(configuration).
+ *                        start(new WebApp() {
+ *     &#064;Override public void setup() {
+ *       route("/foo/action", FooController.class);
+ *       route("/foo/:id", FooController.class, "show");
+ *     }
+ *   });</pre>
+ */
+public class WebApps {
+  static final Logger LOG = LoggerFactory.getLogger(WebApps.class);
+
+  public static class Builder<T> {
+    final String name;
+    final Class<T> api;
+    final T application;
+    String bindAddress = "0.0.0.0";
+    int port = 0;
+    boolean findPort = false;
+    Configuration conf;
+    boolean devMode = false;
+    Module[] modules;
+
+    Builder(String name, Class<T> api, T application) {
+      this.name = name;
+      this.api = api;
+      this.application = application;
+    }
+
+    public Builder<T> at(String bindAddress) {
+      String[] parts = StringUtils.split(bindAddress, ':');
+      if (parts.length == 2) {
+        return at(parts[0], Integer.parseInt(parts[1]), true);
+      }
+      return at(bindAddress, 0, true);
+    }
+
+    public Builder<T> at(int port) {
+      return at("0.0.0.0", port, false);
+    }
+
+    public Builder<T> at(String address, int port, boolean findPort) {
+      this.bindAddress = checkNotNull(address, "bind address");
+      this.port = port;
+      this.findPort = findPort;
+      return this;
+    }
+
+    public Builder<T> with(Configuration conf) {
+      this.conf = conf;
+      return this;
+    }
+
+    public Builder<T> with(Module... modules) {
+      this.modules = modules; // OK
+      return this;
+    }
+
+    public Builder<T> inDevMode() {
+      devMode = true;
+      return this;
+    }
+
+    public WebApp start(WebApp webapp) {
+      if (webapp == null) {
+        webapp = new WebApp() {
+          @Override
+          public void setup() {
+            // Defaults should be fine in usual cases
+          }
+        };
+      }
+      webapp.setName(name);
+      if (conf == null) {
+        conf = new Configuration();
+      }
+      try {
+        if (application != null) {
+          webapp.setHostClass(application.getClass());
+        } else {
+          String cls = inferHostClass();
+          LOG.debug("setting webapp host class to {}", cls);
+          webapp.setHostClass(Class.forName(cls));
+        }
+        if (devMode) {
+          if (port > 0) {
+            try {
+              new URL("http://localhost:"+ port +"/__stop").getContent();
+              LOG.info("stopping existing webapp instance");
+              Thread.sleep(100);
+            } catch (ConnectException e) {
+              LOG.info("no existing webapp instance found: {}", e.toString());
+            } catch (Exception e) {
+              // should not be fatal
+              LOG.warn("error stopping existing instance: {}", e.toString());
+            }
+          } else {
+            LOG.error("dev mode does NOT work with ephemeral port!");
+            System.exit(1);
+          }
+        }
+        HttpServer server =
+            new HttpServer(name, bindAddress, port, findPort, conf);
+        server.addGlobalFilter("guice", GuiceFilter.class.getName(), null);
+        webapp.setConf(conf);
+        webapp.setHttpServer(server);
+        server.start();
+        LOG.info("Web app /"+ name +" started at "+ server.getPort());
+      } catch (Exception e) {
+        throw new WebAppException("Error starting http server", e);
+      }
+      Injector injector = Guice.createInjector(webapp, new AbstractModule() {
+        @Override @SuppressWarnings("unchecked")
+        protected void configure() {
+          if (api != null) {
+            bind(api).toInstance(application);
+          }
+        }
+      });
+      LOG.info("Registered webapp guice modules");
+      // save a guice filter instance for webapp stop (mostly for unit tests)
+      webapp.setGuiceFilter(injector.getInstance(GuiceFilter.class));
+      if (devMode) {
+        injector.getInstance(Dispatcher.class).setDevMode(devMode);
+        LOG.info("in dev mode!");
+      }
+      return webapp;
+    }
+
+    public WebApp start() {
+      return start(null);
+    }
+
+    private String inferHostClass() {
+      String thisClass = this.getClass().getName();
+      Throwable t = new Throwable();
+      for (StackTraceElement e : t.getStackTrace()) {
+        if (e.getClassName().equals(thisClass)) continue;
+        return e.getClassName();
+      }
+      LOG.warn("could not infer host class from", t);
+      return thisClass;
+    }
+  }
+
+  /**
+   * Create a new webapp builder.
+   * @see WebApps for a complete example
+   * @param <T> application (holding the embedded webapp) type
+   * @param prefix of the webapp
+   * @param api the api class for the application
+   * @param app the application instance
+   * @return a webapp builder
+   */
+  public static <T> Builder<T> $for(String prefix, Class<T> api, T app) {
+    return new Builder<T>(prefix, api, app);
+  }
+
+  // Short cut mostly for tests/demos
+  @SuppressWarnings("unchecked")
+  public static <T> Builder<T> $for(String prefix, T app) {
+    return $for(prefix, (Class<T>)app.getClass(), app);
+  }
+
+  // Ditto
+  @SuppressWarnings("unchecked")
+  public static <T> Builder<T> $for(T app) {
+    return $for("", app);
+  }
+
+  public static <T> Builder<T> $for(String prefix) {
+    return $for(prefix, null, null);
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/HelloWorld.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/HelloWorld.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/HelloWorld.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/HelloWorld.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,53 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp.example;
+
+import org.apache.hadoop.yarn.webapp.Controller;
+import org.apache.hadoop.yarn.webapp.WebApps;
+import org.apache.hadoop.yarn.webapp.view.HtmlPage;
+
+/**
+ * The obligatory example. No xml/jsp/templates/config files! No
+ * proliferation of strange annotations either :)
+ *
+ * <p>3 in 1 example. Check results at
+ * <br>http://localhost:8888/hello and
+ * <br>http://localhost:8888/hello/html
+ * <br>http://localhost:8888/hello/json
+ */
+public class HelloWorld {
+  public static class Hello extends Controller {
+    @Override public void index() { renderText("Hello world!"); }
+    public void html() { setTitle("Hello world!"); }
+    public void json() { renderJSON("Hello world!"); }
+  }
+
+  public static class HelloView extends HtmlPage {
+    @Override protected void render(Page.HTML<_> html) {
+      html. // produces valid html 4.01 strict
+        title($("title")).
+        p("#hello-for-css").
+          _($("title"))._()._();
+    }
+  }
+
+  public static void main(String[] args) {
+    WebApps.$for(new HelloWorld()).at(8888).inDevMode().start().joinThread();
+  }
+}

Added: 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/MyApp.java
URL: 
http://svn.apache.org/viewvc/hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/MyApp.java?rev=1082677&view=auto
==============================================================================
--- 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/MyApp.java
 (added)
+++ 
hadoop/mapreduce/branches/MR-279/yarn/yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/example/MyApp.java
 Thu Mar 17 20:21:13 2011
@@ -0,0 +1,75 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you 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 org.apache.hadoop.yarn.webapp.example;
+
+import com.google.inject.Inject;
+
+import org.apache.hadoop.yarn.webapp.Controller;
+import org.apache.hadoop.yarn.webapp.WebApps;
+import org.apache.hadoop.yarn.webapp.view.HtmlPage;
+
+/**
+ * The embedded UI serves two pages at:
+ * <br>http://localhost:8888/my and
+ * <br>http://localhost:8888/my/anythingYouWant
+ */
+public class MyApp {
+
+  // This is an app API
+  public String anyAPI() { return "anything ☁, really!"; }
+
+  // Note this is static so it can be in any files.
+  public static class MyController extends Controller {
+    final MyApp app;
+
+    // The app injection is optional
+    @Inject MyController(MyApp app, RequestContext ctx) {
+      super(ctx);
+      this.app = app;
+    }
+
+    @Override
+    public void index() {
+      set("anything", "something ☯");
+    }
+
+    public void anythingYouWant() {
+      set("anything", app.anyAPI());
+    }
+  }
+
+  // Ditto
+  public static class MyView extends HtmlPage {
+    // You can inject the app in views if needed.
+    @Override
+    public void render(Page.HTML<_> html) {
+      html.
+        title("My App").
+        p("#content_id_for_css_styling").
+          _("You can have", $("anything"))._()._();
+      // Note, there is no _(); (to parent element) method at root level.
+      // and IDE provides instant feedback on what level you're on in
+      // the auto-completion drop-downs.
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    WebApps.$for(new MyApp()).at(8888).inDevMode().start().joinThread();
+  }
+}


Reply via email to