/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.rest.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.api.config.Setting;
import org.structr.core.Services;
import org.structr.api.config.Settings;
import org.structr.api.config.SettingsGroup;
import org.structr.api.service.Service;
import org.structr.api.util.html.Attr;
import org.structr.api.util.html.Document;
import org.structr.api.util.html.Tag;
import org.structr.api.util.html.attr.Href;
import org.structr.api.util.html.attr.Rel;
/**
*
* @author Christian Morgner
*/
public class ConfigServlet extends HttpServlet {
private static final Logger logger = LoggerFactory.getLogger(ConfigServlet.class);
private static final Set<String> authenticatedSessions = new HashSet<>();
private static final String ConfigUrl = "/structr/config";
private static final String ConfigName = "structr.conf";
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
if (!isAuthenticated(request)) {
// no trailing semicolon so we dont trip MimeTypes.getContentTypeWithoutCharset
response.setContentType("text/html; charset=utf-8");
try (final PrintWriter writer = new PrintWriter(response.getWriter())) {
final Document doc = createLoginDocument(request, writer);
doc.render();
writer.append("\n");
writer.flush();
} catch (IOException ioex) {
ioex.printStackTrace();
}
} else {
if (request.getParameter("reload") != null) {
// reload data
Settings.loadConfiguration(ConfigName);
// redirect
response.sendRedirect(ConfigUrl);
} else if (request.getParameter("reset") != null) {
final String key = request.getParameter("reset");
final Setting setting = Settings.getSetting(key);
if (setting != null) {
if (setting.isDynamic()) {
// remove
setting.unregister();
} else {
// reset to default
setting.setValue(setting.getDefaultValue());
}
}
// serialize settings
Settings.storeConfiguration(ConfigName);
// redirect
response.sendRedirect(ConfigUrl);
} else if (request.getParameter("start") != null) {
final String serviceName = request.getParameter("start");
if (serviceName != null && isAuthenticated(request)) {
Services.getInstance().startService(serviceName);
}
// redirect
response.sendRedirect(ConfigUrl + "#services");
} else if (request.getParameter("stop") != null) {
final String serviceName = request.getParameter("stop");
if (serviceName != null && isAuthenticated(request)) {
Services.getInstance().shutdownService(serviceName);
}
// redirect
response.sendRedirect(ConfigUrl + "#services");
} else if (request.getParameter("restart") != null) {
final String serviceName = request.getParameter("restart");
if (serviceName != null && isAuthenticated(request)) {
new Thread(new Runnable() {
@Override
public void run() {
try { Thread.sleep(1000); } catch (Throwable t) {}
Services.getInstance().shutdownService(serviceName);
Services.getInstance().startService(serviceName);
}
}).start();
}
// redirect
response.sendRedirect(ConfigUrl + "#services");
} else {
// no trailing semicolon so we dont trip MimeTypes.getContentTypeWithoutCharset
response.setContentType("text/html; charset=utf-8");
try (final PrintWriter writer = new PrintWriter(response.getWriter())) {
final Document doc = createConfigDocument(request, writer);
doc.render();
writer.append("\n");
writer.flush();
} catch (IOException ioex) {
ioex.printStackTrace();
}
}
}
}
@Override
protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
final String action = request.getParameter("action");
String redirectTarget = "";
if (action != null) {
switch (action) {
case "login":
if (Settings.SuperUserPassword.getValue().equals(request.getParameter("password"))) {
authenticateSession(request);
}
break;
case "logout":
invalidateSession(request);
break;
}
} else if (isAuthenticated(request)) {
// a configuration form was submitted
for (final Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
final String value = getFirstElement(entry.getValue());
final String key = entry.getKey();
SettingsGroup parent = null;
// skip internal group configuration parameter
if (key.endsWith("._settings_group")) {
continue;
}
if ("active_section".equals(key)) {
redirectTarget = "#" + value;
continue;
}
Setting<?> setting = Settings.getSetting(key);
if (setting == null) {
// group specified?
final String group = request.getParameter(key + "._settings_group");
if (group != null) {
parent = Settings.getGroup(group);
if (parent == null) {
// default to misc group
parent = Settings.miscGroup;
}
} else {
// fallback to misc group
parent = Settings.miscGroup;
}
setting = Settings.createSettingForValue(parent, key, value);
}
// store new value
setting.fromString(value);
}
// serialize settings
Settings.storeConfiguration(ConfigName);
}
response.sendRedirect(ConfigUrl + redirectTarget);
}
// ----- private methods -----
private Document createConfigDocument(final HttpServletRequest request, final PrintWriter writer) {
final Document doc = new Document(writer);
final Tag body = setupDocument(request, doc);
final Tag form = body.block("form").css("config-form");
final Tag main = form.block("div").id("main");
final Tag tabs = main.block("div").id("configTabs");
final Tag menu = tabs.block("ul").id("configTabsMenu");
// configure form
form.attr(new Attr("action", ConfigUrl), new Attr("method", "post"));
for (final SettingsGroup group : Settings.getGroups()) {
final String key = group.getKey();
final String name = group.getName();
menu.block("li").block("a").id(key + "Menu").attr(new Attr("href", "#" + key)).block("span").text(name);
final Tag container = tabs.block("div").css("tab-content").id(key);
// let settings group render itself
group.render(container);
// stop floating
container.block("div").attr(new Style("clear: both;"));
}
// add services tab
menu.block("li").block("a").id("servicesMenu").attr(new Attr("href", "#services")).block("span").text("Services");
final Services services = Services.getInstance();
final Tag container = tabs.block("div").css("tab-content").id("services");
final Tag table = container.block("table").id("services-table");
final Tag header = table.block("tr");
header.block("th").text("Service Name");
header.block("th").attr(new Attr("colspan", "2"));
for (final String serviceClassName : services.getServices()) {
final Class<Service> serviceClass = services.getServiceClassForName(serviceClassName);
final boolean running = serviceClass != null ? services.isReady(serviceClass) : false;
final Tag row = table.block("tr");
row.block("td").text(serviceClassName);
if (running) {
row.block("td").block("button").attr(new Type("button"), new OnClick("window.location.href='" + ConfigUrl + "?restart=" + serviceClassName + "';")).text("Restart");
if ("HttpService".equals(serviceClassName)) {
row.block("td");
} else {
row.block("td").block("button").attr(new Type("button"), new OnClick("window.location.href='" + ConfigUrl + "?stop=" + serviceClassName + "';")).text("Stop");
}
row.block("td");
} else {
row.block("td");
row.block("td");
row.block("td").block("button").attr(new Type("button"), new OnClick("window.location.href='" + ConfigUrl + "?start=" + serviceClassName + "';")).text("Start");
}
}
// update active section so we can restore it when redirecting
container.empty("input").attr(new Type("hidden"), new Name("active_section")).id("active_section");
// stop floating
container.block("div").attr(new Style("clear: both;"));
// buttons
final Tag buttons = form.block("div").css("buttons");
buttons.block("button").attr(new Type("button")).id("new-entry-button").text("Add entry");
buttons.block("button").attr(new Type("button"), new OnClick("window.location.href='" + ConfigUrl + "?reload';")).text("Reload configuration");
buttons.empty("input").attr(new Type("submit"), new Value("Save to structr.conf"));
body.block("script").text("$('#new-entry-button').on('click', createNewEntry);");
return doc;
}
private Document createLoginDocument(final HttpServletRequest request, final PrintWriter writer) {
final Document doc = new Document(writer);
final Tag body = setupDocument(request, doc).css("login");
final Tag loginBox = body.block("div").id("login").css("dialog").attr(new Style("display: block; margin: auto; margin-top: 200px;"));
loginBox.block("i").attr(new Attr("title", "Structr Logo")).css("logo-login sprite sprite-structr_gray_100x27");
loginBox.block("p").text("Welcome to the Structr Configuration Editor. Please log in with the <b>super- user</b> password which can be found in your structr.conf.");
final Tag form = loginBox.block("form").attr(new Attr("action", ConfigUrl), new Attr("method", "post"));
final Tag table = form.block("table");
final Tag row1 = table.block("tr");
row1.block("td").block("label").attr(new Attr("for", "passwordField")).text("Password:");
row1.block("td").empty("input").id("passwordField").attr(new Type("password"), new Name("password"));
final Tag row2 = table.block("tr");
final Tag cell13 = row2.block("td").attr(new Attr("colspan", "2")).css("btn");
final Tag button = cell13.block("button").id("loginButton").attr(new Name("login"));
button.block("i").css("sprite sprite-key");
button.block("span").text(" Login");
cell13.empty("input").attr(new Type("hidden"), new Name("action"), new Value("login"));
return doc;
}
// ----- private methods -----
private Tag setupDocument(final HttpServletRequest request, final Document doc) {
final Tag head = doc.block("head");
head.block("title").text("Welcome to Structr 2.1");
head.empty("meta").attr(new Attr("http-equiv", "Content-Type"), new Attr("content", "text/html;charset=utf-8"));
head.empty("meta").attr(new Name("viewport"), new Attr("content", "width=1024, user-scalable=yes"));
head.empty("link").attr(new Rel("stylesheet"), new Href("/structr/css/lib/jquery-ui-1.10.3.custom.min.css"));
head.empty("link").attr(new Rel("stylesheet"), new Href("/structr/css/main.css"));
head.empty("link").attr(new Rel("stylesheet"), new Href("/structr/css/config.css"));
head.empty("link").attr(new Rel("icon"), new Href("favicon.ico"), new Type("image/x-icon"));
head.block("script").attr(new Src("/structr/js/lib/jquery-1.11.1.min.js"));
head.block("script").attr(new Src("/structr/js/lib/jquery-ui-1.11.0.custom.min.js"));
head.block("script").attr(new Src("/structr/js/config.js"));
final Tag body = doc.block("body");
final Tag header = body.block("div").id("header");
header.block("i").css("logo sprite sprite-structr-logo");
final Tag links = header.block("div").id("menu").css("menu").block("ul");
if (isAuthenticated(request)) {
final Tag form = links.block("li").block("form").attr(new Attr("action", ConfigUrl), new Attr("method", "post"), new Style("display: none")).id("logout-form");
form.empty("input").attr(new Type("hidden"), new Name("action"), new Value("logout"));
links.block("a").text("Logout").attr(new Style("cursor: pointer"), new OnClick("$('#logout-form').submit();"));
}
return body;
}
private boolean isAuthenticated(final HttpServletRequest request) {
final HttpSession session = request.getSession();
if (session != null) {
final String sessionId = session.getId();
if (sessionId != null) {
return authenticatedSessions.contains(sessionId);
} else {
logger.warn("Cannot check HTTP session without session ID, ignoring.");
}
} else {
logger.warn("Cannot check HTTP request, no session.");
}
return false;
}
private void authenticateSession(final HttpServletRequest request) {
final HttpSession session = request.getSession();
if (session != null) {
final String sessionId = session.getId();
if (sessionId != null) {
authenticatedSessions.add(sessionId);
} else {
logger.warn("Cannot authenticate HTTP session without session ID, ignoring.");
}
} else {
logger.warn("Cannot authenticate HTTP request, no session.");
}
}
private void invalidateSession(final HttpServletRequest request) {
final HttpSession session = request.getSession();
if (session != null) {
final String sessionId = session.getId();
if (sessionId != null) {
authenticatedSessions.remove(sessionId);
} else {
logger.warn("Cannot invalidate HTTP session without session ID, ignoring.");
}
} else {
logger.warn("Cannot invalidate HTTP request, no session.");
}
}
private String getFirstElement(final String[] values) {
if (values != null && values.length == 1) {
return values[0];
}
return null;
}
// ----- nested classes -----
private static class Style extends Attr {
public Style(final Object value) {
super("style", value);
}
}
private static class Src extends Attr {
public Src(final Object value) {
super("src", value);
}
}
private static class Type extends Attr {
public Type(final Object value) {
super("type", value);
}
}
private static class Name extends Attr {
public Name(final Object value) {
super("name", value);
}
}
private static class Value extends Attr {
public Value(final Object value) {
super("value", value);
}
}
private static class OnClick extends Attr {
public OnClick(final Object value) {
super("onclick", value);
}
}
}