/*
* RHQ Management Platform
* Copyright (C) 2005-2011 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.util.ArrayList;
import java.util.List;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.jetbrains.annotations.NotNull;
/**
* Holds an indexed list of child {@link Property properties}. This can hold any number of properties, including
* additional lists and maps of properties (which means you can have N-levels of hierarchical data).
*
* <p>This list will store the properties in the order they are {@link #add(Property) added}.</p>
*
* <p>Caution must be used when accessing this object. This class is not thread safe and, for entity persistence, the
* child properties must have their {@link Property#getParentList()} field set. This is done for you when using the
* {@link #add(Property)} method.</p>
*
* @author Jason Dobies
* @author Greg Hinkle
*/
@DiscriminatorValue("list")
@Entity
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class PropertyList extends Property {
private static final long serialVersionUID = 1L;
// 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 = "parentList", targetEntity = Property.class, fetch = FetchType.EAGER, orphanRemoval = true)
// Order by primary key which will also put the list elements in chronological order.
// Note, if we decide at some point to add support in the GUI for reordering list elements, we'll
// need to add a new ORDER column and order by that.
@OrderBy
private List<Property> list = new ArrayList<Property>();
@Transient
String memberPropertyName;
/* no-arg constructor required by EJB spec and Externalizable (Externalizable also requires it to be public) */
public PropertyList() {
}
/**
* Creates a new, empty {@link PropertyList} object that is associated with the given name.
*
* @param name the name of the list itself
*/
public PropertyList(@NotNull String name) {
setName(name);
}
protected PropertyList(PropertyList original, boolean keepId) {
super(original, keepId);
}
/**
* Creates a new {@link PropertyList} object that is associated with the given name and has the given properties as
* its initial list of child properties. All properties found in <code>startingList</code> will have their
* {@link Property#setParentList(PropertyList) parent list} set to this newly constructed list.
*
* @param name the name of the list itself
* @param startingList a list of properties to be immediately added to this list
*/
public PropertyList(@NotNull String name, @NotNull Property... startingList) {
this(name);
for (Property property : startingList) {
add(property);
}
}
/**
* Returns the children of this list.
*
* <p><b>Warning:</b> Caution should be used when accessing the returned list. Please see
* {@link PropertyList the javadoc for this class} for more information.</p>
*
* @return the list of child properties
*/
@NotNull
public List<Property> getList() {
if (this.list == null) {
this.list = new ArrayList<Property>();
}
return this.list;
}
/**
* Sets the list of child properties directly to the given <code>list</code> reference. This means the actual <code>
* list</code> object is stored internally in this object. Changes made to <code>list</code> will be reflected back
* into this object.
*
* <p><b>Warning:</b> Caution should be used when setting this object's internal list. Please see
* {@link PropertyList the javadoc for this class} for more information.</p>
*
* @param list the new list used internally by this object
*/
public void setList(List<Property> list) {
if (list != null) {
for (Property property : list) {
add(property);
}
}
}
/**
* Adds a child property to the end of this list. This method also sets the
* {@link Property#setParentList(PropertyList) parent list} for the child property to make persistence work.
*
* @param property the property to add to this list
*/
public void add(@NotNull Property property) {
if (this.memberPropertyName == null) {
this.memberPropertyName = property.getName();
}
if (!property.getName().equals(this.memberPropertyName)) {
throw new IllegalStateException("All properties in a PropertyList (id=[" + getId() + "], name=["
+ getName() + "]) must have the same name: [" + property.getName() + "] != [" + this.memberPropertyName
+ "]");
}
getList().add(property);
property.setParentList(this);
}
/**
* NOTE: An PropertyList containing a null list is considered equal to a PropertyList containing an empty list.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || !(obj instanceof PropertyList)) {
return false;
}
if (!super.equals(obj)) {
return false; // superclass checks equality of the name fields
}
PropertyList that = (PropertyList) obj;
if ((this.list == null) || this.list.isEmpty()) {
// NOTE: Use that.getList(), rather than that.list, in case 'that' is a JPA/Hibernate proxy, to
// force loading of the List.
return (that.getList() == null) || that.getList().isEmpty();
}
return this.list.containsAll(that.getList()) && that.getList().containsAll(this.list);
}
@Override
public int hashCode() {
int result = super.hashCode(); // superclass hashCode is derived from the name field
result = (31 * result) + (((this.list != null) && !this.list.isEmpty()) ? this.list.hashCode() : 0);
return result;
}
public PropertyList deepCopy(boolean keepId) {
PropertyList copy = new PropertyList(this, keepId);
for (Property property : list) {
copy.add(property.deepCopy(false));
}
return copy;
}
@Override
protected void appendToStringInternals(StringBuilder str) {
super.appendToStringInternals(str);
str.append(", list=").append(getList());
}
/**
* This listener runs after jaxb unmarshalling and reconnects children properties to their parent list (as we don't
* send them avoiding cyclic references).
*/
public void afterUnmarshal(Object u, Object parent) {
for (Property p : this.list) {
p.setParentList(this);
}
}
}