package org.ff4j.web.embedded; /* * #%L * ff4j-web * %% * Copyright (C) 2013 - 2015 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.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.ff4j.FF4j; import org.ff4j.conf.XmlConfig; import org.ff4j.conf.XmlParser; import org.ff4j.core.Feature; import org.ff4j.core.FeatureStore; import org.ff4j.core.FlippingStrategy; import org.ff4j.property.Property; import org.ff4j.property.store.PropertyStore; import org.ff4j.property.util.PropertyFactory; import org.ff4j.utils.Util; import org.ff4j.web.bean.WebConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.ff4j.web.embedded.ConsoleConstants.*; public final class ConsoleOperations { /** Logger for this class. */ private static Logger LOGGER = LoggerFactory.getLogger(ConsoleOperations.class); private ConsoleOperations() {} /** * User action to create a new Feature. * * @param req * http request containing operation parameters */ public static void createFeature(FF4j ff4j, HttpServletRequest req) { // uid final String featureId = req.getParameter(FEATID); if (featureId != null && !featureId.isEmpty()) { Feature fp = new Feature(featureId, false); // Description final String featureDesc = req.getParameter(DESCRIPTION); if (null != featureDesc && !featureDesc.isEmpty()) { fp.setDescription(featureDesc); } // GroupName final String groupName = req.getParameter(GROUPNAME); if (null != groupName && !groupName.isEmpty()) { fp.setGroup(groupName); } // Strategy final String strategy = req.getParameter(STRATEGY); if (null != strategy && !strategy.isEmpty()) { try { Class<?> strategyClass = Class.forName(strategy); FlippingStrategy fstrategy = (FlippingStrategy) strategyClass.newInstance(); final String strategyParams = req.getParameter(STRATEGY_INIT); if (null != strategyParams && !strategyParams.isEmpty()) { Map<String, String> initParams = new HashMap<String, String>(); String[] params = strategyParams.split(";"); for (String currentP : params) { String[] cur = currentP.split("="); if (cur.length < 2) { throw new IllegalArgumentException("Invalid Syntax : param1=val1,val2;param2=val3,val4"); } initParams.put(cur[0], cur[1]); } fstrategy.init(featureId, initParams); } fp.setFlippingStrategy(fstrategy); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot find strategy class", e); } catch (InstantiationException e) { throw new IllegalArgumentException("Cannot instantiate strategy", e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot instantiate : no public constructor", e); } } // Permissions final String permission = req.getParameter(PERMISSION); if (null != permission && PERMISSION_RESTRICTED.equals(permission)) { @SuppressWarnings("unchecked") Map<String, Object> parameters = req.getParameterMap(); Set<String> permissions = new HashSet<String>(); for (String key : parameters.keySet()) { if (key.startsWith(PREFIX_CHECKBOX)) { permissions.add(key.replace(PREFIX_CHECKBOX, "")); } } fp.setPermissions(permissions); } // Creation ff4j.getFeatureStore().create(fp); LOGGER.info(featureId + " has been created"); } } /** * Sample Element should be updated like name, description, value * @param ff4j * @param req */ public static void updateProperty(FF4j ff4j, HttpServletRequest req) { String name = req.getParameter("name"); String type = req.getParameter("pType"); String description = req.getParameter("desc"); String value = req.getParameter("pValue"); String uid = req.getParameter("uid"); String featureId = req.getParameter(WebConstants.FEATURE_UID); Property<?> ap; // To update the core the uid is the name (rename, edit) if (uid == null) { uid = name; } // Update Feature property if (Util.hasLength(featureId)) { Feature current = ff4j.getFeatureStore().read(featureId); ap = current.getProperty(uid); ap.setDescription(description); if (ap.getType().equalsIgnoreCase(type)) { ap.setValueFromString(value); } else { ap = PropertyFactory.createProperty(name, type, value); LOGGER.warn("By changing property type you loose the fixedValues, cannot evaluate ? at runtime"); } ff4j.getFeatureStore().update(current); } else if (ff4j.getPropertiesStore().existProperty(uid)) { // Do not change name, just and update if (uid.equalsIgnoreCase(name)) { ap = ff4j.getPropertiesStore().readProperty(uid); // just an update for the value if (ap.getType().equalsIgnoreCase(type)) { ap.setDescription(description); ap.setValueFromString(value); ff4j.getPropertiesStore().updateProperty(ap); } else { ap = PropertyFactory.createProperty(name, type, value); ap.setDescription(description); // Note : Fixed Values are LOST if type changed => cannot cast ? to T LOGGER.warn("By changing property type you loose the fixedValues, cannot evaluate ? at runtime"); ff4j.getPropertiesStore().deleteProperty(name); ff4j.getPropertiesStore().createProperty(ap); } } else { // Name change delete and create a new ap = PropertyFactory.createProperty(name, type, value); ap.setDescription(description); // Note : Fixed Values are LOST if name changed => cannot cast ? to T LOGGER.warn("By changing property name you loose the fixedValues, cannot evaluate generics at runtime (type inference)"); ff4j.getPropertiesStore().deleteProperty(uid); ff4j.getPropertiesStore().createProperty(ap); } } } /** * Create new property in store. * * @param ff4j * current ff4j instance. * @param req * current http request */ public static void createProperty(FF4j ff4j, HttpServletRequest req) { String name = req.getParameter("name"); String type = req.getParameter("pType"); String description = req.getParameter("desc"); String value = req.getParameter("pValue"); String featureId = req.getParameter(WebConstants.FEATURE_UID); Property<?> ap = PropertyFactory.createProperty(name, type, value); ap.setDescription(description); if (Util.hasLength(featureId)) { Feature current = ff4j.getFeatureStore().read(featureId); current.addProperty(ap); ff4j.getFeatureStore().update(current); } else { ff4j.getPropertiesStore().createProperty(ap); } } private static void updateFlippingStrategy(Feature fp, String strategy, String strategyParams) { if (null != strategy && !strategy.isEmpty()) { try { Class<?> strategyClass = Class.forName(strategy); FlippingStrategy fstrategy = (FlippingStrategy) strategyClass.newInstance(); if (null != strategyParams && !strategyParams.isEmpty()) { Map<String, String> initParams = new HashMap<String, String>(); String[] params = strategyParams.split(";"); for (String currentP : params) { String[] cur = currentP.split("="); if (cur.length < 2) { throw new IllegalArgumentException("Invalid Syntax : param1=val1,val2;param2=val3,val4"); } initParams.put(cur[0], cur[1]); } fstrategy.init(fp.getUid(), initParams); } fp.setFlippingStrategy(fstrategy); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot find strategy class", e); } catch (InstantiationException e) { throw new IllegalArgumentException("Cannot instantiate strategy", e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot instantiate : no public constructor", e); } } } /** * User action to update a target feature's description. * * @param req * http request containing operation parameters */ public static void updateFeatureDescription(FF4j ff4j, HttpServletRequest req) { // uid final String featureId = req.getParameter(FEATID); if (featureId != null && !featureId.isEmpty()) { // https://github.com/clun/ff4j/issues/66 Feature old = ff4j.getFeatureStore().read(featureId); Feature fp = new Feature(featureId, old.isEnable()); // <-- // Description final String featureDesc = req.getParameter(DESCRIPTION); if (null != featureDesc && !featureDesc.isEmpty()) { fp.setDescription(featureDesc); } // GroupName final String groupName = req.getParameter(GROUPNAME); if (null != groupName && !groupName.isEmpty()) { fp.setGroup(groupName); } // Strategy updateFlippingStrategy(fp, req.getParameter(STRATEGY), req.getParameter(STRATEGY_INIT)); // Permissions final String permission = req.getParameter(PERMISSION); if (null != permission && PERMISSION_RESTRICTED.equals(permission)) { @SuppressWarnings("unchecked") Map<String, Object> parameters = req.getParameterMap(); Set<String> permissions = new HashSet<String>(); for (String key : parameters.keySet()) { if (key.startsWith(PREFIX_CHECKBOX)) { permissions.add(key.replace(PREFIX_CHECKBOX, "")); } } fp.setPermissions(permissions); } // Creation ff4j.getFeatureStore().update(fp); LOGGER.info(featureId + " has been updated"); } } /** * User action to import Features from a properties files. * * @param in * inpustream from configuration file * @throws IOException * Error raised if the configuration cannot be read */ public static void importFile(FF4j ff4j, InputStream in) throws IOException { FeatureStore store = ff4j.getFeatureStore(); XmlConfig xmlConfig = new XmlParser().parseConfigurationFile(in); Map<String, Feature> mapsOfFeat = xmlConfig.getFeatures(); for (Entry<String, Feature> feature : mapsOfFeat.entrySet()) { if (store.exist(feature.getKey())) { store.update(feature.getValue()); } else { store.create(feature.getValue()); } } LOGGER.info(mapsOfFeat.size() + " features have been imported."); PropertyStore pstore = ff4j.getPropertiesStore(); Map<String, Property<?>> mapsOfProperties = xmlConfig.getProperties(); for (Entry<String, Property<?>> p : mapsOfProperties.entrySet()) { if (pstore.existProperty(p.getKey())) { pstore.updateProperty(p.getValue()); } else { pstore.createProperty(p.getValue()); } } LOGGER.info(mapsOfProperties.size() + " features have been imported."); } /** * Build Http response when invoking export features. * * @param res * http response * @throws IOException * error when building response */ public static void exportFile(FF4j ff4j, HttpServletResponse res) throws IOException { Map<String, Feature> features = ff4j.getFeatureStore().readAll(); InputStream in = new XmlParser().exportFeatures(features); ServletOutputStream sos = null; try { sos = res.getOutputStream(); res.setContentType("text/xml"); res.setHeader("Content-Disposition", "attachment; filename=\"ff4j.xml\""); // res.setContentLength() org.apache.commons.io.IOUtils.copy(in, sos); LOGGER.info(features.size() + " features have been exported."); } finally { if (in != null) { in.close(); } if (sos != null) { sos.flush(); sos.close(); } } } }