package org.ff4j.web.embedded;
import static org.ff4j.web.embedded.ConsoleConstants.CONTENT_TYPE_HTML;
import static org.ff4j.web.embedded.ConsoleConstants.CONTENT_TYPE_JSON;
import static org.ff4j.web.embedded.ConsoleConstants.FEATID;
import static org.ff4j.web.embedded.ConsoleConstants.FF4J_SESSIONATTRIBUTE_NAME;
import static org.ff4j.web.embedded.ConsoleConstants.FLIPFILE;
import static org.ff4j.web.embedded.ConsoleConstants.GROUPNAME;
import static org.ff4j.web.embedded.ConsoleConstants.NAME;
import static org.ff4j.web.embedded.ConsoleConstants.OPERATION;
import static org.ff4j.web.embedded.ConsoleConstants.OP_ADD_FIXEDVALUE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_CREATE_FEATURE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_CREATE_PROPERTY;
import static org.ff4j.web.embedded.ConsoleConstants.OP_DELETE_FIXEDVALUE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_DISABLE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_EDIT_FEATURE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_EDIT_PROPERTY;
import static org.ff4j.web.embedded.ConsoleConstants.OP_ENABLE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_EXPORT;
import static org.ff4j.web.embedded.ConsoleConstants.OP_READ_FEATURE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_READ_PROPERTY;
import static org.ff4j.web.embedded.ConsoleConstants.OP_RMV_FEATURE;
import static org.ff4j.web.embedded.ConsoleConstants.OP_RMV_PROPERTY;
import static org.ff4j.web.embedded.ConsoleConstants.OP_TOGGLE_GROUP;
import static org.ff4j.web.embedded.ConsoleConstants.PARAM_FIXEDVALUE;
import static org.ff4j.web.embedded.ConsoleConstants.PROVIDER_PARAM_NAME;
import static org.ff4j.web.embedded.ConsoleConstants.SUBOPERATION;
import static org.ff4j.web.embedded.ConsoleConstants.VIEW;
import static org.ff4j.web.embedded.ConsoleOperations.createFeature;
import static org.ff4j.web.embedded.ConsoleOperations.createProperty;
import static org.ff4j.web.embedded.ConsoleOperations.exportFile;
import static org.ff4j.web.embedded.ConsoleOperations.importFile;
import static org.ff4j.web.embedded.ConsoleOperations.updateFeatureDescription;
import static org.ff4j.web.embedded.ConsoleOperations.updateProperty;
import static org.ff4j.web.embedded.ConsoleRenderer.msg;
import static org.ff4j.web.embedded.ConsoleRenderer.renderMessageBox;
import static org.ff4j.web.embedded.ConsoleRenderer.renderMsgGroup;
import static org.ff4j.web.embedded.ConsoleRenderer.renderMsgProperty;
import static org.ff4j.web.embedded.ConsoleRenderer.renderPage;
import static org.ff4j.web.embedded.ConsoleRenderer.renderPageMonitoring;
/*
* #%L AdministrationConsoleServlet.java (ff4j-web) by Cedrick LUNVEN %% Copyright (C) 2013 Ff4J %% 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. #L%
*/
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.ff4j.FF4j;
import org.ff4j.core.Feature;
import org.ff4j.property.Property;
import org.ff4j.web.FF4jProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Unique Servlet to manage FlipPoints and security
*
* @author <a href="mailto:cedrick.lunven@gmail.com">Cedrick LUNVEN</a>
*/
public class ConsoleServlet extends HttpServlet {
/** serial number. */
private static final long serialVersionUID = -3982043895954284269L;
/** Logger for this class. */
public static final Logger LOGGER = LoggerFactory.getLogger(ConsoleServlet.class);
/** Error Message. */
public static final String ERROR = "error";
/** instance of ff4j. */
private FF4j ff4j = null;
/** initializing ff4j provider. */
private FF4jProvider ff4jProvider = null;
/**
* Servlet initialization, init FF4J from "ff4jProvider" attribute Name.
*
* @param servletConfig
* current {@link ServletConfig} context
* @throws ServletException
* error during servlet initialization
*/
public void init(ServletConfig servletConfig) throws ServletException {
LOGGER.info(" __ __ _ _ _ ");
LOGGER.info(" / _|/ _| || | (_)");
LOGGER.info("| |_| |_| || |_| |");
LOGGER.info("| _| _|__ _| |");
LOGGER.info("|_| |_| |_|_/ |");
LOGGER.info(" |__/ Embedded Console - v" + getClass().getPackage().getImplementationVersion());
LOGGER.info(" ");
if (ff4j == null) {
String className = servletConfig.getInitParameter(PROVIDER_PARAM_NAME);
try {
Class<?> c = Class.forName(className);
Object o = c.newInstance();
ff4jProvider = (FF4jProvider) o;
LOGGER.info("ff4j context has been successfully initialized - {} feature(s)", ff4jProvider.getFF4j().getFeatures().size());
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot load ff4jProvider as " + ff4jProvider, e);
} catch (InstantiationException e) {
throw new IllegalArgumentException("Cannot instantiate " + ff4jProvider + " as ff4jProvider", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("No public constructor for " + ff4jProvider + " as ff4jProvider", e);
} catch (ClassCastException ce) {
throw new IllegalArgumentException("ff4jProvider expected instance of " + FF4jProvider.class, ce);
}
// Put the FF4J in ApplicationScope (useful for tags)
ff4j = ff4jProvider.getFF4j();
servletConfig.getServletContext().setAttribute(FF4J_SESSIONATTRIBUTE_NAME, ff4j);
LOGGER.debug("Servlet has been initialized and ff4j store in session with {} ", ff4j.getFeatures().size());
} else {
LOGGER.debug("Servlet has been initialized, ff4j was injected");
}
}
/** {@inheritDoc} */
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
String targetView = req.getParameter(VIEW);
if (targetView == null || "".equals(targetView)) {
pageCore(req, res);
} else if ("monitoring".equals(targetView)) {
pageMonitoring(req, res);
}
}
public void pageMonitoring(HttpServletRequest req, HttpServletResponse res) throws IOException {
String message = null;
String messagetype = "info";
renderPageMonitoring(getFf4j(), req, res, message, messagetype);
}
public void pageCore(HttpServletRequest req, HttpServletResponse res) throws IOException {
String message = null;
String messagetype = "info";
try {
// 'RSC' parameter will load some static resources
if (ConsoleRenderer.renderResources(req, res)) return;
// Serve operation from GET
String operation = req.getParameter(OPERATION);
String featureId = req.getParameter(FEATID);
LOGGER.info("GET - op=" + operation + " feat=" + featureId);
if (operation != null && !operation.isEmpty()) {
// operation which do not required features
if (OP_EXPORT.equalsIgnoreCase(operation)) {
exportFile(ff4j, res);
return;
}
// Work on a feature ID
if ((featureId != null) && (!featureId.isEmpty())) {
if (getFf4j().getFeatureStore().exist(featureId)) {
message = workWithFeature(res, operation, featureId);
}
if (getFf4j().getPropertiesStore().existProperty(featureId)) {
message = workWithProperties(res, req, operation, featureId);
}
}
}
} catch (Exception e) {
// Any Error is trapped and display in the console
messagetype = ERROR;
message = e.getMessage();
LOGGER.error("An error occured ", e);
}
// Default page rendering (table)
renderPage(getFf4j(), req, res, message, messagetype);
}
/**
* Execute operation on properties when needed.
*
* @param res
* target response
* @param req
* current request
* @param operation
* operation to be completed on properties
* @param uid
* unique property identifier
* @return
* targte message to be displayed
* @throws IOException
* error occurs when filling the output page
*/
private String workWithProperties(HttpServletResponse res, HttpServletRequest req, String operation, String uid)
throws IOException {
String message = null;
if (OP_RMV_PROPERTY.equalsIgnoreCase(operation)) {
getFf4j().getPropertiesStore().deleteProperty(uid);
LOGGER.info("Property '" + uid + "' has been deleted");
message = renderMsgProperty(uid, "DELETED");
}
if (OP_READ_PROPERTY.equalsIgnoreCase(operation)) {
Property<?> ap = getFf4j().getPropertiesStore().readProperty(uid);
res.setContentType(CONTENT_TYPE_JSON);
res.getWriter().println(ap.toString());
}
if (OP_DELETE_FIXEDVALUE.equalsIgnoreCase(operation)) {
String fixedValue = req.getParameter(PARAM_FIXEDVALUE);
Property<?> ap = getFf4j().getPropertiesStore().readProperty(uid);
ap.getFixedValues().remove(fixedValue);
getFf4j().getPropertiesStore().updateProperty(ap);
}
if (OP_ADD_FIXEDVALUE.equalsIgnoreCase(operation)) {
String fixedValue = req.getParameter(PARAM_FIXEDVALUE);
Property<?> ap = getFf4j().getPropertiesStore().readProperty(uid);
ap.add2FixedValueFromString(fixedValue);
getFf4j().getPropertiesStore().updateProperty(ap);
}
return message;
}
/**
* Operations relative to features.
*
* @param res
* http response
* @param operation
* operation to execute on features
* @param uid
* unique feature identifier
* @throws IOException
* an error occur when fill the output
*/
private String workWithFeature(HttpServletResponse res, String operation, String uid)
throws IOException {
String message = null;
if (OP_DISABLE.equalsIgnoreCase(operation)) {
getFf4j().disable(uid);
res.setContentType(CONTENT_TYPE_HTML);
res.getWriter().println(renderMessageBox(msg(uid, "DISABLED"), "info"));
LOGGER.info(uid + " has been disabled");
}
if (OP_ENABLE.equalsIgnoreCase(operation)) {
getFf4j().enable(uid);
res.setContentType(CONTENT_TYPE_HTML);
res.getWriter().println(renderMessageBox(msg(uid, "ENABLED"), "info"));
LOGGER.info("Feature '" + uid + "' has been successfully enabled");
}
if (OP_READ_FEATURE.equalsIgnoreCase(operation)) {
Feature f = getFf4j().getFeatureStore().read(uid);
res.setContentType(CONTENT_TYPE_JSON);
res.getWriter().println(f.toJson());
}
// As no return the page is draw
if (OP_RMV_FEATURE.equalsIgnoreCase(operation)) {
getFf4j().getFeatureStore().delete(uid);
LOGGER.info(uid + " has been deleted");
message = msg(uid, "DELETED");
}
return message;
}
/** {@inheritDoc} */
@Override
public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String message = null;
String messagetype = "info";
try {
if (ServletFileUpload.isMultipartContent(req)) {
List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(req);
for (FileItem item : items) {
if (item.isFormField()) {
if (OPERATION.equalsIgnoreCase(item.getFieldName())) {
LOGGER.info("Processing action : " + item.getString());
}
} else if (FLIPFILE.equalsIgnoreCase(item.getFieldName())) {
String filename = FilenameUtils.getName(item.getName());
if (filename.toLowerCase().endsWith("xml")) {
importFile(getFf4j(), item.getInputStream());
message = "The file <b>" + filename + "</b> has been successfully imported";
} else {
messagetype = ERROR;
message = "Invalid FILE, must be CSV, XML or PROPERTIES files";
}
}
}
} else {
String operation = req.getParameter(OPERATION);
String uid = req.getParameter(FEATID);
LOGGER.info("POST - op=" + operation + " feat=" + uid);
if (operation != null && !operation.isEmpty()) {
if (OP_EDIT_FEATURE.equalsIgnoreCase(operation)) {
updateFeatureDescription(getFf4j(), req);
message = msg(uid, "UPDATED");
} else if (OP_EDIT_PROPERTY.equalsIgnoreCase(operation)) {
updateProperty(getFf4j(), req);
message = renderMsgProperty(uid, "UPDATED");
} else if (OP_CREATE_PROPERTY.equalsIgnoreCase(operation)) {
createProperty(getFf4j(), req);
message = renderMsgProperty(req.getParameter(NAME), "ADDED");
} else if (OP_CREATE_FEATURE.equalsIgnoreCase(operation)) {
createFeature(getFf4j(), req);
message = msg(uid, "ADDED");
} else if (OP_TOGGLE_GROUP.equalsIgnoreCase(operation)) {
String groupName = req.getParameter(GROUPNAME);
if (groupName != null && !groupName.isEmpty()) {
String operationGroup = req.getParameter(SUBOPERATION);
if (OP_ENABLE.equalsIgnoreCase(operationGroup)) {
getFf4j().getFeatureStore().enableGroup(groupName);
message = renderMsgGroup(groupName, "ENABLED");
LOGGER.info("Group '" + groupName + "' has been ENABLED.");
} else if (OP_DISABLE.equalsIgnoreCase(operationGroup)) {
getFf4j().getFeatureStore().disableGroup(groupName);
message = renderMsgGroup(groupName, "DISABLED");
LOGGER.info("Group '" + groupName + "' has been DISABLED.");
}
}
} else {
LOGGER.error("Invalid POST OPERATION" + operation);
messagetype = ERROR;
message = "Invalid REQUEST";
}
} else {
LOGGER.error("No ID provided" + operation);
messagetype = ERROR;
message = "Invalid UID";
}
}
} catch (Exception e) {
messagetype = ERROR;
message = e.getMessage();
LOGGER.error("An error occured ", e);
}
renderPage(ff4j, req, res, message, messagetype);
}
/**
* Getter accessor for attribute 'ff4j'.
*
* @return current value of 'ff4j'
*/
public FF4j getFf4j() {
if (ff4j == null) {
throw new IllegalStateException("Console Servlet has not been initialized, please set 'load-at-startup' to 1");
}
return ff4j;
}
/**
* Setter accessor for attribute 'ff4j'.
* @param ff4j
* new value for 'ff4j '
*/
public void setFf4j(FF4j ff4j) {
this.ff4j = ff4j;
}
}