li.strolch.runtime.configuration.ConfigurationSaxParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of agent Show documentation
Show all versions of agent Show documentation
Strolch Agent which is the runtime for Strolch
The newest version!
/*
* Copyright 2013 Robert von Burg
*
* 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 li.strolch.runtime.configuration;
import li.strolch.model.Locator;
import li.strolch.model.Locator.LocatorBuilder;
import li.strolch.model.Tags;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;
import java.text.MessageFormat;
import java.util.*;
import static li.strolch.runtime.configuration.ConfigurationTags.*;
public class ConfigurationSaxParser extends DefaultHandler {
private static final Logger logger = LoggerFactory.getLogger(ConfigurationSaxParser.class);
private final String environment;
private String currentEnvironment;
private final ConfigurationBuilder globalEnvBuilder;
private final Map envBuilders;
private final LocatorBuilder locatorBuilder;
private final Deque delegateHandlers;
public ConfigurationSaxParser(String environment) {
this.environment = environment;
this.locatorBuilder = new LocatorBuilder();
this.delegateHandlers = new ArrayDeque<>();
this.globalEnvBuilder = new ConfigurationBuilder();
this.envBuilders = new HashMap<>();
}
public ConfigurationBuilder getGlobalEnvBuilder() {
return this.globalEnvBuilder;
}
public ConfigurationBuilder getEnvBuilder() {
return this.envBuilders.get(this.environment);
}
public String getEnvironment() {
return this.environment;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().characters(ch, start, length);
}
private boolean isRequiredEnv(String env) {
return env.equals(ENV_GLOBAL) || env.equals(this.environment);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
this.locatorBuilder.append(qName);
Locator locator = this.locatorBuilder.build();
switch (locator.toString()) {
case STROLCH_CONFIGURATION_ENV -> {
String env = attributes.getValue(ID);
DBC.PRE.assertNotEmpty("attribute 'id' must be set on element 'env'", env);
if (this.envBuilders.containsKey(env)) {
String msg = "Environment {0} already exists!";
throw new IllegalStateException(MessageFormat.format(msg, env));
}
this.currentEnvironment = env;
ConfigurationBuilder newEnvBuilder = new ConfigurationBuilder();
newEnvBuilder.runtimeBuilder().setEnvironment(this.currentEnvironment);
this.envBuilders.put(env, newEnvBuilder);
}
case STROLCH_CONFIGURATION_ENV_RUNTIME -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
RuntimeHandler runtimeHandler = new RuntimeHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimeHandler);
}
}
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
PropertiesHandler runtimePropertiesHandler = new PropertiesHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimePropertiesHandler);
configurationBuilder.setPropertyBuilder(configurationBuilder.runtimeBuilder());
}
}
case STROLCH_CONFIGURATION_ENV_COMPONENT -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
configurationBuilder.nextComponentBuilder();
ComponentHandler componentHandler = new ComponentHandler(configurationBuilder, locator);
this.delegateHandlers.push(componentHandler);
}
}
case STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
PropertiesHandler componentPropertiesHandler = new PropertiesHandler(configurationBuilder, locator);
this.delegateHandlers.push(componentPropertiesHandler);
configurationBuilder.setPropertyBuilder(configurationBuilder.componentBuilder());
}
}
default -> {
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().startElement(uri, localName, qName, attributes);
}
}
}
private ConfigurationBuilder getEnvBuilder(String environment) {
if (StringHelper.isEmpty(environment))
throw new IllegalStateException("environment must be set!");
else if (environment.equals(ENV_GLOBAL))
return this.globalEnvBuilder;
ConfigurationBuilder envBuilder = this.envBuilders.get(environment);
if (envBuilder == null)
throw new IllegalStateException(
MessageFormat.format("No ConfigurationBuilder exists for env {0}", environment));
return envBuilder;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
Locator locator = this.locatorBuilder.build();
switch (locator.toString()) {
case STROLCH_CONFIGURATION_ENV:
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME, STROLCH_CONFIGURATION_ENV_COMPONENT:
if (isRequiredEnv(this.currentEnvironment)) {
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
}
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES, STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES:
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
configurationBuilder.setPropertyBuilder(null);
}
break;
default:
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().endElement(uri, localName, qName);
}
this.locatorBuilder.removeLast();
}
private void assertExpectedLocator(Locator expectedLocator, Locator actualLocator) {
if (!expectedLocator.equals(actualLocator)) {
String msg = "Locator mismatch. Expected {0}. Current: {1}";
msg = MessageFormat.format(msg, expectedLocator, actualLocator);
throw new IllegalStateException(msg);
}
}
public static class ElementHandler extends DefaultHandler {
protected final ConfigurationBuilder configurationBuilder;
protected final Locator locator;
protected StringBuilder valueBuffer;
public ElementHandler(ConfigurationBuilder configurationBuilder, Locator locator) {
DBC.PRE.assertNotNull("configurationBuilder must be set!", configurationBuilder);
DBC.PRE.assertNotNull("locator must be set!", locator);
this.configurationBuilder = configurationBuilder;
this.locator = locator;
}
public Locator getLocator() {
return this.locator;
}
@Override
public void characters(char[] ch, int start, int length) {
if (this.valueBuffer != null)
this.valueBuffer.append(ch, start, length);
}
}
public static class RuntimeHandler extends ElementHandler {
public RuntimeHandler(ConfigurationBuilder configurationBuilder, Locator locator) {
super(configurationBuilder, locator);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (qName.equals(APPLICATION_NAME)) {
this.valueBuffer = new StringBuilder();
} else if (qName.equals(LANGUAGE)) {
String locale = attributes.getValue(Tags.Json.LOCALE);
String name = attributes.getValue(Tags.Json.NAME);
if (StringHelper.isEmpty(locale) || StringHelper.isEmpty(name)) {
logger.error("Ignoring invalid supported language definition with empty values!");
} else {
SupportedLanguage language = new SupportedLanguage(locale, name);
configurationBuilder.runtimeBuilder.addSupportedLanguage(language);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if (qName.equals(APPLICATION_NAME)) {
String applicationName = this.valueBuffer.toString();
this.configurationBuilder.runtimeBuilder().setApplicationName(applicationName);
this.valueBuffer = null;
}
}
}
public static class ComponentHandler extends ElementHandler {
public ComponentHandler(ConfigurationBuilder configurationBuilder, Locator locator) {
super(configurationBuilder, locator);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
switch (qName) {
case NAME, API, IMPL, DEPENDS -> this.valueBuffer = new StringBuilder();
default -> {
// no nothing for others, as only these are text elements
}
}
}
@Override
public void endElement(String uri, String localName, String qName) {
switch (qName) {
case NAME -> {
String name = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setName(name);
this.valueBuffer = null;
}
case API -> {
String api = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setApi(api);
this.valueBuffer = null;
}
case IMPL -> {
String impl = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setImpl(impl);
}
case DEPENDS -> {
String depends = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().addDependency(depends);
}
default -> throw new IllegalStateException("Unexpected value: " + qName);
}
}
}
public static class PropertiesHandler extends ElementHandler {
public PropertiesHandler(ConfigurationBuilder configurationBuilder, Locator locator) {
super(configurationBuilder, locator);
}
private String propertyName;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (this.propertyName != null) {
String msg = "Opening another tag {0} although {1} is still open!";
msg = MessageFormat.format(msg, this.propertyName, qName);
throw new IllegalStateException(msg);
}
this.propertyName = qName;
this.valueBuffer = new StringBuilder();
}
@Override
public void endElement(String uri, String localName, String qName) {
if (this.propertyName == null || !this.propertyName.equals(qName)) {
String msg = "Previous tag {0} was not closed before new tag {1}!";
msg = MessageFormat.format(msg, this.propertyName, qName);
throw new IllegalStateException(msg);
}
String propertyValue = this.valueBuffer.toString().trim();
this.configurationBuilder.getPropertyBuilder().addProperty(this.propertyName, propertyValue);
this.propertyName = null;
this.valueBuffer = null;
}
}
public static class ConfigurationBuilder {
private final RuntimeBuilder runtimeBuilder;
private final List componentBuilders;
private ComponentBuilder componentBuilder;
private PropertyBuilder propertyBuilder;
public ConfigurationBuilder() {
this.componentBuilders = new ArrayList<>();
this.runtimeBuilder = new RuntimeBuilder();
}
public void setPropertyBuilder(PropertyBuilder propertyBuilder) {
this.propertyBuilder = propertyBuilder;
}
public PropertyBuilder getPropertyBuilder() {
return this.propertyBuilder;
}
public RuntimeBuilder runtimeBuilder() {
return this.runtimeBuilder;
}
public void nextComponentBuilder() {
this.componentBuilder = new ComponentBuilder();
this.componentBuilders.add(this.componentBuilder);
}
public ComponentBuilder componentBuilder() {
return this.componentBuilder;
}
public StrolchConfiguration build(File configPathF, File dataPathF, File tempPathF) {
RuntimeConfiguration runtimeConfiguration = this.runtimeBuilder.build(configPathF, dataPathF, tempPathF);
Map configurationByComponent = new HashMap<>();
for (ComponentBuilder componentBuilder : this.componentBuilders) {
ComponentConfiguration componentConfiguration = componentBuilder.build(runtimeConfiguration);
configurationByComponent.put(componentConfiguration.getName(), componentConfiguration);
}
return new StrolchConfiguration(runtimeConfiguration, configurationByComponent);
}
/**
* Merge the given {@link ConfigurationBuilder ConfigurationBuilder's} values into this configuration builder
*
* @param otherConfBuilder the {@link ConfigurationBuilder} to be merged into this
*/
public void merge(ConfigurationBuilder otherConfBuilder) {
runtimeBuilder().setEnvironment(otherConfBuilder.runtimeBuilder().getEnvironment());
RuntimeBuilder thisRuntime = this.runtimeBuilder;
RuntimeBuilder other = otherConfBuilder.runtimeBuilder;
if (StringHelper.isNotEmpty(other.getApplicationName()))
thisRuntime.setApplicationName(other.getApplicationName());
if (!other.getProperties().isEmpty())
thisRuntime.getProperties().putAll(other.getProperties());
if (!other.supportedLanguages.isEmpty())
thisRuntime.supportedLanguages.addAll(other.supportedLanguages);
if (!otherConfBuilder.componentBuilders.isEmpty()) {
Map thisComponentBuilders = new HashMap<>();
for (ComponentBuilder thisComponentBuilder : this.componentBuilders) {
thisComponentBuilders.put(thisComponentBuilder.getName(), thisComponentBuilder);
}
for (ComponentBuilder otherComponentBuilder : otherConfBuilder.componentBuilders) {
ComponentBuilder thisComponentBuilder = thisComponentBuilders.get(otherComponentBuilder.getName());
if (thisComponentBuilder == null) {
this.componentBuilders.add(otherComponentBuilder);
} else {
if (StringHelper.isNotEmpty(otherComponentBuilder.getImpl())) {
thisComponentBuilder.setImpl(otherComponentBuilder.getImpl());
thisComponentBuilder.setDependencies(otherComponentBuilder.getDependencies());
}
thisComponentBuilder.getProperties().putAll(otherComponentBuilder.getProperties());
}
}
}
}
}
public abstract static class PropertyBuilder {
private final Map properties;
public PropertyBuilder() {
this.properties = new HashMap<>();
}
public void addProperty(String key, String value) {
if (StringHelper.isEmpty(key))
throw new IllegalStateException("Key is empty!");
this.properties.put(key, value);
}
public Map getProperties() {
return this.properties;
}
}
public static class RuntimeBuilder extends PropertyBuilder {
private String applicationName;
private String environment;
private final Set supportedLanguages;
public RuntimeBuilder() {
this.supportedLanguages = new HashSet<>();
}
public String getApplicationName() {
return this.applicationName;
}
public String getEnvironment() {
return this.environment;
}
public void addSupportedLanguage(SupportedLanguage language) {
this.supportedLanguages.add(language);
}
public Set getSupportedLanguages() {
return this.supportedLanguages;
}
public RuntimeConfiguration build(File configPathF, File dataPathF, File tempPathF) {
return new RuntimeConfiguration(this.applicationName, this.environment, getProperties(), configPathF,
dataPathF, tempPathF, this.supportedLanguages);
}
public RuntimeBuilder setApplicationName(String applicationName) {
this.applicationName = applicationName;
return this;
}
public RuntimeBuilder setEnvironment(String environment) {
this.environment = environment;
return this;
}
}
public static class ComponentBuilder extends PropertyBuilder {
private String name;
private String api;
private String impl;
private Set dependencies;
public ComponentBuilder() {
this.dependencies = new HashSet<>();
}
public ComponentConfiguration build(RuntimeConfiguration runtimeConfiguration) {
return new ComponentConfiguration(runtimeConfiguration, this.name, getProperties(), this.api, this.impl,
this.dependencies);
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getApi() {
return this.api;
}
public void setApi(String api) {
this.api = api;
}
public String getImpl() {
return this.impl;
}
public void setImpl(String impl) {
this.impl = impl;
}
public Set getDependencies() {
return this.dependencies;
}
public void setDependencies(Set dependencies) {
this.dependencies = dependencies;
}
public void addDependency(String dependency) {
this.dependencies.add(dependency);
}
}
}