The Conflict Resolution framework helps Liferay users identify and solve
conflicts in the Recycle Bin. The most common conflict for the Recycle Bin is
duplicate naming. For instance, say you create a file called myfile.txt
in a
folder called Documents
. Then you decide you should delete it, because you
already have that file on your file system, so you recycle it and upload the
myfile.txt
from your file system. You would get a naming conflict because,
although myfile.txt
has a Recycle Bin entry, it’s actually still in the
original folder but with its status changed and its visibility turned off.
This tutorial covers how to implement the Conflict Resolution framework so you can avoid Recycle Bin conflicts. (Note that you’re on your own when it comes to conflicts outside the Recycle Bin.)
Follow these steps and you’ll be resolving conflicts in no time!
- Rename Entities sent to the Recycle Bin
- Restore the Entity’s Original Name When Restoring From Recycle Bin
- Implement Conflict Resolution Trash Handler Methods
Step 1: Rename Entities Sent to the Recycle Bin
Entities are never actually created in the Recycle Bin. Instead, the entity is kept in its original location and a trash entry that points to it is created in the Recycle Bin. When viewing the entry in the Recycle Bin, the name appears the same as it does in the original entry. But the portal needs a way to distinguish between the recycled pointer and the real entity, which can be restored. This requires adding logic to your entity’s service and leveraging trash utilities to generate new names for Recycle Bin entries.
When an entry is sent to the Recycle Bin, it’s still in its original
location, but with a different status. Users may create an entity with a name,
move it to the Recycle Bin, and then create another entity with the same name.
Since both entities are in the same place, a naming conflict could occur. Some
applications only allow one entity to have a particular property value; for
example, names for documents, titles for songs in an album, friendly URLs for
pages, etc. On moving an entity to the Recycle Bin, you must rename properties
that could generate conflicts. For example Liferay’s Jukebox Portlet
uses a UnicodeProperties
instance to store a mapping of each song’s title:
UnicodeProperties typeSettingsProperties = new UnicodeProperties();
typeSettingsProperties.put("title", song.getName());
TrashEntry trashEntry = trashEntryLocalService.addTrashEntry(
userId, song.getGroupId(), Song.class.getName(), song.getSongId(),
song.getUuid(), null, oldStatus, null, typeSettingsProperties);
song.setName(TrashUtil.getTrashTitle(trashEntry.getEntryId()));
The mapping is stored with the trash entry.
TrashUtil.getTrashTitle(trashEntry.getEntryId())
is invoked from SongLocalServiceImpl
’s
moveSongToTrash
method to set the name of the original entity. In this case, a
song is renamed to a unique value that can be used to look up the trash entry
associated with the entity. Invoking
TrashUtil.getTrashTitle(trashEntry.getEntryId())
resets the name of the
original entity to a unique value–a slash followed by the trash entry’s ID.
Since the entity is now in the Recycle Bin, it’s hidden from view in its
original location; so users never see this mane. As you’ll see shortly, the
unique names generated by TrashUtil.getTrashTitle(...)
are used to get
the names of the original entities when restoring those entities.
Figure 1: The Recycle Bin allows you to manage trash entries, even if they share the same name.
Next, you’ll see how to restore a trashed entity’s original name in your restore process.
Step 2: Restore the Entity’s Original Name When Restoring From Recycle Bin
Since recycled entities are renamed, you need to retrieve the original name when
restoring them. The code snippet below, from the Jukebox portlet’s restoreSongFromTrash(long userId, long songId)
method in
SongLocalServiceImpl
,
demonstrates how to retrieve the song’s old name to restore it:
Song song = songPersistence.findByPrimaryKey(songId);
song.setName(TrashUtil.getOriginalTitle(song.getName()));
The original entity is retrieved by its ID. Then the
TrashUtil.getOriginalTitle
method is called to look up the entity’s
original name. Remember that the entity’s current name is based on the trash
entry’s ID. TrashUtil
’s getTrashTitle
method looks up the trash entry and
returns the title value (the song’s original name, in this case) previously
mapped in the entry’s type settings properties. The original song name is set
back to the song entity.
Lastly, whether an entity is in the Recycle Bin or not, you should always
render an entity with its original name. As an example of rendering a song’s
original title based on a locale, the Jukebox portlet’s SongAssetRenderer
uses the following method:
@Override
public String getTitle(Locale locale) {
if (!_song.isInTrash()) {
return _song.getName();
}
return TrashUtil.getOriginalTitle(_song.getName());
}
If the song isn’t in the Recycle Bin, the song’s current name is returned.
Otherwise, the method invokes TrashUtil.getOriginalTitle(_song.getName())
to
return the song’s original name.
Next, you need to finish satisfying the interface contract, and completely implement the Conflicts Resolution functionality.
Step 3: Implement Conflict Resolution Trash Handler Methods
Your app can now rename entries when they’re removed and reinstate their original names when they’re restored. What happens when the original entry is restored to its original location, and a new entry with the same name also is there? This causes a naming conflict that needs to be resolved by the user.
The Recycle Bin framework provides a UI for users to decide whether to overwrite the existing entry or to keep both entries by renaming the title of the entry they’re restoring. The figure below shows a conflict resolution pop-up that’s displayed in the Jukebox portlet on trying to restore a song for which an identically named song is already present.
Figure 2: The Recycle Bin enables you to handle conflicts by notifying the user with a pop-up message and options for solving the problem. Clearly, if you recycled Kashmir, someone may have tried to fix that both by re-uploading it and by restoring it.
Two methods need to be implemented in the trash handler to allow for the
necessary checks and updates. The first method must check for duplicate trash
entries. If an entry with the same name is detected in a directory, it must
throw an exception. To learn how to do this, you can reference the
checkDuplicateTrashEntry
method from
SongTrashHandler
.
Here’s the code from that method:
public void checkDuplicateTrashEntry(
TrashEntry trashEntry, long containerModelId, String newName)
throws PortalException {
Song song = SongLocalServiceUtil.getSong(trashEntry.getClassPK());
if (containerModelId == TrashEntryConstants.DEFAULT_CONTAINER_ID) {
containerModelId = song.getAlbumId();
}
String originalName = trashEntry.getTypeSettingsProperty("title");
if (Validator.isNotNull(newName)) {
originalName = newName;
}
Song duplicateSong = SongLocalServiceUtil.getSong(
song.getGroupId(), song.getArtistId(), containerModelId,
originalName);
if (duplicateSong != null) {
RestoreEntryException ree = new RestoreEntryException();
ree.setDuplicateEntryId(duplicateSong.getSongId());
ree.setOldName(duplicateSong.getName());
ree.setTrashEntryId(trashEntry.getEntryId());
throw ree;
}
}
Lastly, implement a method that updates the entry title name. For instance, the
Jukebox portlet updates a song title by calling this updateTitle
method:
public void updateTitle(long classPK, String name) throws PortalException {
Song song = SongLocalServiceUtil.getSong(classPK);
song.setName(name);
SongLocalServiceUtil.updateSong(song);
}
This method is also from the SongTrashHandler
class. It is called when the entry you’re restoring needs its title renamed
to resolve a conflict with a preexisting entry that has the same name. After
implementing these methods, your app’s users should always be able to resolve
naming conflicts involving trashed entries.
Fantastic! By leveraging the Conflicts Resolution framework in your app, you’re able to provide a smarter Recycle Bin that handles potential conflicts with ease.