-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: [EntityManager] Composite Structures with SortedSet
PostPosted: Thu Jun 12, 2008 9:40 am 
Newbie

Joined: Sat Nov 22, 2003 11:49 am
Posts: 11
Location: Minneapolis, Minnesota
Hibernate version:
hibernate-3.2.6.GA
hibernate-annotations-3.3.1.GA
hibernate-entitymanager-3.3.2.GA

Mapping documents:
Code:
@Entity(name = "folder")
@Proxy(lazy = false)
@EntityListeners({AuditEntityListener.class, VersionedEntityListener.class})
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, length = 2)
public abstract class AbstractFolderItem extends AbstractLightComposite implements FolderItem {

    /**
     * Parent of this FolderItem.
     */
    private Folder parentFolder;

    /**
     * Is this a shared FolderItem?
     */
    private boolean shared;

    protected EntitySupport<String> entitySupport = new EntitySupport<String>();

    /**
     * Default constructor to allow JPA to create and then use setters.
     */
    protected AbstractFolderItem() {
        super();

        try {
            if (null == getIdentifier()) {
                setIdentifier(GUIDGenerator.getInstance().getUnformatedUUID());
            }
        } catch (PropertyVetoException e) {
            throw new IllegalStateException(e);
        }
    }

    protected AbstractFolderItem(User aCreator) {
        this();

        try {
            setOwner(aCreator);
            setAnnotation(new DefaultFolderItemAnnotation(aCreator));
        } catch (PropertyVetoException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Return a value used to uniquely identify this instance. Will not be null.
     *
     * @return a value used to uniquely identify this instance. Will not be null.
     */
    @Id
    public String getIdentifier() {
        return entitySupport.getIdentifier();
    }

    /**
     * Set a new identifier value which is used to uniquely identify this instance.
     *
     * @param anIdentifier a value used to uniquely identify this instance. Must not be null.
     * @throws java.beans.PropertyVetoException
     *          if <code>anIdentifier</code> is null;
     */
    public void setIdentifier(String anIdentifier) throws PropertyVetoException {
        entitySupport.setIdentifier(anIdentifier);
    }

    /**
     * If this instance is not yet persisted, returns null. Otherwise, returns the current version
     * of this instance.
     *
     * @return If this instance is not yet persisted, returns null. Otherwise, returns the current
     *         version of this instance.
     */
    @Version
    @Column(nullable = false)
    public Integer getVersion() {
        return entitySupport.getVersion();
    }

    /**
     * Sets the version of this instance. Null is allowed.
     *
     * @param nuVersion - the new version of this instance. Null is allowed.
     */
    public void setVersion(Integer nuVersion) {
        entitySupport.setVersion(nuVersion);
    }

    /**
     * Returns this folderItem's parent or null if this folderItem has no parent(s).
     * <p/>
     * The mapping for this parent reference is based on
     * http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e1355.
     *
     * @return this folderItem's parent or null if this folderItem has no parent(s).
     */
    @ManyToOne(targetEntity = DefaultFolder.class)
    @JoinColumn(name = "parent_folder_id")
    public Folder getParentFolder() {
        return parentFolder;
    }

    protected void setParentFolder(Folder nuParentFolder) {
        parentFolder = nuParentFolder;
    }

    /**
     * Compares this FolderItem to another given FolderItem. Folders are higher on the list than non-folders. After that is is alphabetical.
     *
     * @param otherFolderItem the Object to be compared.
     * @return a negative integer, zero, or a positive integer as this object
     *         is less than, equal to, or greater than the specified object.
     */
    public int compareTo(FolderItem otherFolderItem) {
        // Null item?
        if (null == otherFolderItem) return 1;
        else if (this.equals(otherFolderItem)) return 0;

        boolean isFolder = getAllowsChildren();
        boolean isOtherFolder = otherFolderItem.getAllowsChildren();

        // I'm a folder, and the other is not?
        if (isFolder && !isOtherFolder) {
            return -1;
        }

        // I'm not a folder, and the other is?
        if (!isFolder && isOtherFolder) {
            return 1;
        }

        // Both are the same type, sort alphabetically...
        return getName().compareToIgnoreCase(otherFolderItem.getName());
    }

    /**
     * Returns a hash code value for the object.
     *
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode() {
        String id = getIdentifier();
        return null == id ? 0 : id.hashCode();
    }

    /**
     * Indicates whether some other object is "equal to" this one.
     *
     * @param obj the reference object with which to compare.
     * @return <code>true</code> if this object is the same as the obj
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof HasIdentifier) {
            Serializable id = getIdentifier();
            Serializable otherId = ((HasIdentifier) obj).getIdentifier();
            return null == id ? null == otherId : id.equals(otherId);
        }
        return false;
    }
}

@Entity
@Proxy(lazy = false)
@DiscriminatorValue("FI")
@NamedQueries({
@NamedQuery(name = "FindFolderItemByUserIdAndTargetObjectId", query = "select folderItem from DefaultFolderItem folderItem " +
        "left join folderItem.owner as user " +
        "where user.identifier like :userId and folderItem.targetObjectId like :targetObjectId"),
@NamedQuery(name = "DoesFolderItemExist", query = "select count(folderItem.identifier) from DefaultFolderItem folderItem " +
        "where folderItem.identifier like :itemId")})
public class DefaultFolderItem extends AbstractFolderItem {

    /**
     * The optional target StringControlledResource.
     */
    private StringControlledResource targetObject;

    /**
     * The optional target StringControlledResource's ID.
     */
    private String targetObjectId;

    /**
     * Default constructor to allow JPA to create and then use setters.
     */
    protected DefaultFolderItem() {
        super();
    }

    public DefaultFolderItem(User nuOwner, StringControlledResource nuTargetResource) {
        this(nuOwner, nuTargetResource, false);
    }

    public DefaultFolderItem(User nuOwner, StringControlledResource nuTargetResource, boolean shared) {
        super(nuOwner);

        try {
            setTargetObject(nuTargetResource);
            setShared(shared);
        } catch (PropertyVetoException e) {
            throw new IllegalArgumentException(e.getLocalizedMessage(), e.getCause());
        }
    }

    @Override
    @Column(name = "target_object_id", nullable = false)
    public String getTargetObjectId() {
        return targetObjectId;
    }

    @Override
    public void setTargetObjectId(String nuTargetObjectId) throws PropertyVetoException {
        if (null == nuTargetObjectId) {
            String unqualifiedClassName = Util.unqualifiedClassName(getClass().getName());
            String tmsg = Util.formatMsg(Messages.ERROR_NULL_PARAM, "nuTargetObjectId", unqualifiedClassName, Util.executingMethodName());
            throw new PropertyVetoException(tmsg, new PropertyChangeEvent(this, PROPERTY_TARGET_OBJECT_ID, targetObjectId, nuTargetObjectId));
        }

        targetObjectId = nuTargetObjectId;
    }

    @Transient
    public String getName() {
        StringControlledResource targetObj = getTargetObject();
        return null == targetObj ? "" : targetObj.getName();
    }

    /**
     * @see com.donaldson.dtools.folders.FolderItem#getTargetObject()
     */
    @Transient
    public StringControlledResource getTargetObject() {
        return targetObject;
    }

    public void setTargetObject(StringControlledResource nuTargetObject) throws PropertyVetoException {
        if (null == nuTargetObject) {
            String unqualifiedClassName = Util.unqualifiedClassName(getClass().getName());
            String tmsg = Util.formatMsg(Messages.ERROR_NULL_PARAM, "nuTargetObject", unqualifiedClassName, Util.executingMethodName());
            throw new PropertyVetoException(tmsg, new PropertyChangeEvent(this, PROPERTY_TARGET_OBJECT, targetObject, nuTargetObject));
        }
        StringControlledResource oldTarget = targetObject;
        getVetoSupport().fireVetoableChange(PROPERTY_TARGET_OBJECT, oldTarget, nuTargetObject);
        targetObject = nuTargetObject;
        setTargetObjectId(targetObject.getIdentifier());

        // Manually firing a PropertyChangeEvent as when the RunSetStat is changed oldTarget.equals(nuTargetObject) is true
        // which means PropertyChangeSupport will not fire the event.
        // getChangeSupport().firePropertyChange(PROPERTY_TARGET_OBJECT, oldTarget, nuTargetObject); // Will not work
        PropertyChangeEvent tevent = new PropertyChangeEvent(this, PROPERTY_TARGET_OBJECT, oldTarget, nuTargetObject);
        PropertyChangeListener[] tlisteners = getChangeSupport().getPropertyChangeListeners();
        for (int icnt = tlisteners.length - 1; icnt >= 0; icnt--) {
            PropertyChangeListener tlistener = tlisteners[icnt];
            tlistener.propertyChange(tevent);
        }
    }
}

@Entity
@Proxy(lazy = false)
@DiscriminatorValue("F")
@NamedQueries({
@NamedQuery(name = "FindPrivateRootFolderForUserId", query = "select folder from DefaultFolder folder " +
        "left join folder.parentFolder as pf " +
        "where folder.owner.identifier like :userId and folder.shared = false and " +
        "(pf is null or pf.name like :parentFolderName)"),
@NamedQuery(name = "FindPrivateRootFolderCountForUserId", query = "select count(folder.identifier) from DefaultFolder folder " +
        "left join folder.parentFolder as pf " +
        "where folder.owner.identifier like :userId and folder.shared = false and " +
        "(pf is null or pf.name like :parentFolderName)"),
@NamedQuery(name = "FindSharedRootFolder", query = "select folder from DefaultFolder folder " +
        "left join folder.parentFolder as pf " +
        "where folder.shared = true and (pf is null or pf.name like :parentFolderName)"),
@NamedQuery(name = "DoesFolderExist", query = "select count(folder.identifier) from DefaultFolder folder " +
        "where folder.identifier like :itemId")})
public class DefaultFolder extends AbstractFolderItem implements Folder {

    /**
     * Name of this Folder.
     */
    private String name;

    private SortedSet<FolderItem> folderItems = new TreeSet<FolderItem>();


    /**
     * Default constructor to allow JPA to create and then use setters.
     */
    protected DefaultFolder() {
        super();
    }

    /**
     * @param nuOwner
     * @param nuName
     */
    public DefaultFolder(User nuOwner, String nuName) {
        this(nuOwner, nuName, false);
    }

    /**
     * @param nuOwner
     * @param nuName
     * @param shared
     */
    public DefaultFolder(User nuOwner, String nuName, boolean shared) {
        super(nuOwner);

        try {
            setName(nuName);
            setShared(shared);
        } catch (PropertyVetoException e) {
            throw new IllegalArgumentException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Returns the name of this folder.
     *
     * @return the name of this folder.
     */
    @Column(name = "name", nullable = false)
    public String getName() {
        return name;
    }

    public void setName(String nuName) throws PropertyVetoException {
        if (StringUtils.isBlank(nuName)) {
            String unqualifiedClassName = Util.unqualifiedClassName(getClass().getName());
            String tmsg = Util.formatMsg(Messages.ERROR_NULL_EMPTY_PARAM, "nuName", unqualifiedClassName, Util.executingMethodName());
            throw new PropertyVetoException(tmsg, new PropertyChangeEvent(this, HasName.PROPERTY_NAME, name, nuName));
        }
        getVetoSupport().fireVetoableChange(PROPERTY_NAME, getName(), nuName);

        String oldName = getName();
        name = nuName;
        getChangeSupport().firePropertyChange(PROPERTY_NAME, oldName, getName());
    }

    /**
     * This is a getter that should only be used within the class, or by JPA.
     *
     * @return
     */
    @OneToMany(targetEntity = AbstractFolderItem.class, fetch = FetchType.EAGER, mappedBy = "parentFolder",
            cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
    @Sort(type = SortType.NATURAL)
    protected SortedSet<FolderItem> getFolderItemsInternal() {
        return folderItems;
    }

    /**
     * This is a setter, should only be used within the class, or by JPA.
     *
     * @param nuFolderItems
     * @see Folder#setFolderItems(java.util.Set
     */
    protected void setFolderItemsInternal(SortedSet<FolderItem> nuFolderItems) {
        folderItems = nuFolderItems;
    }

    public void add(LightComposite aChild) throws ValidationException, IllegalArgumentException {
        addInternal(aChild, LightCompositeEventType.ADD);
    }

    public void add(LightComposite aChild, LightCompositeEventType eventType) throws ValidationException, IllegalArgumentException {
        addInternal(aChild, eventType);
    }

    public void remove(LightComposite aChild) throws ValidationException, IllegalArgumentException {
        removeInternal(aChild, LightCompositeEventType.REMOVE);
    }

    public void remove(LightComposite aChild, LightCompositeEventType eventType) throws ValidationException, IllegalArgumentException {
        removeInternal(aChild, eventType);
    }

    /**
     * The internal representation of the {@link #add(com.donaldson.dtools.patterns.composite.light.LightComposite)} .
     *
     * @param aChild
     * @param eventType
     * @return
     * @throws ValidationException
     * @throws IllegalArgumentException
     */
    private boolean addInternal(LightComposite aChild, LightCompositeEventType eventType) throws ValidationException, IllegalArgumentException {
        // Is it a null child?
        if (null == aChild) {
            throw new IllegalArgumentException("Null aChild parameter.");
        }
        // Is the child already my ancestor?
        if (isAncestor(aChild)) {
            throw new ValidationException("aChild is already my ancestor.");
        }

        AbstractFolderItem childFolderItem = (AbstractFolderItem) aChild;

        // Will child cause a duplicate sibling name?
        String childFolderItemName = childFolderItem.getName();
        for (LightComposite currentChild : getChildren()) {
            String currentChildName = ((FolderItem) currentChild).getName();
            if (childFolderItemName.equalsIgnoreCase(currentChildName)) {
                throw new DuplicateSiblingException("A sibling already has the same name '" + currentChildName + "'.");
            }
        }

        // Remove existing relationship, if one exists
        Folder oldParentFolder = childFolderItem.getParentFolder();
        if (null != oldParentFolder) {
            oldParentFolder.removeFolderItem(childFolderItem);
        }

        // Create new relationship
        childFolderItem.setParentFolder(this);
        boolean result = getFolderItemsInternal().add(childFolderItem);
        switch (eventType) {
            case ADD:
            case ADD_VIA_MOVE:
            case ADD_VIA_REPLACE:
                fireCompositeEvent(this, childFolderItem, eventType, indexOf(childFolderItem));
                break;

            default:
                throw new IllegalArgumentException("Unexpected LightCompositeEventType: " + eventType);
        }
        return result;
    }

    /**
     * The internal representation of the {@link #remove(com.donaldson.dtools.patterns.composite.light.LightComposite)} .
     *
     * @param aChild
     * @param eventType
     * @return
     * @throws ValidationException
     * @throws IllegalArgumentException
     */
    private boolean removeInternal(LightComposite aChild, LightCompositeEventType eventType) throws ValidationException, IllegalArgumentException {
        AbstractFolderItem oldFolderItem = (AbstractFolderItem) aChild;
        int childIndex = indexOf(oldFolderItem);
        oldFolderItem.setParentFolder(null);
        boolean result = getFolderItemsInternal().remove(oldFolderItem);
        switch (eventType) {
            case REMOVE:
            case REMOVE_VIA_MOVE:
            case REMOVE_VIA_REPLACE:
                fireCompositeEvent(this, aChild, eventType, childIndex);
                break;

            default:
                throw new IllegalArgumentException("Unexpected LightCompositeEventType: " + eventType);
        }
        return result;
    }
}


Name and version of the database you are using:
Postgres-8.2.4

----------------

I have a composite file structure I am trying to persist via JPA. I have an AbstractFileItem (abstract), DefaultFileItem (concrete)
and a DefaultFolder (concrete). DefaultFileItem and DefaultFolder are subclasses of AbstractFileItem. I am using single table
inheritance. All the relationships are bidirectional. The ManyToOne side is a SortedSet, using natural as the comparator type.

I can successfully persist a lone DefaultFolder. I can also successfully add a number of DefaultFolders to it, and
its children (DefaultFolders), and persist the change. However, when I add more than one DefaultFileItem to a DefaultFolder, and
then merge the DefaultFolder into the PersistenceContext, all but one child DefaultFileItem shows up. I have verified that all
the children are actually persisting in the database, and they refer to the correct parent DefaultFolder.

On calls to find the recently updated folder I’ve printed out the current PersistenceContext*, and get the output below. I get the
PersistenceContext via
Code:
((SessionImpl) ((EntityManagerImpl) getCurrentEntityManager()).getSession()).getPersistenceContext()
. This
tells me that the JPA is not only persisting the correct information, but also retrieving the correct relationships.

Formatted for easier reading...
=================================================================================
PersistencContext before JPA call -
PersistenceContext[
entityKeys=[
EntityKey[User#783cd16d0a0c5f1b01ec890917c42476]],
collectionKeys=[]]

PersistencContext after JPA call -
PersistenceContext[
entityKeys=[
EntityKey[User#783cd16d0a0c5f1b01ec890917c42476],
EntityKey[DefaultFolder#7cda89e90a0c5f1b004583646048ffb6],
EntityKey[AbstractFolderItem#7cdf710c0a0c5f1b0125d61e782f9e51],
EntityKey[AbstractFolderItem#7cdf68610a0c5f1b0125d61e68af4010],
EntityKey[AbstractFolderItem#7cdf78210a0c5f1b0125d61e2bc13fd2]],
collectionKeys=[
CollectionKey[DefaultFolder.folderItemsInternal#7cda89e90a0c5f1b004583646048ffb6]]]
=================================================================================

Any ideas know why JPA is not returning the expected Set of children, even though it's loading them into the PersistenceContext?


Top
 Profile  
 
 Post subject: Solved
PostPosted: Thu Jun 12, 2008 10:26 am 
Newbie

Joined: Sat Nov 22, 2003 11:49 am
Posts: 11
Location: Minneapolis, Minnesota
OK, I see my error: In the
Code:
getName()
of the DefaultFolderItem class, I return an empty string when the targetObject is null.

HashSet is backed by a HashMap, and TreeSet is backed by a TreeMap. Unlike HashMap, TreeMap does not base its
Code:
put(E o)
on the hashcode, but on the compare values.

When a DefaultFolderItem is loaded, it has its targetObjectId, but not the actual targetObject. Hence the TreeMap was comparing the children's empty strings. Thus only one would show up.

User error.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.