/*
* RHQ Management Platform
* Copyright (C) 2005-2008 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.definition;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
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.OrderBy;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlSeeAlso;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The entire definition for a {@link Configuration}. This includes mapped property
* definitions of arbitrary complexity, made up of {@link PropertyDefinitionSimple}s, {@link PropertyDefinitionList}s,
* and {@link PropertyDefinitionMap}s.
*
* @author Greg Hinkle
*/
@Entity
@NamedQueries({
@NamedQuery(name = ConfigurationDefinition.QUERY_FIND_DEPLOYMENT_BY_PACKAGE_TYPE_ID, query = "SELECT cd FROM PackageType pt JOIN pt.deploymentConfigurationDefinition cd "
+ "WHERE pt.id = :packageTypeId"),
@NamedQuery(name = ConfigurationDefinition.QUERY_FIND_RESOURCE_BY_RESOURCE_TYPE_ID, query = "SELECT cd FROM ResourceType rt JOIN rt.resourceConfigurationDefinition cd "
+ "WHERE rt.id = :resourceTypeId AND rt.deleted = false"),
@NamedQuery(name = ConfigurationDefinition.QUERY_FIND_PLUGIN_BY_RESOURCE_TYPE_ID, query = "SELECT cd FROM ResourceType rt JOIN rt.pluginConfigurationDefinition cd "
+ "WHERE rt.id = :resourceTypeId AND rt.deleted = false") })
@SequenceGenerator(allocationSize = org.rhq.core.domain.util.Constants.ALLOCATION_SIZE, name = "RHQ_CONFIG_DEF_ID_SEQ", sequenceName = "RHQ_CONFIG_DEF_ID_SEQ")
@Table(name = "RHQ_CONFIG_DEF")
@XmlSeeAlso({ PropertyDefinitionSimple.class, PropertyDefinitionList.class, PropertyDefinitionMap.class })
public class ConfigurationDefinition implements Serializable {
private static final long serialVersionUID = 1L;
public static final String QUERY_FIND_DEPLOYMENT_BY_PACKAGE_TYPE_ID = "ConfigurationDefinition.findDeploymentByPackageTypeId";
public static final String QUERY_FIND_RESOURCE_BY_RESOURCE_TYPE_ID = "ConfigurationDefinition.findResourceByResourceTypeId";
public static final String QUERY_FIND_PLUGIN_BY_RESOURCE_TYPE_ID = "ConfigurationDefinition.findPluginByResourceTypeId";
@Column(name = "id")
@GeneratedValue(generator = "RHQ_CONFIG_DEF_ID_SEQ", strategy = GenerationType.AUTO)
@Id
private int id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "description")
private String description;
// use the propDef name as the map key
@MapKey(name = "name")
// base the insert-order on propDef Id (asc). Since rows are inserted in the order presented in the
// plugin descriptor, iterating on the map (LinkedHashMap) will give us the same ordering. So, unless
// propDef.order is set and used for ordering by the accessing code, we'll default to the descriptor order.
@OrderBy
@OneToMany(mappedBy = "configurationDefinition", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Map<String, PropertyDefinition> propertyDefinitions = new LinkedHashMap<String, PropertyDefinition>();
// use the configTemplate name as the map key
@MapKey(name = "name")
// base the insert-order on configTemplate Id (asc). Since rows are inserted in the order presented in the
// plugin descriptor, iterating on the map (LinkedHashMap) will give us the same ordering.
@OrderBy
@OneToMany(mappedBy = "configurationDefinition", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Map<String, ConfigurationTemplate> templates = new LinkedHashMap<String, ConfigurationTemplate>();
@Column(name = "config_format")
@Enumerated(EnumType.STRING)
private ConfigurationFormat configurationFormat;
protected ConfigurationDefinition() {
// JPA use only
}
public ConfigurationDefinition(@NotNull
String name, String description) {
this.name = name.intern();
this.description = description;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NotNull
public String getName() {
return name;
}
public void setName(@NotNull
String name) {
// Need to protect due to possible deserialization from Coregui.
if (name != null) {
this.name = name.intern();
} else {
this.name = null;
}
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
/**
* This property is currently used only for resource configuration which may support structured config, raw
* config, both, or neither. For an older plugin that was implemented prior to raw config support, this would be
* <code>null</code>.
*
* @return The configuration format which may be structured, raw, or both. <code>null</code> indicates an older
* plugin that was developed prior to raw configuration being supported.
*/
public ConfigurationFormat getConfigurationFormat() {
if (null == configurationFormat) {
configurationFormat = ConfigurationFormat.STRUCTURED;
}
return configurationFormat;
}
public void setConfigurationFormat(ConfigurationFormat configurationFormat) {
this.configurationFormat = configurationFormat;
}
/**
* Get the properties for this configuration. This is the only method that returns a Collection, where an update
* will be reflected in the database.
*
* @return the property definitions, not a copy
*/
@NotNull
public Map<String, PropertyDefinition> getPropertyDefinitions() {
if (this.propertyDefinitions == null) {
this.propertyDefinitions = new LinkedHashMap<String, PropertyDefinition>();
}
return this.propertyDefinitions;
}
public void setPropertyDefinitions(Map<String, PropertyDefinition> propertyDefinitions) {
if (this.propertyDefinitions == propertyDefinitions) {
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.propertyDefinitions = getPropertyDefinitions();
this.propertyDefinitions.clear();
if (null != propertyDefinitions) {
this.propertyDefinitions.putAll(propertyDefinitions);
}
}
public void put(PropertyDefinition propertyDefinition) {
if (this.propertyDefinitions == null) {
this.propertyDefinitions = new LinkedHashMap<String, PropertyDefinition>();
}
this.propertyDefinitions.put(propertyDefinition.getName(), propertyDefinition);
propertyDefinition.setConfigurationDefinition(this);
}
public PropertyDefinition get(String name) {
return this.propertyDefinitions.get(name);
}
public PropertyDefinitionSimple getPropertyDefinitionSimple(String name) {
return (PropertyDefinitionSimple) this.propertyDefinitions.get(name);
}
public PropertyDefinitionList getPropertyDefinitionList(String name) {
return (PropertyDefinitionList) this.propertyDefinitions.get(name);
}
public PropertyDefinitionMap getPropertyDefinitionMap(String name) {
return (PropertyDefinitionMap) this.propertyDefinitions.get(name);
}
/**
* Goes through the properties of this definition and builds a list of the groups that contain its properties. The
* list is sorted by group order index, then by name.
*
* <p/>NOTE: updates of the result List will not be reflected in the database!
*
* @return all groups holding properties in this definition
*/
@NotNull
public List<PropertyGroupDefinition> getGroupDefinitions() {
Set<PropertyGroupDefinition> groupSet = new HashSet<PropertyGroupDefinition>();
for (PropertyDefinition propertyDefinition : getPropertyDefinitions().values()) {
if (propertyDefinition.getPropertyGroupDefinition() != null) {
groupSet.add(propertyDefinition.getPropertyGroupDefinition());
}
}
List<PropertyGroupDefinition> groups = new ArrayList<PropertyGroupDefinition>(groupSet.size());
for (PropertyGroupDefinition group : groupSet) {
groups.add(group);
}
Collections.sort(groups, new GroupComparator());
return groups;
}
/**
* Retrieve property definitions for properties in a group with the provided name. The list is sorted by property
* definition order index, then by name.
*
* <p/>NOTE: updates of the result List will not be reflected in the database!
*
* @param groupName the name of the group
*
* @return the set of properties in a group with the provided name
*/
@NotNull
public List<PropertyDefinition> getPropertiesInGroup(String groupName) {
List<PropertyDefinition> propertyDefinitions = new ArrayList<PropertyDefinition>();
for (PropertyDefinition propertyDefinition : this.propertyDefinitions.values()) {
PropertyGroupDefinition group = propertyDefinition.getPropertyGroupDefinition();
if ((group != null) && group.getName().equals(groupName)) {
propertyDefinitions.add(propertyDefinition);
}
}
Collections.sort(propertyDefinitions, new PropertyDefinitionComparator());
return propertyDefinitions;
}
/**
* Retrieve property definitions for properties that are not in a group. The list is sorted by property definition
* order index, then by name.
*
* <p/>DO NOT use this for updating the collection, as the updates will not be persisted!
*
* <p/>
*
* @return the set of properties that are not in a group
*
* @see #getPropertyDefinitions()
*/
@NotNull
public List<PropertyDefinition> getNonGroupedProperties() {
List<PropertyDefinition> propertyDefinitions = new ArrayList<PropertyDefinition>();
for (PropertyDefinition propertyDefinition : this.propertyDefinitions.values()) {
if (propertyDefinition.getPropertyGroupDefinition() == null) {
propertyDefinitions.add(propertyDefinition);
}
}
Collections.sort(propertyDefinitions, new PropertyDefinitionComparator());
return propertyDefinitions;
}
/**
* This is a list of templates for this configuration definition. Each template relates to a single "configuration"
* object which holds the values for the template.
*
* @return the set of templates for this configuration
*/
@NotNull
public Map<String, ConfigurationTemplate> getTemplates() {
if (this.templates == null) {
this.templates = new LinkedHashMap<String, ConfigurationTemplate>();
}
return templates;
}
public void putTemplate(ConfigurationTemplate template) {
template.setConfigurationDefinition(this);
getTemplates().put(template.getName(), template);
}
public ConfigurationTemplate removeTemplate(ConfigurationTemplate template) {
return getTemplates().remove(template.getName());
}
/**
* If there is a default template, return it, otherwise return null.
*
* @return a template if one is default, null otherwise
*/
@Nullable
public ConfigurationTemplate getDefaultTemplate() {
return getTemplates().get(ConfigurationTemplate.DEFAULT_TEMPLATE_NAME);
}
/**
* Returns the template indicated by the specified name
*
* @param name name of the template to return
*
* @return <code>ConfigurationTemplate</code> with the specified name; <code>null</code> if no template by that name
* exists.
*/
public ConfigurationTemplate getTemplate(@NotNull
String name) {
return getTemplates().get(name);
}
/**
* Returns the set of all template names for this definition.
*
* @return set of template names.
*/
public Set<String> templateNamesSet() {
return getTemplates().keySet();
}
/**
* Get a copy of this {@link ConfigurationDefinition}. The property definitions will be returned in
* a new Map, which can be safely manipulated without affecting the original Map. It is not a deep copy, it
* is backed by the same {@link #PropertyDefinition} objects as the original and changes to the Map entries will
* modify the originals. The templates Map is treated the same way. The id is not copied.
*
* @return the copy
*/
public ConfigurationDefinition copy() {
ConfigurationDefinition copy = new ConfigurationDefinition(this.getName(), this.getDescription());
copy.setPropertyDefinitions(this.getPropertyDefinitions());
copy.setConfigurationFormat(this.getConfigurationFormat());
copy.templates = new LinkedHashMap<String, ConfigurationTemplate>(this.getTemplates());
return copy;
}
@Override
public String toString() {
return "ConfigurationDefinition[id=" + this.id + ", name=" + this.name + "]";
}
private class PropertyDefinitionComparator implements Comparator<PropertyDefinition> {
public int compare(PropertyDefinition propDef1, PropertyDefinition propDef2) {
return (propDef1.getOrder() == propDef2.getOrder()) ? propDef1.getName().compareTo(propDef2.getName())
: Integer.valueOf(propDef1.getOrder()).compareTo(propDef2.getOrder());
}
}
private class GroupComparator implements Comparator<PropertyGroupDefinition> {
public int compare(PropertyGroupDefinition group1, PropertyGroupDefinition group2) {
return (group1.getOrder() == group2.getOrder()) ? group1.getName().compareTo(group2.getName()) : Integer
.valueOf(group1.getOrder()).compareTo(group2.getOrder());
}
}
}