/*
* 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 as published by
* the Free Software Foundation version 2 of the License.
*
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.configuration.metadata;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.ConfigurationTemplate;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionEnumeration;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionList;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionMap;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.PropertyGroupDefinition;
import org.rhq.core.domain.configuration.definition.constraint.Constraint;
import org.rhq.enterprise.server.RHQConstants;
/**
* Used to work with metadata defining generic configurations.
*/
@Stateless
public class ConfigurationMetadataManagerBean implements ConfigurationMetadataManagerLocal {
private final Log log = LogFactory.getLog(this.getClass());
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
public ConfigurationDefinitionUpdateReport updateConfigurationDefinition(ConfigurationDefinition newDefinition,
ConfigurationDefinition existingDefinition) {
ConfigurationDefinitionUpdateReport updateReport = new ConfigurationDefinitionUpdateReport(existingDefinition);
/*
* handle grouped and ungrouped properties separately. for ungrouped, we don't need to care about the group, but
* for the grouped ones we need to start at group level and then look at the properties. This is done below.
*
* First look at the ungrouped ones.
*/
List<PropertyDefinition> existingPropertyDefinitions = existingDefinition.getNonGroupedProperties();
List<PropertyDefinition> newPropertyDefinitions = newDefinition.getNonGroupedProperties();
if (existingPropertyDefinitions != null) {
for (PropertyDefinition newProperty : newPropertyDefinitions) {
PropertyDefinition existingProp = existingDefinition.get(newProperty.getName());
if (existingProp != null) {
log.debug("Updating nonGrouped property [" + existingProp + "]");
updatePropertyDefinition(existingProp, newProperty);
updateReport.addUpdatedPropertyDefinition(newProperty);
} else {
log.debug("Adding nonGrouped property [" + newProperty + "]");
existingDefinition.put(newProperty);
updateReport.addNewPropertyDefinition(newProperty);
}
}
existingDefinition = removeNoLongerUsedProperties(newDefinition, existingDefinition,
existingPropertyDefinitions);
} else {
// TODO what if existingDefinitions is null?
// we probably don't run in here, as the initial persisting is done
// somewhere else.
}
/*
* Now update / delete contained groups We need to be careful here, as groups are present in PropertyDefinition
* as "backlink" from PropertyDefinition to group
*/
List<PropertyGroupDefinition> existingGroups = existingDefinition.getGroupDefinitions();
List<PropertyGroupDefinition> newGroups = newDefinition.getGroupDefinitions();
List<PropertyGroupDefinition> toPersist = missingInFirstList(existingGroups, newGroups);
List<PropertyGroupDefinition> toDelete = missingInFirstList(newGroups, existingGroups);
List<PropertyGroupDefinition> toUpdate = intersection(existingGroups, newGroups);
// delete groups no longer present
for (PropertyGroupDefinition group : toDelete) {
List<PropertyDefinition> groupedDefinitions = existingDefinition.getPropertiesInGroup(group.getName());
// first look for contained stuff
for (PropertyDefinition def : groupedDefinitions) {
log.debug("Removing property [" + def + "] from group [" + group + "]");
existingPropertyDefinitions.remove(def);
existingDefinition.getPropertyDefinitions().remove(def.getName());
def.setPropertyGroupDefinition(null);
entityManager.remove(def);
}
// then remove the definition itself
log.debug("Removing group [" + group + "]");
existingGroups.remove(group);
entityManager.remove(group);
}
// update existing groups that stay
for (PropertyGroupDefinition group : toUpdate) {
String groupName = group.getName();
List<PropertyDefinition> newGroupedDefinitions = newDefinition.getPropertiesInGroup(groupName);
for (PropertyDefinition nDef : newGroupedDefinitions) {
PropertyDefinition existingProperty = existingDefinition.getPropertyDefinitions().get(nDef.getName());
if (existingProperty != null) {
log.debug("Updating property [" + nDef + "] in group [" + group + "]");
updatePropertyDefinition(existingProperty, nDef);
updateReport.addUpdatedPropertyDefinition(nDef);
} else {
log.debug("Adding property [" + nDef + "] to group [" + group + "]");
existingDefinition.put(nDef);
updateReport.addNewPropertyDefinition(nDef);
}
}
// delete outdated properties of this group
existingDefinition = removeNoLongerUsedProperties(newDefinition, existingDefinition,
existingDefinition.getPropertiesInGroup(groupName));
}
// persist new groups
for (PropertyGroupDefinition group : toPersist) {
// First persist a new group definition and then link the properties to it
log.debug("Persisting new group [" + group + "]");
entityManager.persist(group);
existingGroups.add(group); // iterating over this does not update the underlying crap
List<PropertyDefinition> defs = newDefinition.getPropertiesInGroup(group.getName());
Map<String, PropertyDefinition> exPDefs = existingDefinition.getPropertyDefinitions();
for (PropertyDefinition def : defs) {
entityManager.persist(def);
def.setPropertyGroupDefinition(group);
def.setConfigurationDefinition(existingDefinition);
if (!exPDefs.containsKey(def.getName())) {
updateReport.addNewPropertyDefinition(def);
}
exPDefs.put(def.getName(), def);
}
}
/*
* Now work on the templates.
*/
Map<String, ConfigurationTemplate> existingTemplates = existingDefinition.getTemplates();
Map<String, ConfigurationTemplate> newTemplates = newDefinition.getTemplates();
List<String> toRemove = new ArrayList<String>();
List<String> templatesToUpdate = new ArrayList<String>();
for (String name : existingTemplates.keySet()) {
if (newTemplates.containsKey(name)) {
templatesToUpdate.add(name);
} else {
toRemove.add(name);
}
}
for (String name : toRemove) {
log.debug("Removing template [" + name + "]");
ConfigurationTemplate template = existingTemplates.remove(name);
entityManager.remove(template);
}
for (String name : templatesToUpdate) {
log.debug("Updating template [" + name + "]");
updateTemplate(existingDefinition.getTemplate(name), newTemplates.get(name));
}
for (String name : newTemplates.keySet()) {
// add completely new templates
if (!existingTemplates.containsKey(name)) {
log.debug("Adding template [" + name + "]");
ConfigurationTemplate newTemplate = newTemplates.get(name);
// we need to set a valid configurationDefinition, where we will live on.
newTemplate.setConfigurationDefinition(existingDefinition);
entityManager.persist(newTemplate);
existingTemplates.put(name, newTemplate);
}
}
return updateReport;
}
private void updateTemplate(ConfigurationTemplate existingDT, ConfigurationTemplate newDT) {
try {
Configuration existConf = existingDT.getConfiguration();
Configuration newConf = newDT.getConfiguration();
Collection<String> exNames = existConf.getNames();
Collection<String> newNames = newConf.getNames();
List<String> toRemove = new ArrayList<String>();
for (String name : exNames) {
Property prop = newConf.get(name);
if (prop instanceof PropertySimple) {
PropertySimple ps = newConf.getSimple(name);
if (ps != null) {
Property eprop = existConf.get(name);
if (eprop instanceof PropertySimple) {
PropertySimple exps = existConf.getSimple(name);
if (ps.getStringValue() != null) {
exps.setStringValue(ps.getStringValue());
// System.out.println(" updated " + name + " to value " + ps.getStringValue());
}
} else {
if (eprop != null) {
// System.out.println("Can't yet handle target prop: " + eprop);
}
}
} else { // property not in new template -> deleted
toRemove.add(name);
}
} else {
if (prop != null) {
// System.out.println("Can't yet handle source prop: " + prop);
}
}
}
for (String name : toRemove)
existConf.remove(name);
// now check for new names and add them
for (String name : newNames) {
if (!exNames.contains(name)) {
Property prop = newConf.get(name);
if (prop instanceof PropertySimple) {
PropertySimple ps = newConf.getSimple(name);
if (ps.getStringValue() != null) {
// TODO add a new property
// Collection<Property> properties = existConf.getProperties();
// properties = new ArrayList<Property>(properties);
// properties.add(ps);
// existConf.setProperties(properties);
Property property = ps.deepCopy(false);
existConf.put(property);
}
}
}
}
entityManager.flush();
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* Removes PropertyDefinition items from the configuration
*
* @param newConfigDef new configuration being merged into the existing one
* @param existingConfigDef existing persisted configuration
* @param existingProperties list of existing properties to inspect for potential removal
*/
private ConfigurationDefinition removeNoLongerUsedProperties(ConfigurationDefinition newConfigDef,
ConfigurationDefinition existingConfigDef, List<PropertyDefinition> existingProperties) {
List<PropertyDefinition> propDefsToDelete = new ArrayList<PropertyDefinition>();
for (PropertyDefinition existingPropDef : existingProperties) {
PropertyDefinition newPropDef = newConfigDef.get(existingPropDef.getName());
if (newPropDef == null) {
// not in new configuration
propDefsToDelete.add(existingPropDef);
}
}
if (!propDefsToDelete.isEmpty()) {
log.debug("Deleting obsolete props from configDef [" + existingConfigDef + "]: " + propDefsToDelete);
for (PropertyDefinition propDef : propDefsToDelete) {
existingConfigDef.getPropertyDefinitions().remove(propDef.getName());
existingProperties.remove(propDef); // does not operate on original list!!
}
existingConfigDef = entityManager.merge(existingConfigDef);
}
return existingConfigDef;
}
/**
* Update objects of type on:property (simple-property, map-property, list-property
*
* @param existingProperty
* @param newProperty
*/
private void updatePropertyDefinition(PropertyDefinition existingProperty, PropertyDefinition newProperty) {
existingProperty.setDescription(newProperty.getDescription());
existingProperty.setDisplayName(newProperty.getDisplayName());
existingProperty.setActivationPolicy(newProperty.getActivationPolicy());
existingProperty.setVersion(newProperty.getVersion());
existingProperty.setRequired(newProperty.isRequired());
existingProperty.setReadOnly(newProperty.isReadOnly());
existingProperty.setSummary(newProperty.isSummary());
existingProperty.setPropertyGroupDefinition(newProperty.getPropertyGroupDefinition());
/*
* After the general things have been set, go through the subtypes of PropertyDefinition. If the new type is the
* same as the old, we update. Else we simply replace
*/
if (existingProperty instanceof PropertyDefinitionMap) {
if (newProperty instanceof PropertyDefinitionMap) {
// alter existingPropDefs to reflect newPropDefs
Map<String, PropertyDefinition> existingPropDefs = ((PropertyDefinitionMap) existingProperty)
.getMap();
Map<String, PropertyDefinition> newPropDefs = ((PropertyDefinitionMap) newProperty)
.getMap();
Set<String> newKeys = newPropDefs.keySet();
// remove obsolete propDefs
List<String> doomedKeys = new ArrayList<String>();
for (String existingKey : existingPropDefs.keySet()) {
if (!newKeys.contains(existingKey)) {
doomedKeys.add(existingKey);
}
}
for (String doomedKey : doomedKeys) {
PropertyDefinition doomed = existingPropDefs.get(doomedKey);
existingPropDefs.remove(doomedKey);
entityManager.remove(entityManager.find(PropertyDefinition.class, doomed.getId()));
}
int order = 0;
for (String key : newKeys) {
PropertyDefinition existingPropDef = existingPropDefs.get(key);
PropertyDefinition newPropDef = newPropDefs.get(key);
if (null == existingPropDef) {
newPropDef.setOrder(order++);
newPropDef.setParentPropertyMapDefinition((PropertyDefinitionMap) existingProperty);
entityManager.persist(newPropDef);
existingPropDefs.put(key, newPropDef);
} else {
existingPropDef.setOrder(order++);
updatePropertyDefinition(existingPropDef, newPropDef);
}
}
existingProperty = entityManager.merge(existingProperty);
} else { // different type
replaceProperty(existingProperty, newProperty);
}
} else if (existingProperty instanceof PropertyDefinitionList) {
PropertyDefinitionList exList = (PropertyDefinitionList) existingProperty;
if (newProperty instanceof PropertyDefinitionList) {
PropertyDefinitionList newList = (PropertyDefinitionList) newProperty;
replaceListProperty(exList, newList);
} else { // simple property or map-property
replaceProperty(existingProperty, newProperty);
}
} else if (existingProperty instanceof PropertyDefinitionSimple) {
PropertyDefinitionSimple existingPDS = (PropertyDefinitionSimple) existingProperty;
if (newProperty instanceof PropertyDefinitionSimple) {
PropertyDefinitionSimple newPDS = (PropertyDefinitionSimple) newProperty;
existingPDS.setType(newPDS.getType());
// handle <property-options>?
List<PropertyDefinitionEnumeration> existingOptions = existingPDS.getEnumeratedValues();
List<PropertyDefinitionEnumeration> newOptions = newPDS.getEnumeratedValues();
List<PropertyDefinitionEnumeration> toPersist = missingInFirstList(existingOptions, newOptions);
List<PropertyDefinitionEnumeration> toDelete = missingInFirstList(newOptions, existingOptions);
List<PropertyDefinitionEnumeration> changed = intersection(existingOptions, newOptions);
// sync the enumerated values and then merge the changes into the PDS, I think this
// solves previous issues with orderIndex values.
// First remove obsolete values
for (PropertyDefinitionEnumeration pde : toDelete) {
existingPDS.removeEnumeratedValues(pde);
}
// save new ones
for (PropertyDefinitionEnumeration pde : toPersist) {
existingPDS.addEnumeratedValues(pde);
entityManager.persist(pde);
}
// update others
for (PropertyDefinitionEnumeration pde : changed) {
for (PropertyDefinitionEnumeration nPde : newOptions) {
if (nPde.equals(pde)) {
pde.setValue(nPde.getValue());
pde.setName(nPde.getName());
}
}
}
existingPDS = entityManager.merge(existingPDS);
// handle <constraint> [0..*]
Set<Constraint> exCon = existingPDS.getConstraints();
if (exCon.size() > 0) {
for (Constraint con : exCon) {
con.setPropertyDefinitionSimple(null);
entityManager.remove(con);
}
existingPDS.getConstraints().clear(); // clear out existing
}
for (Constraint con : newPDS.getConstraints()) {
existingPDS.addConstraints(con);
}
// handle <defaultValue> [0..1]
existingPDS.setDefaultValue(newPDS.getDefaultValue());
// handle <c:source>
existingPDS.setOptionsSource(newPDS.getOptionsSource());
} else {
// other type
replaceProperty(existingProperty, newProperty);
}
}
}
/**
* Replace the existing property of a given type with a new property of a (possibly) different type
*
* @param existingProperty the existing prop
* @param newProperty the new prop that should replace the existing prop
*/
private void replaceProperty(PropertyDefinition existingProperty, PropertyDefinition newProperty) {
ConfigurationDefinition configDef = existingProperty.getConfigurationDefinition();
// First take id from existing prop, and replace existing prop in the config def.
newProperty.setId(existingProperty.getId());
configDef.put(newProperty);
entityManager.remove(existingProperty);
entityManager.merge(configDef);
}
/**
* This replaces an existing list property def with a new list property definition. Primarily it replaces
* the member prop def for the list. If the member prop def is a nested structure the whole thing
* is replaced from the top.
*
* @param exList the existing prop def list
* @param newList the new prop def list
*/
private void replaceListProperty(PropertyDefinitionList exList, PropertyDefinitionList newList) {
PropertyDefinition doomedMemberDef = null;
if (newList.getMemberDefinition() == null) {
log.error("\n\n!! Member definition for new list property [" + newList.getName()
+ "] is null - check and fix the plugin descriptor\n");
return;
}
// We did not have a member definition before (which is wrong )
// we need to add it now
// only remove the existing member if it is a different entity
PropertyDefinition exListMemberDefinition = exList.getMemberDefinition();
if (exListMemberDefinition != null && exListMemberDefinition.getId() != newList.getMemberDefinition().getId()) {
doomedMemberDef = exListMemberDefinition;
}
exList.setMemberDefinition(newList.getMemberDefinition());
exList.setMax(newList.getMax());
exList.setMin(newList.getMin());
// BZ 594706
// Don't clean this up here because it's causing deadlocks in Oracle. Instead we'll just leave
// garbage in the db. Although annoying, and confusing for db queries, it's not a lot of data,
// just some extra prop defs and prop_def_enums.
if (null != doomedMemberDef) {
// entityManager.remove(doomedMemberDef);
if (log.isDebugEnabled()) {
log.debug("Ignoring cleanup of [" + doomedMemberDef + "] due to BZ 594706");
}
}
entityManager.merge(exList);
}
/**
* Return a list containing those element that are in reference, but not in first. Both input lists are not modified
*
* @param first
* @param reference
*
* @return list containing those element that are in reference, but not in first
*/
private <T> List<T> missingInFirstList(List<T> first, List<T> reference) {
List<T> result = new ArrayList<T>();
if (reference != null) {
// First collection is null -> everything is missing
if (first == null) {
result.addAll(reference);
return result;
}
// else loop over the set and sort out the right items.
for (T item : reference) {
if (!first.contains(item)) {
result.add(item);
}
}
}
return result;
}
/**
* Return a new List with elements both in first and second passed collection.
*
* @param first First list
* @param second Second list
*
* @return a new set (depending on input type) with elements in first and second
*/
private <T> List<T> intersection(List<T> first, List<T> second) {
List<T> result = new ArrayList<T>();
if ((first != null) && (second != null)) {
result.addAll(first);
result.retainAll(second);
}
return result;
}
}