You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mapreduce-commits@hadoop.apache.org by ac...@apache.org on 2011/03/17 21:21:54 UTC
svn commit: r1082677 [21/38] - in /hadoop/mapreduce/branches/MR-279: ./
assembly/ ivy/ mr-client/ mr-client/hadoop-mapreduce-client-app/
mr-client/hadoop-mapreduce-client-app/src/
mr-client/hadoop-mapreduce-client-app/src/main/ mr-client/hadoop-mapredu...
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() {
+ * @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();
+ }
+}