/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.params.extractor;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.ResourceStore;
import org.geotools.util.logging.Logging;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public final class RulesDao {
private static final Logger LOGGER = Logging.getLogger(RulesDao.class);
private static final String NEW_LINE = System.getProperty("line.separator");
private static final GeoServerDataDirectory DATA_DIRECTORY = (GeoServerDataDirectory) GeoServerExtensions.bean("dataDirectory");
public static String getRulesPath() {
return "params-extractor/extraction-rules.xml";
}
public static String getTempRulesPath() {
return String.format("params-extractor/%s-extraction-rules.xml", UUID.randomUUID());
}
public static List<Rule> getRules() {
Resource rules = DATA_DIRECTORY.get(getRulesPath());
return getRules(rules.in());
}
public static List<Rule> getRules(InputStream inputStream) {
try {
if (inputStream.available() == 0) {
Utils.debug(LOGGER, "Rules files seems to be empty.");
return new ArrayList<>();
}
RuleHandler handler = new RuleHandler();
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
saxParser.parse(inputStream, handler);
return handler.rules;
} catch (Exception exception) {
throw Utils.exception(exception, "Error parsing rules files.");
} finally {
Utils.closeQuietly(inputStream);
}
}
public static void saveOrUpdateRule(Rule rule) {
Resource rules = DATA_DIRECTORY.get(getRulesPath());
Resource tmpRules = DATA_DIRECTORY.get(getTempRulesPath());
saveOrUpdateRule(rule, rules.in(), tmpRules.out());
rules.delete();
tmpRules.renameTo(rules);
}
public static void saveOrUpdateRule(Rule rule, InputStream inputStream, OutputStream outputStream) {
try {
List<Rule> rules = getRules(inputStream);
boolean exists = false;
for (int i = 0; i < rules.size() && !exists; i++) {
if (rules.get(i).getId().equals(rule.getId())) {
rules.set(i, rule);
exists = true;
}
}
if (!exists) {
rules.add(rule);
}
writeRules(rules, outputStream);
} finally {
Utils.closeQuietly(inputStream);
Utils.closeQuietly(outputStream);
}
}
public static void deleteRules(String... rulesIds) {
Resource rules = DATA_DIRECTORY.get(getRulesPath());
Resource tmpRules = DATA_DIRECTORY.get(getTempRulesPath());
deleteRules(rules.in(), tmpRules.out(), rulesIds);
rules.delete();
tmpRules.renameTo(rules);
}
public static void deleteRules(InputStream inputStream, OutputStream outputStream, String... ruleIds) {
try {
writeRules(getRules(inputStream).stream()
.filter(rule -> !Arrays.stream(ruleIds)
.anyMatch(ruleId -> ruleId.equals(rule.getId())))
.collect(Collectors.toList()), outputStream);
} finally {
Utils.closeQuietly(inputStream);
Utils.closeQuietly(outputStream);
}
}
private static void writeRules(List<Rule> rules, OutputStream outputStream) {
try {
XMLStreamWriter output = XMLOutputFactory.newInstance().
createXMLStreamWriter(new OutputStreamWriter(outputStream, "utf-8"));
output.writeStartDocument();
output.writeCharacters(NEW_LINE);
output.writeStartElement("Rules");
output.writeCharacters(NEW_LINE);
rules.forEach(rule -> writeRule(rule, output));
output.writeEndElement();
output.writeCharacters(NEW_LINE);
output.writeEndDocument();
output.close();
} catch (Exception exception) {
throw Utils.exception(exception, "Something bad happen when writing rules.");
}
}
private static void writeRule(Rule rule, XMLStreamWriter output) {
try {
output.writeCharacters(" ");
output.writeStartElement("Rule");
writeAttribute("id", rule.getId(), output);
writeAttribute("activated", rule.getActivated(), output);
writeAttribute("position", rule.getPosition(), output);
writeAttribute("match", rule.getMatch(), output);
writeAttribute("activation", rule.getActivation(), output);
writeAttribute("parameter", rule.getParameter(), output);
writeAttribute("transform", rule.getTransform(), output);
writeAttribute("remove", rule.getRemove(), output);
writeAttribute("combine", rule.getCombine(), output);
output.writeEndElement();
output.writeCharacters(NEW_LINE);
} catch (Exception exception) {
throw Utils.exception(exception, "Error writing rule %s.", rule.getId());
}
}
private static <T> void writeAttribute(String name, T value, XMLStreamWriter output) throws Exception {
if (value != null) {
output.writeAttribute(name, value.toString());
}
}
private static final class RuleHandler extends DefaultHandler {
final List<Rule> rules = new ArrayList<>();
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (!qName.equalsIgnoreCase("rule")) {
return;
}
Utils.debug(LOGGER, "Start parsing rule.");
RuleBuilder ruleBuilder = new RuleBuilder();
getAttribute("id", attributes, ruleBuilder::withId);
getAttribute("activated", attributes, compose(Boolean::valueOf, ruleBuilder::withActivated));
getAttribute("position", attributes, compose(Integer::valueOf, ruleBuilder::withPosition));
getAttribute("match", attributes, ruleBuilder::withMatch);
getAttribute("activation", attributes, ruleBuilder::withActivation);
getAttribute("parameter", attributes, ruleBuilder::withParameter);
getAttribute("remove", attributes, compose(Integer::valueOf, ruleBuilder::withRemove));
getAttribute("transform", attributes, ruleBuilder::withTransform);
getAttribute("combine", attributes, ruleBuilder::withCombine);
rules.add(ruleBuilder.build());
Utils.debug(LOGGER, "End parsing rule.");
}
private static <T> Consumer<String> compose(Function<String, T> convert, Consumer<T> setter) {
return (value) -> setter.accept(convert.apply(value));
}
private void getAttribute(String attributeName, Attributes attributes, Consumer<String> setter) {
String attributeValue = attributes.getValue(attributeName);
if (attributeValue == null) {
Utils.debug(LOGGER, "Rule attribute %s is NULL.", attributeName);
return;
}
Utils.debug(LOGGER, "Rule attribute %s is %s.", attributeName, attributeValue);
try {
setter.accept(attributeValue);
} catch (Exception exception) {
throw Utils.exception(exception,
"Error setting attribute '%s' with value '%s'.", attributeName, attributeValue);
}
}
}
}