/* * Copyright 2013 NGDATA nv * Copyright 2008 Outerthought bvba and Schaubroeck nv * * 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 org.lilyproject.runtime.conf; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.jxpath.JXPathContext; import org.lilyproject.runtime.conf.jxpath.ConfPointerFactory; import org.lilyproject.util.location.Location; import org.lilyproject.util.location.LocationImpl; public class ConfImpl implements Conf { private List<ConfImpl> children = new ArrayList<ConfImpl>(); private Map<String, String> attributes = new HashMap<String, String>(); private String name; private String value; private Location location; private Inheritance inheritance; /** * Inheritance uniqueness constraint: an expression (JXPath) used when inheriting to determine * if a node is already present. * */ private String inheritConstraint; public enum Inheritance { NONE, SHALLOW, DEEP } static { ConfPointerFactory.register(); } public ConfImpl(String name, Location location) { if (name == null) { throw new IllegalArgumentException("Null argument: name"); } if (location == null) { throw new IllegalArgumentException("Null argument: location"); } this.name = name; this.location = location; } public void addChild(ConfImpl config) { if (config == null) { throw new IllegalArgumentException("Null argument: config"); } this.children.add(config); } public void setValue(String value) { if (value == null) { throw new IllegalArgumentException("Null argument: config"); } if (value.length() != value.trim().length()) { throw new IllegalArgumentException("Configuration values should be trimmed."); } if (value.length() == 0) { throw new IllegalArgumentException("Configuration values should have a length > 0."); } this.value = value; } public void addAttribute(String name, String value) { attributes.put(name, value); } public List<Conf> getChildren() { return Collections.<Conf>unmodifiableList(children); } public boolean hasChildren() { return children.size() > 0; } public List<Conf> getChildren(String name) { List<Conf> result = new ArrayList<Conf>(); for (Conf conf : children) { if (conf.getName().equals(name)) { result.add(conf); } } return Collections.unmodifiableList(result); } public Conf getChild(String name) { return getChild(name, true); } public Conf getChild(String name, boolean create) { for (Conf conf : children) { if (conf.getName().equals(name)) { return conf; } } if (create) { return new ConfImpl(name, new LocationImpl(null, "<generated>" + location.getURI(), location.getLineNumber(), location.getColumnNumber())); } else { return null; } } public Conf getRequiredChild(String name) { Conf child = getChild(name, false); if (child != null) { return child; } else { throw new ConfException("Missing configuration node \"" + name + "\" inside " + name + " at " + getLocation()); } } public String getName() { return name; } public Location getLocation() { return location; } public String getValue() { checkValuePresent(); return value; } public boolean getValueAsBoolean() { return convertToBoolean(getValue(), null); } public int getValueAsInteger() { return convertToInteger(getValue(), null); } public long getValueAsLong() { return convertToLong(getValue(), null); } public float getValueAsFloat() { return convertToFloat(getValue(), null); } public double getValueAsDouble() { return convertToDouble(getValue(), null); } public String getValue(String defaultValue) { if (value == null) { return defaultValue; } return getValue(); } public Boolean getValueAsBoolean(Boolean defaultValue) { if (value == null) { return defaultValue; } return getValueAsBoolean(); } public Integer getValueAsInteger(Integer defaultValue) { if (value == null) { return defaultValue; } return getValueAsInteger(); } public Long getValueAsLong(Long defaultValue) { if (value == null) { return defaultValue; } return getValueAsLong(); } public Float getValueAsFloat(Float defaultValue) { if (value == null) { return defaultValue; } return getValueAsFloat(); } public Double getValueAsDouble(Double defaultValue) { if (value == null) { return defaultValue; } return getValueAsDouble(); } public Map<String, String> getAttributes() { return Collections.unmodifiableMap(attributes); } public String getAttribute(String name) { checkAttributePresent(name); return attributes.get(name); } public boolean getAttributeAsBoolean(String name) { return convertToBoolean(getAttribute(name), name); } public int getAttributeAsInteger(String name) { return convertToInteger(getAttribute(name), name); } public long getAttributeAsLong(String name) { return convertToLong(getAttribute(name), name); } public float getAttributeAsFloat(String name) { return convertToFloat(getAttribute(name), name); } public double getAttributeAsDouble(String name) { return convertToDouble(getAttribute(name), name); } public String getAttribute(String name, String defaultValue) { if (!attributes.containsKey(name)) { return defaultValue; } return getAttribute(name); } public Boolean getAttributeAsBoolean(String name, Boolean defaultValue) { if (!attributes.containsKey(name)) { return defaultValue; } return getAttributeAsBoolean(name); } public Integer getAttributeAsInteger(String name, Integer defaultValue) { if (!attributes.containsKey(name)) { return defaultValue; } return getAttributeAsInteger(name); } public Long getAttributeAsLong(String name, Long defaultValue) { if (!attributes.containsKey(name)) { return defaultValue; } return getAttributeAsLong(name); } public Float getAttributeAsFloat(String name, Float defaultValue) { if (!attributes.containsKey(name)) { return defaultValue; } return getAttributeAsFloat(name); } public Double getAttributeAsDouble(String name, Double defaultValue) { if (!attributes.containsKey(name)) { return defaultValue; } return getAttributeAsDouble(name); } private void checkValuePresent() { if (value == null) { throw new ConfException("No value is associated with the configuration element " + getName() + " at " + getLocation()); } } private void checkAttributePresent(String attributeName) { if (!attributes.containsKey(attributeName)) { throw new ConfException("No attribute \"" + attributeName + "\" on the configuration element " + getName() + " at " + getLocation()); } } private boolean convertToBoolean(String value, String attributeName) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("t") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("y")) { return true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("f") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("n")) { return false; } else { produceBadFormatException("boolean", value, attributeName); return true; // never reached } } private int convertToInteger(String value, String attributeName) { try { return Integer.parseInt(value); } catch (NumberFormatException e) { produceBadFormatException("integer", value, attributeName); return 0; // never reached } } private long convertToLong(String value, String attributeName) { try { return Long.parseLong(value); } catch (NumberFormatException e) { produceBadFormatException("long", value, attributeName); return 0; // never reached } } private float convertToFloat(String value, String attributeName) { try { return Float.parseFloat(value); } catch (NumberFormatException e) { produceBadFormatException("float", value, attributeName); return 0; // never reached } } private double convertToDouble(String value, String attributeName) { try { return Double.parseDouble(value); } catch (NumberFormatException e) { produceBadFormatException("double", value, attributeName); return 0; // never reached } } private void produceBadFormatException(String typeName, String value, String attributeName) { StringBuilder message = new StringBuilder(); message.append("Configuration value is not a valid ").append(typeName).append(": \"").append(value).append("\""); if (attributeName == null) { message.append(" in configuration element ").append(getName()).append(" at ").append(getLocation()); } else { message.append(" in attribute ").append(attributeName).append(" of configuration element ") .append(getName()).append(" at ").append(getLocation()); } throw new ConfException(message.toString()); } public Inheritance getInheritance() { return inheritance; } public void setInheritance(Inheritance inheritance) { this.inheritance = inheritance; } public String getInheritConstraint() { return inheritConstraint; } public void setInheritConstraint(String inheritConstraint) { this.inheritConstraint = inheritConstraint; } public void inherit(ConfImpl parent) { inherit(parent, Inheritance.NONE); } public void inherit(ConfImpl parent, Inheritance defaultInheritance) { // In case of deep inheritance, we let our children inherit from the corresponding // children of the parent. // // But what are corresponding children? // // - in case there is an inheritance constraint defined, we take the first child // from the parent which has the same value for the inheritance constraint. // // - otherwise, corresponding children are children with the same name, thus // an inheritance constraint "local-name()" // // Note that performance is not very critical of this routine, as it is supposed // that (inherited) configurations are cached. // // Note that we first perform inheritance for our children (= deep inheritance), // before ourselve, to avoid that we would do deep inheritance on our inherited // children too. Inheritance inheritance = this.inheritance != null ? this.inheritance : defaultInheritance; String inheritConstraint = this.inheritConstraint != null ? this.inheritConstraint : "local-name()"; if (inheritance == Inheritance.DEEP && inheritConstraint.length() != 0) { for (ConfImpl child : children) { String key = evalInheritanceConstraint(child, inheritConstraint); for (ConfImpl parentChild : parent.children) { String parentKey = evalInheritanceConstraint(parentChild, inheritConstraint); if (key.equals(parentKey)) { child.inherit(parentChild, inheritance); break; } } } } if (inheritance != Inheritance.NONE) { // Inherit attributes for (Map.Entry<String, String> attr : parent.getAttributes().entrySet()) { if (!attributes.containsKey(attr.getKey())) { addAttribute(attr.getKey(), attr.getValue()); } } // Calculate the set of keys for the current children Set<String> presentKeys = null; if (inheritConstraint.length() != 0) { presentKeys = new HashSet<String>(); for (Conf conf : children) { String value = evalInheritanceConstraint(conf, inheritConstraint); if (value != null) { presentKeys.add(value); } } } // Inherit child elements for (ConfImpl conf : parent.children) { if (inheritConstraint.length() == 0) { // empty inherit constraint means: inherit everything children.add(conf.deepClone()); } else { String key = evalInheritanceConstraint(conf, inheritConstraint); if (key != null && !presentKeys.contains(key)) { children.add(conf.deepClone()); presentKeys.add(key); } } } } } public String evalInheritanceConstraint(Conf conf, String inheritConstraint) { try { JXPathContext jxpc = JXPathContext.newContext(conf); return (String)jxpc.getValue(inheritConstraint); } catch (Exception e) { throw new ConfException("Error evaluating configuration inheritance uniqueness constraint expression: \"" + inheritConstraint + "\" on conf defined at " + conf.getLocation(), e); } } /** * Makes a deep clone of this Conf, the inheritance level * is however not cloned. */ public ConfImpl deepClone() { ConfImpl conf = new ConfImpl(name, location); conf.attributes.putAll(this.attributes); conf.value = value; for (ConfImpl childConf : children) { conf.children.add(childConf.deepClone()); } return conf; } }