/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 static li.strolch.runtime.configuration.ConfigurationTags.API;
import static li.strolch.runtime.configuration.ConfigurationTags.APPLICATION_NAME;
import static li.strolch.runtime.configuration.ConfigurationTags.DEPENDS;
import static li.strolch.runtime.configuration.ConfigurationTags.ENV_GLOBAL;
import static li.strolch.runtime.configuration.ConfigurationTags.ID;
import static li.strolch.runtime.configuration.ConfigurationTags.IMPL;
import static li.strolch.runtime.configuration.ConfigurationTags.NAME;
import static li.strolch.runtime.configuration.ConfigurationTags.STROLCH_CONFIGURATION_ENV;
import static li.strolch.runtime.configuration.ConfigurationTags.STROLCH_CONFIGURATION_ENV_COMPONENT;
import static li.strolch.runtime.configuration.ConfigurationTags.STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES;
import static li.strolch.runtime.configuration.ConfigurationTags.STROLCH_CONFIGURATION_ENV_RUNTIME;
import static li.strolch.runtime.configuration.ConfigurationTags.STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import li.strolch.model.Locator;
import li.strolch.model.Locator.LocatorBuilder;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
public class ConfigurationSaxParser extends DefaultHandler {
//private static final Logger logger = LoggerFactory.getLogger(ConfigurationSaxParser.class);
private final String environment;
private String currentEnvironment;
private ConfigurationBuilder globalEnvBuilder;
private Map<String, ConfigurationBuilder> envBuilders;
private LocatorBuilder locatorBuilder;
private Deque<ElementHandler> 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;
}
public String getCurrentEnvironment() {
return this.currentEnvironment;
}
@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();
//logger.info("path: " + locator.toString()); //$NON-NLS-1$
switch (locator.toString()) {
case STROLCH_CONFIGURATION_ENV:
String env = attributes.getValue(ID);
DBC.PRE.assertNotEmpty("attribute 'id' must be set on element 'env'", env); //$NON-NLS-1$
if (this.envBuilders.containsKey(env)) {
String msg = "Environment {0} already exists!"; //$NON-NLS-1$
throw new IllegalStateException(MessageFormat.format(msg, env));
}
this.currentEnvironment = env;
ConfigurationBuilder newEnvBuilder = new ConfigurationBuilder();
newEnvBuilder.runtimeBuilder().setEnvironment(this.currentEnvironment);
this.envBuilders.put(env, newEnvBuilder);
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME:
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
RuntimeHandler runtimeHandler = new RuntimeHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimeHandler);
}
break;
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());
}
break;
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);
}
break;
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());
}
break;
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!"); //$NON-NLS-1$
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)); //$NON-NLS-1$
return envBuilder;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
Locator locator = this.locatorBuilder.build();
//LoggerFactory.getLogger(getClass()).info("path: " + locator.toString()); //$NON-NLS-1$
switch (locator.toString()) {
case STROLCH_CONFIGURATION_ENV:
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME:
if (isRequiredEnv(this.currentEnvironment)) {
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
}
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES:
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
configurationBuilder.setPropertyBuilder(null);
}
break;
case STROLCH_CONFIGURATION_ENV_COMPONENT:
if (isRequiredEnv(this.currentEnvironment)) {
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
}
break;
case 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}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, expectedLocator, actualLocator);
throw new IllegalStateException(msg);
}
}
public 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); //$NON-NLS-1$
DBC.PRE.assertNotNull("locator must be set!", locator); //$NON-NLS-1$
this.configurationBuilder = configurationBuilder;
this.locator = locator;
}
public Locator getLocator() {
return this.locator;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (this.valueBuffer != null)
this.valueBuffer.append(ch, start, length);
}
}
public 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)
throws SAXException {
switch (qName) {
case APPLICATION_NAME:
this.valueBuffer = new StringBuilder();
break;
default:
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (qName) {
case APPLICATION_NAME:
String applicationName = this.valueBuffer.toString();
this.configurationBuilder.runtimeBuilder().setApplicationName(applicationName);
this.valueBuffer = null;
break;
default:
break;
}
}
}
public 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)
throws SAXException {
switch (qName) {
case NAME:
this.valueBuffer = new StringBuilder();
break;
case API:
this.valueBuffer = new StringBuilder();
break;
case IMPL:
this.valueBuffer = new StringBuilder();
break;
case DEPENDS:
this.valueBuffer = new StringBuilder();
break;
default:
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (qName) {
case NAME:
String name = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setName(name);
this.valueBuffer = null;
break;
case API:
String api = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setApi(api);
this.valueBuffer = null;
break;
case IMPL:
String impl = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setImpl(impl);
break;
case DEPENDS:
String depends = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().addDependency(depends);
break;
default:
break;
}
}
}
public 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)
throws SAXException {
if (this.propertyName != null) {
String msg = "Opening another tag {0} although {1} is still open!"; //$NON-NLS-1$
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) throws SAXException {
if (this.propertyName == null || !this.propertyName.equals(qName)) {
String msg = "Previous tag {0} was not closed before new tag {1}!"; //$NON-NLS-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 class ConfigurationBuilder {
private RuntimeBuilder runtimeBuilder;
private ComponentBuilder componentBuilder;
private PropertyBuilder propertyBuilder;
private List<ComponentBuilder> componentBuilders;
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 ComponentBuilder nextComponentBuilder() {
this.componentBuilder = new ComponentBuilder();
this.componentBuilders.add(this.componentBuilder);
return 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<String, ComponentConfiguration> configurationByComponent = new HashMap<>();
for (ComponentBuilder componentBuilder : this.componentBuilders) {
ComponentConfiguration componentConfiguration = componentBuilder.build(runtimeConfiguration);
configurationByComponent.put(componentConfiguration.getName(), componentConfiguration);
}
StrolchConfiguration strolchConfiguration = new StrolchConfiguration(runtimeConfiguration,
configurationByComponent);
return strolchConfiguration;
}
/**
* 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());
if (otherConfBuilder.runtimeBuilder != null) {
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 (!otherConfBuilder.componentBuilders.isEmpty()) {
Map<String, ComponentBuilder> thisComponentBuilders = new HashMap<>();
for (ComponentBuilder thisComponentBuilder : this.componentBuilders) {
thisComponentBuilders.put(thisComponentBuilder.getName(), thisComponentBuilder);
}
List<ComponentBuilder> otherComponents = otherConfBuilder.componentBuilders;
for (ComponentBuilder otherComponentBuilder : otherComponents) {
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 class PropertyBuilder {
private Map<String, String> properties;
public PropertyBuilder() {
this.properties = new HashMap<>();
}
public void addProperty(String key, String value) {
if (StringHelper.isEmpty(key))
throw new IllegalStateException("Key is empty!"); //$NON-NLS-1$
this.properties.put(key, value);
}
public Map<String, String> getProperties() {
return this.properties;
}
}
public class RuntimeBuilder extends PropertyBuilder {
private String applicationName;
private String environment;
public String getApplicationName() {
return this.applicationName;
}
public String getEnvironment() {
return this.environment;
}
public RuntimeConfiguration build(File configPathF, File dataPathF, File tempPathF) {
RuntimeConfiguration configuration = new RuntimeConfiguration(this.applicationName, this.environment,
getProperties(), configPathF, dataPathF, tempPathF);
return configuration;
}
public RuntimeBuilder setApplicationName(String applicationName) {
this.applicationName = applicationName;
return this;
}
public RuntimeBuilder setEnvironment(String environment) {
this.environment = environment;
return this;
}
}
public class ComponentBuilder extends PropertyBuilder {
private String name;
private String api;
private String impl;
private Set<String> dependencies;
public ComponentBuilder() {
this.dependencies = new HashSet<>();
}
public ComponentConfiguration build(RuntimeConfiguration runtimeConfiguration) {
ComponentConfiguration componentConfiguration = new ComponentConfiguration(runtimeConfiguration, this.name,
getProperties(), this.api, this.impl, this.dependencies);
return componentConfiguration;
}
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<String> getDependencies() {
return this.dependencies;
}
public void setDependencies(Set<String> dependencies) {
this.dependencies = dependencies;
}
public void addDependency(String dependency) {
this.dependencies.add(dependency);
}
}
}