/* * RHQ Management Platform * Copyright (C) 2005-2010 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.rhq.core.domain.configuration; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MapKey; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * This is the root object for the storage of a hierarchical value set of data. This data may represent configurations * of external systems or the components within ON. The data values supported are the basic primitive types in * containers of maps and lists. Containers may hold other containers creating the hierarchical data structure. This * content is loosely related to the definition entities that can provide a model for the possible values and validation * of them. * * <p>A <code>Configuration</code> has one or more named {@link Property} objects contained within it (similar to a * <code>Map</code>). Note that {@link Property} is an abstract class that actually represents either:</p> * * <ul> * <li>a simple value ({@link PropertySimple})</li> * <li>a list of other {@link Property} objects ({@link PropertyList})</li> * <li>a map of other {@link Property} objects ({@link PropertyMap})</li> * </ul> * * <p>Because a Configuration can contain a list or map of properties, a Configuration can contain a hierarchy of * properties N-levels deep.</p> * * <p>Each Property within a Configuration has a name - this not only includes simple properties, but also lists and * maps of properties as well. For example, you can retrieve a list of properties via {@link #getList(String)} by * passing in the name of the list.</p> * * @author Jason Dobies * @author Greg Hinkle * * @see Property * @see PropertySimple * @see PropertyList * @see PropertyMap */ @Entity(name = "Configuration") @NamedQueries({ // @NamedQuery(name = Configuration.QUERY_GET_PLUGIN_CONFIG_BY_RESOURCE_ID, query = "" // + "select r.pluginConfiguration from Resource r where r.id = :resourceId"), @NamedQuery(name = Configuration.QUERY_GET_RESOURCE_CONFIG_BY_RESOURCE_ID, query = "" // + "select r.resourceConfiguration from Resource r where r.id = :resourceId"), @NamedQuery(name = Configuration.QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_ID, query = "" // + "SELECT r.id, r.resourceConfiguration " // + " FROM ResourceGroup rg " // + " JOIN rg.explicitResources r " // + " WHERE rg.id = :resourceGroupId AND r.inventoryStatus = 'COMMITTED'"), @NamedQuery(name = Configuration.QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_ID, query = "" // + "SELECT r.id, r.pluginConfiguration " // + " FROM ResourceGroup rg " // + " JOIN rg.explicitResources r " // + " WHERE rg.id = :resourceGroupId AND r.inventoryStatus = 'COMMITTED'"), @NamedQuery(name = Configuration.QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_UPDATE_ID, query = "" // + "SELECT res.id, cu.configuration " // + " FROM ResourceConfigurationUpdate cu " // + " JOIN cu.resource res " // + " WHERE cu.groupConfigurationUpdate.id = :groupConfigurationUpdateId"), @NamedQuery(name = Configuration.QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_UPDATE_ID, query = "" // + "SELECT res.id, cu.configuration " // + " FROM PluginConfigurationUpdate cu " // + " JOIN cu.resource res " // + " WHERE cu.groupConfigurationUpdate.id = :groupConfigurationUpdateId"), @NamedQuery(name = Configuration.QUERY_BREAK_PROPERTY_RECURSION_BY_CONFIGURATION_IDS, query = "" // + "UPDATE Property p " // + " SET p.parentMap = NULL, " // + " p.parentList = NULL " // + " WHERE p.configuration.id IN ( :configurationIds )"), @NamedQuery(name = Configuration.QUERY_DELETE_RAW_CONFIGURATIONS_CONFIGURATION_IDS, query = "" // + "DELETE FROM RawConfiguration rc " // + " WHERE rc.configuration.id IN ( :configurationIds )"), @NamedQuery(name = Configuration.QUERY_DELETE_CONFIGURATIONS_BY_CONFIGURATION_IDs, query = "" // + "DELETE FROM Configuration c " // + " WHERE c.id IN ( :configurationIds )") }) @SequenceGenerator(allocationSize = org.rhq.core.domain.util.Constants.ALLOCATION_SIZE, name = "RHQ_CONFIG_ID_SEQ", sequenceName = "RHQ_CONFIG_ID_SEQ") @Table(name = "RHQ_CONFIG") @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement public class Configuration implements Serializable, Cloneable, AbstractPropertyMap { private static final long serialVersionUID = 1L; public static final String QUERY_GET_PLUGIN_CONFIG_BY_RESOURCE_ID = "Configuration.getPluginConfigByResourceId"; public static final String QUERY_GET_RESOURCE_CONFIG_BY_RESOURCE_ID = "Configuration.getResourceConfigByResourceId"; public static final String QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_ID = "Configuration.getResourceConfigMapByGroupId"; public static final String QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_ID = "Configuration.getPluginConfigMapByGroupId"; public static final String QUERY_GET_RESOURCE_CONFIG_MAP_BY_GROUP_UPDATE_ID = "Configuration.getResourceConfigMapByGroupUpdateId"; public static final String QUERY_GET_PLUGIN_CONFIG_MAP_BY_GROUP_UPDATE_ID = "Configuration.getPluginConfigMapByGroupUpdateId"; public static final String QUERY_BREAK_PROPERTY_RECURSION_BY_CONFIGURATION_IDS = "Property.breakPropertyRecursionByConfigurationIds"; public static final String QUERY_DELETE_RAW_CONFIGURATIONS_CONFIGURATION_IDS = "Configuration.deleteRawByConfigurationIds"; public static final String QUERY_DELETE_CONFIGURATIONS_BY_CONFIGURATION_IDs = "Configuration.deleteByConfigurationIdS"; private static abstract class AbstractPropertyMapBuilder<T extends AbstractPropertyMap, This extends AbstractPropertyMapBuilder<T, This>> { private T map; protected AbstractPropertyMapBuilder(T map) { this.map = map; } /** * Adds a simple property. * @param name the name of the simple property * @param value the value of the simple property * @return continue with the definition */ public This addSimple(String name, Object value) { getMap().put(new PropertySimple(name, value)); return castThis(); } /** * Starts defining a new sub list. * @param name the name of the sub list * @param memberName the names of the member properties of the sub list * @return the builder of the list */ public Builder.ListInMap<This> openList(String name, String memberName) { return new Builder.ListInMap<This>(castThis(), name, memberName); } /** * Starts defining a new sub map. * @param name the name of the sub map * @return the builder of the map */ public Builder.MapInMap<This> openMap(String name) { return new Builder.MapInMap<This>(castThis(), name); } protected T getMap() { return map; } @SuppressWarnings("unchecked") private This castThis() { return (This) this; } } private static abstract class AbstractPropertyListBuilder<This extends AbstractPropertyListBuilder<This>> { private PropertyList list; private AbstractPropertyListBuilder(String name, String memberName) { this.list = new PropertyList(name); this.list.memberPropertyName = memberName; } /** * Adds a simple property. The name of the property is the member name defined by this list. * @param value the value of the simple property * @return continue with the definition */ public This addSimple(Object value) { list.add(new PropertySimple(list.memberPropertyName, value)); return castThis(); } /** * Adds a number of simple properties. The names of the properties are the member name defined by this list. * @param values the values of the simple properties * @return continue with the definition */ public This addSimples(Object... values) { for(Object v : values) { list.add(new PropertySimple(list.memberPropertyName, v)); } return castThis(); } /** * Starts defining a new sub map. * @return the builder of the map */ public Builder.MapInList<This> openMap() { return new Builder.MapInList<This>(castThis(), list.memberPropertyName); } /** * Starts defining a new sub list. * @param memberName the names of the member properties of the sub list * @return the builder of the list */ public Builder.ListInList<This> openList(String memberName) { return new Builder.ListInList<This>(castThis(), list.memberPropertyName, memberName); } protected PropertyList getList() { return list; } @SuppressWarnings("unchecked") private This castThis() { return (This) this; } } /** * A builder to easily build Configuration instances using a fluent API. */ public static class Builder extends AbstractPropertyMapBuilder<Configuration, Builder> { public static class MapInMap<Parent extends AbstractPropertyMapBuilder<?, ?>> extends AbstractPropertyMapBuilder<PropertyMap, MapInMap<Parent>> { private Parent parent; private MapInMap(Parent parent, String name) { super(new PropertyMap(name)); this.parent = parent; } /** * Closes the definition of the current map and returns to the parent context. * @return the parent context */ public Parent closeMap() { parent.getMap().put(getMap()); return parent; } } public static class MapInList<Parent extends AbstractPropertyListBuilder<?>> extends AbstractPropertyMapBuilder<PropertyMap, MapInList<Parent>> { private Parent parent; public MapInList(Parent parent, String name) { super(new PropertyMap(name)); this.parent = parent; } /** * Closes the definition of the current map and returns to the parent context. * @return the parent context */ public Parent closeMap() { parent.getList().add(getMap()); return parent; } } public static class ListInMap<Parent extends AbstractPropertyMapBuilder<?, ?>> extends AbstractPropertyListBuilder<ListInMap<Parent>> { private Parent parent; private ListInMap(Parent parent, String name, String memberName) { super(name, memberName); this.parent = parent; } /** * Closes the definition of the current list and returns to the parent context. * @return the parent context */ public Parent closeList() { parent.getMap().put(getList()); return parent; } } public static class ListInList<Parent extends AbstractPropertyListBuilder<?>> extends AbstractPropertyListBuilder<ListInList<Parent>> { private Parent parent; private ListInList(Parent parent, String name, String memberName) { super(name, memberName); this.parent = parent; } /** * Closes the definition of the current list and returns to the parent context. * @return the parent context */ public Parent closeList() { parent.getList().add(getList()); return parent; } } public class RawConfigurationBuilder { private RawConfiguration rawConfig; public RawConfigurationBuilder() { rawConfig = new RawConfiguration(); rawConfig.setConfiguration(getMap()); } public RawConfigurationBuilder withPath(String path) { rawConfig.setPath(path); return this; } public RawConfigurationBuilder withContents(String content, String sha256) { rawConfig.setContents(content, sha256); return this; } /** * Closes the definition of the current raw configuration and returns to the parent context. * @return the parent context */ public Builder closeRawConfiguration() { getMap().getRawConfigurations().add(rawConfig); return Builder.this; } } public Builder() { super(new Configuration()); } public Builder withNotes(String notes) { getMap().setNotes(notes); return this; } public Builder withVersion(long version) { getMap().setVersion(version); return this; } /** * Starts defining a new raw configuration that will become part of this configuration. * @return the builder of the raw configuration */ public RawConfigurationBuilder openRawConfiguration() { return new RawConfigurationBuilder(); } public Configuration build() { return getMap(); } } @GeneratedValue(generator = "RHQ_CONFIG_ID_SEQ", strategy = GenerationType.AUTO) @Id private int id; // use the prop name as the map key @MapKey(name = "name") // CascadeType.REMOVE has been omitted, the cascade delete has been moved to the data model for performance @Cascade({ CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH }) @OneToMany(mappedBy = "configuration", fetch = FetchType.EAGER, orphanRemoval = true) @XmlTransient private Map<String, Property> properties = new LinkedHashMap<String, Property>(1); private class PropertiesProxy implements Collection<Property> { @Override public int size() { return properties==null ? 0: properties.size(); } @Override public boolean isEmpty() { return properties == null || properties.isEmpty(); } @Override public boolean contains(Object o) { return properties==null ? false : properties.containsValue(o); } @Override public Iterator<Property> iterator() { if (properties == null) { // TODO replace with Collections.emptyIterator(); when we require java 7 return Configuration.emptyIterator(); } else { return properties.values().iterator(); } } @Override public Object[] toArray() { return properties==null ? new Object[]{} :properties.values().toArray(); } @Override public <T> T[] toArray(T[] a) { return properties.values().toArray(a); } @Override public boolean add(Property e) { put(e); return true; //we always allow adding an element even if it is already present } @Override public boolean remove(Object o) { return properties.values().remove(o); } @Override public boolean containsAll(Collection<?> c) { return properties.values().containsAll(c); } @Override public boolean addAll(Collection<? extends Property> c) { boolean ret = false; for (Property p : c) { ret = ret || add(p); } return ret; } @Override public boolean removeAll(Collection<?> c) { boolean ret = false; for (Object o : c) { ret = ret || remove(o); } return ret; } @Override public boolean retainAll(Collection<?> c) { boolean ret = false; ArrayList<Property> ps = new ArrayList<Property>(properties.values()); for (Property p : ps) { if (!c.contains(p)) { ret = ret || remove(p); } } return ret; } @Override public void clear() { if (properties!=null) { properties.clear(); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Collection)) { return false; } return properties.values().equals(obj); } @Override public int hashCode() { return properties.values().hashCode(); } @Override public String toString() { return properties.values().toString(); } } private transient PropertiesProxy propertiesProxy; // If we don't actually get rid of this soon (say, 4.10) then we may want to make this // lazy, so we don't run around doing a fetch for every config we pull. But that means // we have to protect against the unresolved proxy in various places (scrub the proxies) // which has its own issues. Please let's kill this... @OneToMany(mappedBy = "configuration", fetch = FetchType.EAGER) @Cascade({ CascadeType.PERSIST, CascadeType.MERGE }) private Set<RawConfiguration> rawConfigurations; @Column(name = "NOTES") private String notes; @Column(name = "VERSION") private long version; @Column(name = "CTIME") private long ctime = System.currentTimeMillis(); @Column(name = "MTIME") private long mtime = System.currentTimeMillis(); public static Builder builder() { return new Builder(); } public Configuration() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public long getCreatedTime() { return this.ctime; } public long getModifiedTime() { return this.mtime; } @PrePersist void onPersist() { this.mtime = this.ctime = System.currentTimeMillis(); } @PreUpdate void onUpdate() { this.mtime = System.currentTimeMillis(); } /** * Adds the given property to this Configuration object. The property can be a * {@link PropertySimple simple property}, a {@link PropertyList list of properties} or a * {@link PropertyMap map of properties}. * * @param value the new property */ @Override public void put(Property value) { Map<String, Property> map = getMap(); if (value.getName()!=null) { map.put(value.getName().intern(),value); } else { map.put(value.getName(), value); } value.setConfiguration(this); } /** * Retrieves the given property from this Configuration object. The named property can be a * {@link PropertySimple simple property}, a {@link PropertyList list of properties} or a * {@link PropertyMap map of properties}. * * <p>Note that this only gets direct children of this Configuration. You cannot get a property from within a child * list or map via this method.</p> * * @param name the name of the property to be retrieved from this configuration * * @return the named property or <code>null</code> if there was no direct child with the given name */ @Override public Property get(String name) { return getMap().get(name); } /** * Removes the given property from this Configuration object. The named property can be a * {@link PropertySimple simple property}, a {@link PropertyList list of properties} or a * {@link PropertyMap map of properties}. * * <p>Note that this only removes direct children of this Configuration. You cannot remove a property from within a * child list or map via this method.</p> * * @param name the name of the property to be removed from this configuration * * @return the named property or <code>null</code> if there was no direct child with the given name */ public Property remove(String name) { return getMap().remove(name); } /** * Same as {@link #get(String)} except that it returns the object as a {@link PropertySimple}. * * @param name the name of the simple property to be retrieved * * @return the simple property with the given name, or <code>null</code> if there was no simple property with the * given name * * @throws ClassCastException if there was a property in this Configuration with the given name, but it was not of * type {@link PropertySimple} */ @Override public PropertySimple getSimple(String name) { return (PropertySimple) getMap().get(name); } /** * Gets the value of the simple property with the specified name. If the property is not defined, null will be * returned. * * @param name the name of the simple property * @return the value of the simple property with the specified name, or null if the property is not defined * * @since 4.4 */ public String getSimpleValue(String name) { return getSimpleValue(name, null); } public String getSimpleValue(String name, @Nullable String defaultValue) { PropertySimple property = (PropertySimple) getMap().get(name); return ((property != null) && (property.getStringValue() != null)) ? property.getStringValue() : defaultValue; } /** * Set the value of a simple property to the specified value. If the property is not yet defined, it will be added. * * @param name the name of the simple property to be set * @param value the new value for the property * * @since 4.4 */ public void setSimpleValue(String name, String value) { PropertySimple property = getSimple(name); if (value == null) { if (property != null) { remove(name); } } else { if (property == null) { put(new PropertySimple(name, value)); } else { property.setStringValue(value); } } } /** * Same as {@link #get(String)} except that it returns the object as a {@link PropertyList}. * * @param name the name of the list property to be retrieved * * @return the list property with the given name, or <code>null</code> if there was no list property with the given * name * * @throws ClassCastException if there was a property in this Configuration with the given name, but it was not of * type {@link PropertyList} */ @Override public PropertyList getList(String name) { return (PropertyList) getMap().get(name); } /** * Same as {@link #get(String)} except that it returns the object as a {@link PropertyMap}. * * @param name the name of the map property to be retrieved * * @return the map property with the given name, or <code>null</code> if there was no map property with the given * name * * @throws ClassCastException if there was a property in this Configuration with the given name, but it was not of * type {@link PropertyMap} */ @Override public PropertyMap getMap(String name) { return (PropertyMap) getMap().get(name); } /** * Returns the contents of this Configuration as a map. The keys to the map are the member properties' names and the * values are the properties themselves. * * <p><b>Warning:</b> Caution should be used when accessing the returned map. Please see * {@link PropertyMap the javadoc for this class} for more information.</p> * * @return the actual map of the property objects - this is <b>not</b> a copy */ @Override @NotNull public Map<String, Property> getMap() { return this.properties; } /** * Returns the names of all properties that are <i>direct</i> children of this Configuration object. * * @return child property names */ @NotNull public Collection<String> getNames() { return getMap().keySet(); } /** * Returns all the <i>direct</i> children of this Configuration. * This collection is fully modifiable and can be added to and removed from. * <p> * When adding a property to the collection returned from this method, its * {@link Property#getConfiguration() configuration property} is set to this instance. * * @return all child properties of this Configuration */ @NotNull @XmlElementRefs({ @XmlElementRef(name = "PropertyList", type = PropertyList.class), @XmlElementRef(name = "PropertySimple", type = PropertySimple.class), @XmlElementRef(name = "PropertyMap", type = PropertyMap.class) }) public Collection<Property> getProperties() { if (propertiesProxy == null) { propertiesProxy = new PropertiesProxy(); } return propertiesProxy; } /** * This method removes the existing set of properties and adds the properties * that got passed as argument to the list of properties. * * @param properties new set of properties */ public void setProperties(Collection<Property> properties) { //propertiesProxy is a mere view of the properties map. //thus, if one obtained an instance of propertiesProxy from the #getProperties() method and then tried to //pass that instance to this method, the result would be that the set of properties would be effectively //cleared (due to the assignment of the new map to the properties field, of which the propertiesProxy is a //view). We can short-circuit that behavior though, because if we determine that the propertiesProxy is being //assigned as the "new" set of properties, we can return immediately. Logically, the passed in properties are //identical to the ones already present in the properties map in that case. if (propertiesProxy == properties) { return; } // Don't replace the possible Hibernate proxy when orphanRemoval=true. It can cause // "collection with cascade=all-delete-orphan was no longer referenced" exceptions. this.properties.clear(); for (Property p : properties) { this.put(p); } } /** * Returns a map of all <i>direct</i> children of this Configuration that are of type {@link PropertyMap}. The * returned map is keyed on the {@link PropertyMap}'s names. * * @return map containing of all of the Configuration's direct {@link PropertyMap} children */ @NotNull public Map<String, PropertyMap> getMapProperties() { Map<String, PropertyMap> map = new LinkedHashMap<String, PropertyMap>(); for (Property prop : this.getProperties()) { if (prop instanceof PropertyMap) { map.put(prop.getName(), (PropertyMap) prop); } } return map; } /** * Returns a map of all <i>direct</i> children of this Configuration that are of type {@link PropertyList}. The * returned map is keyed on the {@link PropertyList}'s names. * * @return map containing of all of the Configuration's direct {@link PropertyList} children */ @NotNull public Map<String, PropertyList> getListProperties() { Map<String, PropertyList> map = new LinkedHashMap<String, PropertyList>(); for (Property prop : this.getProperties()) { if (prop instanceof PropertyList) { map.put(prop.getName(), (PropertyList) prop); } } return map; } /** * Returns a map of all <i>direct</i> children of this Configuration that are of type {@link PropertySimple}. The * returned map is keyed on the {@link PropertySimple}'s names. * * @return map containing of all of the Configuration's direct {@link PropertySimple} children */ @NotNull public Map<String, PropertySimple> getSimpleProperties() { Map<String, PropertySimple> map = new LinkedHashMap<String, PropertySimple>(); for (Property prop : this.getProperties()) { if (prop instanceof PropertySimple) { map.put(prop.getName(), (PropertySimple) prop); } } return map; } public Set<RawConfiguration> getRawConfigurations() { if (rawConfigurations == null) { rawConfigurations = new HashSet<RawConfiguration>(1); } return rawConfigurations; } public void addRawConfiguration(RawConfiguration rawConfiguration) { rawConfiguration.setConfiguration(this); if (rawConfigurations==null) { rawConfigurations = new HashSet<RawConfiguration>(1); } rawConfigurations.add(rawConfiguration); } public boolean removeRawConfiguration(RawConfiguration rawConfiguration) { if (rawConfigurations==null) { return true; } boolean removed = rawConfigurations.remove(rawConfiguration); if (removed) { rawConfiguration.setConfiguration(null); } if (rawConfigurations.isEmpty()) { rawConfigurations = Collections.emptySet(); } return removed; } public void cleanoutRawConfiguration() { if (rawConfigurations!=null && rawConfigurations.isEmpty()) { rawConfigurations = null; } } /** * Warning: This should probably not be performed on an attached entity, it could replace a * Hibernate proxy, which can lead to issues (especially when orphanRemoval=true) */ public void resize() { Map<String,Property> tmp =new LinkedHashMap<String, Property>(this.properties.size()); tmp.putAll(this.properties); this.properties=tmp; } public String getNotes() { return notes; } public void setNotes(String notes) { this.notes = notes; } public long getVersion() { return version; } public void setVersion(long version) { this.version = version; } /** * Clones this object in the same manner as {@link #deepCopy()}. * * @return a clone of this configuration * * @see #deepCopy() */ @SuppressWarnings("override") //@Override //GWT trips over this, WTH! public Configuration clone() { return deepCopy(); } /** * Makes a fully independent copy of this object and returns it. This means all children N-levels deep in the * hierarchy of this Configuration object are copied. * * <p>This is the underlying implementation for the {@link #clone()} method.</p> * * @return a clone of this configuration */ public Configuration deepCopy() { return deepCopy(true); } /** * Makes a fully independent copy of this object and returns it. This means all children N-levels deep in the * hierarchy of this Configuration object are copied. * * <p>If <code>keepIds</code> is <code>false</code>, then all IDs of all properties and the config object itself are * set to 0. Otherwise, they are kept the same and this method behaves the same as {@link #clone()}. * * @param keepIds if <code>false</code>, zero out all IDs * * @return the new copy */ public Configuration deepCopy(boolean keepIds) { Configuration copy = new Configuration(); if (keepIds) { copy.id = this.id; } copy.notes = this.notes; copy.version = this.version; createDeepCopyOfProperties(copy, keepIds); createDeepCopyOfRawConfigs(copy, keepIds); return copy; } public Configuration deepCopyWithoutProxies() { Configuration copy = new Configuration(); copy.notes = this.notes; copy.version = this.version; createDeepCopyOfProperties(copy, false); createDeepCopyOfRawConfigs(copy, false); return copy; } private void createDeepCopyOfRawConfigs(Configuration copy, boolean keepId) { if (rawConfigurations==null) { return; } for (RawConfiguration rawConfig : rawConfigurations) { copy.addRawConfiguration(rawConfig.deepCopy(keepId)); } } private void createDeepCopyOfProperties(Configuration copy, boolean keepId) { for (Property property : this.properties.values()) { copy.put(property.deepCopy(keepId)); } } /** * NOTE: A Configuration containing a null map is considered equal to a Configuration containing an empty map. */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Configuration)) { return false; } Configuration that = (Configuration) obj; if (this.properties == null || this.properties.isEmpty()){ if ( that.properties== null || that.properties.isEmpty()) { return true; } else { return false; } } else { if (!this.properties.equals(that.properties)) { return false; } } boolean rcEquals=true; if (this.rawConfigurations!=null) { rcEquals = this.getRawConfigurations().equals(that.getRawConfigurations()); } return rcEquals; } @Override public int hashCode() { int hc = 1; if (properties!=null ) { hc = properties.hashCode(); // TODO this requires loading of all properties and is expensive } if (rawConfigurations!=null) { int rchc = rawConfigurations.hashCode() ; hc = hc * rchc + 19; } return hc ; } @Override public String toString() { // Default to non-verbose (i.e. not printing the properties), since printing them makes toStrings extremely // verbose for large configs. final boolean verbose = false; return toString(verbose); } public String toString(boolean verbose) { StringBuilder builder = new StringBuilder("Configuration[id=").append(this.id); if (this.notes != null) { builder.append(", notes=").append(this.notes); } if (verbose) { builder.append(", properties["); boolean first = true; for (Property property : this.getMap().values()) { if (!first) { builder.append(", "); } else { first = false; } builder.append(property.getName()); builder.append("="); String stringValue; if (property instanceof PropertySimple) { stringValue = ((PropertySimple) property).getStringValue(); } else { stringValue = String.valueOf(property); } builder.append(stringValue); } builder.append("], rawConfigurations["); if (rawConfigurations!=null) { for (RawConfiguration rawConfig : rawConfigurations) { builder.append("[").append(rawConfig.getPath()).append(", ").append(rawConfig.getSha256()).append("]"); } } else { builder.append("-none-"); } builder.append("]"); } return builder.append("]").toString(); } /** * This listener runs after jaxb unmarshalling and reconnects children properties to their parent configurations (as * we don't send them avoiding cyclic references). */ public void afterUnmarshal(Object u, Object parent) { for (Property p : this.properties.values()) { p.setConfiguration(this); } } /** * Getter for the properties reference. * * @return {@code Map<String, Property>} */ public Map<String, Property> getAllProperties() { if (this.properties==null) { return Collections.emptyMap(); } return this.properties; } /* ************ Copied from JDK7, as JDK6 is lacking that * remove when we can require JDK 7 */ @SuppressWarnings("unchecked") @Deprecated public static <T> Iterator<T> emptyIterator() { return (Iterator<T>) EmptyIterator.EMPTY_ITERATOR; } @Deprecated private static class EmptyIterator<E> implements Iterator<E> { static final EmptyIterator<Object> EMPTY_ITERATOR = new EmptyIterator(); public boolean hasNext() { return false; } public E next() { throw new NoSuchElementException(); } public void remove() { throw new IllegalStateException(); } } }