/*
* Copyright 2012 LinkedIn Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package azkaban.webapp.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.ServletConfig;
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.joda.time.DateTime;
import azkaban.server.AzkabanServer;
import azkaban.server.HttpRequestUtils;
import azkaban.server.session.Session;
import azkaban.utils.Props;
import azkaban.utils.JSONUtils;
import azkaban.utils.WebUtils;
import azkaban.webapp.AzkabanWebServer;
import azkaban.webapp.plugin.PluginRegistry;
import azkaban.webapp.plugin.ViewerPlugin;
import azkaban.webapp.plugin.TriggerPlugin;
/**
* Base Servlet for pages
*/
public abstract class AbstractAzkabanServlet extends HttpServlet {
private static final String AZKABAN_SUCCESS_MESSAGE =
"azkaban.success.message";
private static final String AZKABAN_WARN_MESSAGE =
"azkaban.warn.message";
private static final String AZKABAN_FAILURE_MESSAGE =
"azkaban.failure.message";
private static final long serialVersionUID = -1;
public static final String DEFAULT_LOG_URL_PREFIX =
"predefined_log_url_prefix";
public static final String LOG_URL_PREFIX = "log_url_prefix";
public static final String HTML_TYPE = "text/html";
public static final String XML_MIME_TYPE = "application/xhtml+xml";
public static final String JSON_MIME_TYPE = "application/json";
protected static final WebUtils utils = new WebUtils();
private AzkabanServer application;
private String name;
private String label;
private String color;
/*
* The variable schedulePanelPageName is in charge of switching on retired schedulePanelDeprecated.vm (old UI)
* or the new schedulePanel.vm (new UI). We can configure it in conf for this binary change.
*/
private String schedulePanelPageName;
private List<ViewerPlugin> viewerPlugins;
private List<TriggerPlugin> triggerPlugins;
/**
* To retrieve the application for the servlet
*
* @return
*/
public AzkabanServer getApplication() {
return application;
}
@Override
public void init(ServletConfig config) throws ServletException {
application =
(AzkabanServer) config.getServletContext().getAttribute(
AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY);
if (application == null) {
throw new IllegalStateException(
"No batch application is defined in the servlet context!");
}
Props props = application.getServerProps();
name = props.getString("azkaban.name", "");
label = props.getString("azkaban.label", "");
color = props.getString("azkaban.color", "#FF0000");
schedulePanelPageName = props.getString("azkaban.schedulePanelPageName", "schedulepanelDeprecated.vm");
if (application instanceof AzkabanWebServer) {
AzkabanWebServer server = (AzkabanWebServer) application;
viewerPlugins = PluginRegistry.getRegistry().getViewerPlugins();
triggerPlugins =
new ArrayList<TriggerPlugin>(server.getTriggerPlugins().values());
}
}
/**
* Checks for the existance of the parameter in the request
*
* @param request
* @param param
* @return
*/
public boolean hasParam(HttpServletRequest request, String param) {
return HttpRequestUtils.hasParam(request, param);
}
/**
* Retrieves the param from the http servlet request. Will throw an exception
* if not found
*
* @param request
* @param name
* @return
* @throws ServletException
*/
public String getParam(HttpServletRequest request, String name)
throws ServletException {
return HttpRequestUtils.getParam(request, name);
}
/**
* Retrieves the param from the http servlet request.
*
* @param request
* @param name
* @param default
*
* @return
*/
public String getParam(HttpServletRequest request, String name,
String defaultVal) {
return HttpRequestUtils.getParam(request, name, defaultVal);
}
/**
* Returns the param and parses it into an int. Will throw an exception if not
* found, or a parse error if the type is incorrect.
*
* @param request
* @param name
* @return
* @throws ServletException
*/
public int getIntParam(HttpServletRequest request, String name)
throws ServletException {
return HttpRequestUtils.getIntParam(request, name);
}
public int getIntParam(HttpServletRequest request, String name, int defaultVal) {
return HttpRequestUtils.getIntParam(request, name, defaultVal);
}
public long getLongParam(HttpServletRequest request, String name)
throws ServletException {
return HttpRequestUtils.getLongParam(request, name);
}
public long getLongParam(HttpServletRequest request, String name,
long defaultVal) {
return HttpRequestUtils.getLongParam(request, name, defaultVal);
}
public Map<String, String> getParamGroup(HttpServletRequest request,
String groupName) throws ServletException {
return HttpRequestUtils.getParamGroup(request, groupName);
}
/**
* Returns the session value of the request.
*
* @param request
* @param key
* @param value
*/
protected void setSessionValue(HttpServletRequest request, String key,
Object value) {
request.getSession(true).setAttribute(key, value);
}
/**
* Adds a session value to the request
*
* @param request
* @param key
* @param value
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void addSessionValue(HttpServletRequest request, String key,
Object value) {
List l = (List) request.getSession(true).getAttribute(key);
if (l == null)
l = new ArrayList();
l.add(value);
request.getSession(true).setAttribute(key, l);
}
/**
* Sets an error message in azkaban.failure.message in the cookie. This will
* be used by the web client javascript to somehow display the message
*
* @param response
* @param errorMsg
*/
protected void setErrorMessageInCookie(HttpServletResponse response,
String errorMsg) {
Cookie cookie = new Cookie(AZKABAN_FAILURE_MESSAGE, errorMsg);
cookie.setPath("/");
response.addCookie(cookie);
}
/**
* Sets a warning message in azkaban.warn.message in the cookie. This will
* be used by the web client javascript to somehow display the message
*
* @param response
* @param warnMsg
*/
protected void setWarnMessageInCookie(HttpServletResponse response,
String errorMsg) {
Cookie cookie = new Cookie(AZKABAN_WARN_MESSAGE, errorMsg);
cookie.setPath("/");
response.addCookie(cookie);
}
/**
* Sets a message in azkaban.success.message in the cookie. This will be used
* by the web client javascript to somehow display the message
*
* @param response
* @param errorMsg
*/
protected void setSuccessMessageInCookie(HttpServletResponse response,
String message) {
Cookie cookie = new Cookie(AZKABAN_SUCCESS_MESSAGE, message);
cookie.setPath("/");
response.addCookie(cookie);
}
/**
* Retrieves a success message from a cookie. azkaban.success.message
*
* @param request
* @return
*/
protected String getSuccessMessageFromCookie(HttpServletRequest request) {
Cookie cookie = getCookieByName(request, AZKABAN_SUCCESS_MESSAGE);
if (cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* Retrieves a warn message from a cookie. azkaban.warn.message
*
* @param request
* @return
*/
protected String getWarnMessageFromCookie(HttpServletRequest request) {
Cookie cookie = getCookieByName(request, AZKABAN_WARN_MESSAGE);
if (cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* Retrieves a success message from a cookie. azkaban.failure.message
*
* @param request
* @return
*/
protected String getErrorMessageFromCookie(HttpServletRequest request) {
Cookie cookie = getCookieByName(request, AZKABAN_FAILURE_MESSAGE);
if (cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* Retrieves a cookie by name. Potential issue in performance if a lot of
* cookie variables are used.
*
* @param request
* @return
*/
protected Cookie getCookieByName(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
}
return null;
}
/**
* Creates a new velocity page to use. With session.
*
* @param req
* @param resp
* @param template
* @return
*/
protected Page newPage(HttpServletRequest req, HttpServletResponse resp,
Session session, String template) {
Page page = new Page(req, resp, application.getVelocityEngine(), template);
page.add("azkaban_name", name);
page.add("azkaban_label", label);
page.add("azkaban_color", color);
page.add("switchToPanelPage", schedulePanelPageName);
page.add("utils", utils);
page.add("timezone", TimeZone.getDefault().getID());
page.add("currentTime", (new DateTime()).getMillis());
if (session != null && session.getUser() != null) {
page.add("user_id", session.getUser().getUserId());
}
page.add("context", req.getContextPath());
String errorMsg = getErrorMessageFromCookie(req);
page.add("error_message", errorMsg == null || errorMsg.isEmpty() ? "null"
: errorMsg);
setErrorMessageInCookie(resp, null);
String warnMsg = getWarnMessageFromCookie(req);
page.add("warn_message", warnMsg == null || warnMsg.isEmpty() ? "null"
: warnMsg);
setWarnMessageInCookie(resp, null);
String successMsg = getSuccessMessageFromCookie(req);
page.add("success_message",
successMsg == null || successMsg.isEmpty() ? "null" : successMsg);
setSuccessMessageInCookie(resp, null);
// @TODO, allow more than one type of viewer. For time sake, I only install
// the first one
if (viewerPlugins != null && !viewerPlugins.isEmpty()) {
page.add("viewers", viewerPlugins);
}
if (triggerPlugins != null && !triggerPlugins.isEmpty()) {
page.add("triggerPlugins", triggerPlugins);
}
return page;
}
/**
* Creates a new velocity page to use.
*
* @param req
* @param resp
* @param template
* @return
*/
protected Page newPage(HttpServletRequest req, HttpServletResponse resp,
String template) {
Page page = new Page(req, resp, application.getVelocityEngine(), template);
page.add("azkaban_name", name);
page.add("azkaban_label", label);
page.add("azkaban_color", color);
page.add("switchToPanelPage", schedulePanelPageName);
page.add("timezone", TimeZone.getDefault().getID());
page.add("currentTime", (new DateTime()).getMillis());
page.add("context", req.getContextPath());
// @TODO, allow more than one type of viewer. For time sake, I only install
// the first one
if (viewerPlugins != null && !viewerPlugins.isEmpty()) {
page.add("viewers", viewerPlugins);
ViewerPlugin plugin = viewerPlugins.get(0);
page.add("viewerName", plugin.getPluginName());
page.add("viewerPath", plugin.getPluginPath());
}
if (triggerPlugins != null && !triggerPlugins.isEmpty()) {
page.add("triggers", triggerPlugins);
}
return page;
}
/**
* Writes json out to the stream.
*
* @param resp
* @param obj
* @throws IOException
*/
protected void writeJSON(HttpServletResponse resp, Object obj)
throws IOException {
writeJSON(resp, obj, false);
}
protected void writeJSON(HttpServletResponse resp, Object obj, boolean pretty)
throws IOException {
resp.setContentType(JSON_MIME_TYPE);
JSONUtils.toJSON(obj, resp.getOutputStream(), true);
}
/**
* Retrieve the Azkaban application
*
* @param config
* @return
*/
public static AzkabanWebServer getApp(ServletConfig config) {
AzkabanWebServer app =
(AzkabanWebServer) config.getServletContext().getAttribute(
AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY);
if (app == null) {
throw new IllegalStateException(
"No batch application is defined in the servlet context!");
} else {
return app;
}
}
public static String createJsonResponse(String status, String message,
String action, Map<String, Object> params) {
HashMap<String, Object> response = new HashMap<String, Object>();
response.put("status", status);
if (message != null) {
response.put("message", message);
}
if (action != null) {
response.put("action", action);
}
if (params != null) {
response.putAll(params);
}
return JSONUtils.toJSON(response);
}
}