/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.ade.sitemap.client.control;
import org.opencms.ade.detailpage.CmsDetailPageInfo;
import org.opencms.ade.sitemap.client.CmsSitemapTreeItem;
import org.opencms.ade.sitemap.client.CmsSitemapView;
import org.opencms.ade.sitemap.client.Messages;
import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry;
import org.opencms.ade.sitemap.shared.CmsDetailPageTable;
import org.opencms.ade.sitemap.shared.CmsSitemapChange;
import org.opencms.ade.sitemap.shared.CmsSitemapChange.ChangeType;
import org.opencms.ade.sitemap.shared.CmsSitemapClipboardData;
import org.opencms.ade.sitemap.shared.CmsSitemapData;
import org.opencms.ade.sitemap.shared.I_CmsSitemapController;
import org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService;
import org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapServiceAsync;
import org.opencms.file.CmsResource;
import org.opencms.gwt.client.CmsCoreProvider;
import org.opencms.gwt.client.property.CmsReloadMode;
import org.opencms.gwt.client.rpc.CmsRpcAction;
import org.opencms.gwt.client.rpc.CmsRpcPrefetcher;
import org.opencms.gwt.client.ui.CmsErrorDialog;
import org.opencms.gwt.client.ui.tree.CmsLazyTreeItem.LoadState;
import org.opencms.gwt.client.util.CmsDebugLog;
import org.opencms.gwt.client.util.CmsDomUtil;
import org.opencms.gwt.client.util.CmsDomUtil.Method;
import org.opencms.gwt.client.util.CmsDomUtil.Target;
import org.opencms.gwt.shared.CmsCoreData;
import org.opencms.gwt.shared.property.CmsClientProperty;
import org.opencms.gwt.shared.property.CmsPropertyModification;
import org.opencms.gwt.shared.rpc.I_CmsVfsServiceAsync;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Maps;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.FormElement;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.RootPanel;
/**
* Sitemap editor controller.<p>
*
* @since 8.0.0
*/
public class CmsSitemapController implements I_CmsSitemapController {
/** The name to use for new entries. */
public static final String NEW_ENTRY_NAME = Messages.get().key(Messages.GUI_NEW_ENTRY_NAME_0);
/** A map of *all* detail page info beans, indexed by page id. */
protected Map<CmsUUID, CmsDetailPageInfo> m_allDetailPageInfos = new HashMap<CmsUUID, CmsDetailPageInfo>();
/** The sitemap data. */
protected CmsSitemapData m_data;
/** The detail page table. */
protected CmsDetailPageTable m_detailPageTable;
/** The event bus. */
protected SimpleEventBus m_eventBus;
/** The entry data model. */
private Map<CmsUUID, CmsClientSitemapEntry> m_entriesById;
/** The sitemap entries by path. */
private Map<String, CmsClientSitemapEntry> m_entriesByPath;
/** The set of names of hidden properties. */
private Set<String> m_hiddenProperties;
/** The map of property maps by structure id. */
private Map<CmsUUID, Map<String, CmsClientProperty>> m_propertyMaps = Maps.newHashMap();
/** The list of property update handlers. */
private List<I_CmsPropertyUpdateHandler> m_propertyUpdateHandlers = new ArrayList<I_CmsPropertyUpdateHandler>();
/** The sitemap service instance. */
private I_CmsSitemapServiceAsync m_service;
/** The vfs service. */
private I_CmsVfsServiceAsync m_vfsService;
/**
* Constructor.<p>
*/
public CmsSitemapController() {
m_entriesById = new HashMap<CmsUUID, CmsClientSitemapEntry>();
m_entriesByPath = new HashMap<String, CmsClientSitemapEntry>();
try {
m_data = (CmsSitemapData)CmsRpcPrefetcher.getSerializedObject(getService(), CmsSitemapData.DICT_NAME);
} catch (Exception e) {
CmsErrorDialog dialog = new CmsErrorDialog("Error", "Deserialization failed.");
dialog.show();
}
m_hiddenProperties = new HashSet<String>();
if (m_data != null) {
m_detailPageTable = m_data.getDetailPageTable();
m_data.getRoot().initializeAll(this);
m_eventBus = new SimpleEventBus();
initDetailPageInfos();
}
}
/**
* Helper method for looking up a value in a map which may be null.<p>
*
* @param <A> the key type
* @param <B> the value type
* @param map the map (which may be null)
* @param key the map key
*
* @return the value of the map at the given key, or null if the map is null
*/
public static <A, B> B safeLookup(Map<A, B> map, A key) {
if (map == null) {
return null;
}
return map.get(key);
}
/**
* Adds a new change event handler.<p>
*
* @param handler the handler to add
*
* @return the handler registration
*/
public HandlerRegistration addChangeHandler(I_CmsSitemapChangeHandler handler) {
return m_eventBus.addHandlerToSource(CmsSitemapChangeEvent.getType(), this, handler);
}
/**
* Adds a new detail page information bean.<p>
*
* @param info the detail page information bean to add
*/
public void addDetailPageInfo(CmsDetailPageInfo info) {
m_detailPageTable.add(info);
m_allDetailPageInfos.put(info.getId(), info);
}
/**
* Adds a new load event handler.<p>
*
* @param handler the handler to add
*
* @return the handler registration
*/
public HandlerRegistration addLoadHandler(I_CmsSitemapLoadHandler handler) {
return m_eventBus.addHandlerToSource(CmsSitemapLoadEvent.getType(), this, handler);
}
/**
* Adds a handler for property changes caused by user edits.<p>
*
* @param handler a new handler for property updates caused by the user
*/
public void addPropertyUpdateHandler(I_CmsPropertyUpdateHandler handler) {
m_propertyUpdateHandlers.add(handler);
}
/**
* Adds the entry to the navigation.<p>
*
* @param entry the entry
*/
public void addToNavigation(CmsClientSitemapEntry entry) {
entry.setInNavigation(true);
CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify);
CmsPropertyModification mod = new CmsPropertyModification(
entry.getId(),
CmsClientProperty.PROPERTY_NAVTEXT,
entry.getTitle(),
true);
change.setPropertyChanges(Collections.singletonList(mod));
change.setPosition(entry.getPosition());
commitChange(change, null);
}
/**
* Makes the given sitemap entry the default detail page for its detail page type.<p>
*
* @param entry an entry representing a detail page
*/
public void bump(CmsClientSitemapEntry entry) {
CmsDetailPageTable table = getDetailPageTable().copy();
table.bump(entry.getId());
CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.bumpDetailPage);
change.setDetailPageInfos(table.toList());
commitChange(change, null);
}
/**
* Clears the deleted clip-board list and commits the change.<p>
*/
public void clearDeletedList() {
CmsSitemapClipboardData clipboardData = getData().getClipboardData().copy();
clipboardData.getDeletions().clear();
CmsSitemapChange change = new CmsSitemapChange(null, null, ChangeType.clipboardOnly);
change.setClipBoardData(clipboardData);
commitChange(change, null);
}
/**
* Clears the modified clip-board list and commits the change.<p>
*/
public void clearModifiedList() {
CmsSitemapClipboardData clipboardData = getData().getClipboardData().copy();
clipboardData.getModifications().clear();
CmsSitemapChange change = new CmsSitemapChange(null, null, ChangeType.clipboardOnly);
change.setClipBoardData(clipboardData);
commitChange(change, null);
}
/**
* Registers a new sitemap entry.<p>
*
* @param newEntry the new entry
* @param parentId the parent entry id
* @param structureId the structure id of the model page (if null, uses default model page)
*/
public void create(final CmsClientSitemapEntry newEntry, CmsUUID parentId, CmsUUID structureId) {
if (structureId == null) {
structureId = m_data.getDefaultNewElementInfo().getCopyResourceId();
}
create(newEntry, parentId, m_data.getDefaultNewElementInfo().getId(), structureId, null);
}
/**
* Registers a new sitemap entry.<p>
*
* @param newEntry the new entry
* @param parentId the parent entry id
* @param resourceTypeId the resource type id
* @param copyResourceId the copy resource id
* @param parameter an additional parameter which may contain more information needed to create the new resource
*/
public void create(
CmsClientSitemapEntry newEntry,
CmsUUID parentId,
int resourceTypeId,
CmsUUID copyResourceId,
String parameter) {
assert (getEntry(newEntry.getSitePath()) == null);
CmsSitemapChange change = new CmsSitemapChange(null, newEntry.getSitePath(), ChangeType.create);
change.setDefaultFileId(newEntry.getDefaultFileId());
change.setParentId(parentId);
change.setName(newEntry.getName());
change.setPosition(newEntry.getPosition());
change.setOwnInternalProperties(newEntry.getOwnProperties());
change.setDefaultFileInternalProperties(newEntry.getDefaultFileProperties());
change.setTitle(newEntry.getTitle());
change.setCreateParameter(parameter);
change.setNewResourceTypeId(resourceTypeId);
change.setNewCopyResourceId(copyResourceId);
if (isDetailPage(newEntry)) {
CmsDetailPageTable table = getDetailPageTable().copy();
if (!table.contains(newEntry.getId())) {
CmsDetailPageInfo info = new CmsDetailPageInfo(
newEntry.getId(),
newEntry.getSitePath(),
newEntry.getDetailpageTypeName());
table.add(info);
}
change.setDetailPageInfos(table.toList());
}
CmsSitemapClipboardData data = getData().getClipboardData().copy();
data.addModified(newEntry);
change.setClipBoardData(data);
commitChange(change, null);
}
/**
* Creates a new sub-entry of an existing sitemap entry.<p>
*
* @param parent the entry to which a new sub-entry should be added
*/
public void createSubEntry(final CmsClientSitemapEntry parent) {
createSubEntry(parent, null);
}
/**
* Creates a new sub-entry of an existing sitemap entry.<p>
*
* @param parent the entry to which a new sub-entry should be added
* @param structureId the structure id of the model page (if null, uses default model page)
*/
public void createSubEntry(final CmsClientSitemapEntry parent, final CmsUUID structureId) {
final CmsClientSitemapEntry newEntry = new CmsClientSitemapEntry();
CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(parent.getId());
AsyncCallback<CmsClientSitemapEntry> callback = new AsyncCallback<CmsClientSitemapEntry>() {
public void onFailure(Throwable caught) {
// nothing to do
}
public void onSuccess(CmsClientSitemapEntry result) {
String urlName = ensureUniqueName(parent, NEW_ENTRY_NAME);
//newEntry.setTitle(urlName);
newEntry.setName(urlName);
String sitePath = parent.getSitePath() + urlName + "/";
newEntry.setSitePath(sitePath);
newEntry.setVfsPath(null);
newEntry.setPosition(0);
newEntry.setNew(true);
newEntry.setInNavigation(true);
newEntry.setResourceTypeName("folder");
newEntry.getOwnProperties().put(
CmsClientProperty.PROPERTY_TITLE,
new CmsClientProperty(CmsClientProperty.PROPERTY_TITLE, NEW_ENTRY_NAME, NEW_ENTRY_NAME));
create(newEntry, parent.getId(), structureId);
}
};
if (item.getLoadState().equals(LoadState.UNLOADED)) {
getChildren(parent.getId(), true, callback);
} else {
callback.onSuccess(parent);
}
}
/**
* Creates a sub-sitemap from the subtree of the current sitemap starting at the given entry.<p>
*
* @param entryId the id of the entry
*/
public void createSubSitemap(final CmsUUID entryId) {
CmsRpcAction<CmsSitemapChange> subSitemapAction = new CmsRpcAction<CmsSitemapChange>() {
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
*/
@Override
public void execute() {
start(0, true);
getService().createSubSitemap(entryId, this);
}
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
*/
@Override
protected void onResponse(CmsSitemapChange result) {
stop(false);
applyChange(result);
}
};
subSitemapAction.execute();
}
/**
* Deletes the given entry and all its descendants.<p>
*
* @param sitePath the site path of the entry to delete
*/
public void delete(String sitePath) {
CmsClientSitemapEntry entry = getEntry(sitePath);
CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(entry.getSitePath()));
CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.delete);
change.setParentId(parent.getId());
change.setDefaultFileId(entry.getDefaultFileId());
CmsSitemapClipboardData data = CmsSitemapView.getInstance().getController().getData().getClipboardData().copy();
if (!entry.isNew()) {
data.addDeleted(entry);
removeDeletedFromModified(entry, data);
}
change.setClipBoardData(data);
CmsDetailPageTable detailPageTable = CmsSitemapView.getInstance().getController().getData().getDetailPageTable();
CmsUUID id = entry.getId();
if (detailPageTable.contains(id)) {
CmsDetailPageTable copyTable = detailPageTable.copy();
copyTable.remove(id);
change.setDetailPageInfos(copyTable.toList());
}
commitChange(change, null);
}
/**
* Edits the given sitemap entry.<p>
*
* @param entry the sitemap entry to update
* @param propertyChanges the property changes
* @param reloadStatus a value indicating which entries need to be reloaded after the change
*/
public void edit(
CmsClientSitemapEntry entry,
List<CmsPropertyModification> propertyChanges,
final CmsReloadMode reloadStatus) {
CmsSitemapChange change = getChangeForEdit(entry, propertyChanges);
final String updateTarget = ((reloadStatus == CmsReloadMode.reloadParent))
? getParentEntry(entry).getSitePath()
: entry.getSitePath();
Command callback = new Command() {
public void execute() {
if ((reloadStatus == CmsReloadMode.reloadParent) || (reloadStatus == CmsReloadMode.reloadEntry)) {
updateEntry(updateTarget);
}
}
};
if (change != null) {
commitChange(change, callback);
}
}
/**
* Edits an entry and changes its URL name.<p>
*
* @param entry the entry which is being edited
* @param newUrlName the new URL name of the entry
* @param propertyChanges the property changes
* @param keepNewStatus <code>true</code> if the entry should keep it's new status
* @param reloadStatus a value indicating which entries need to be reloaded after the change
*/
public void editAndChangeName(
final CmsClientSitemapEntry entry,
String newUrlName,
List<CmsPropertyModification> propertyChanges,
final boolean keepNewStatus,
final CmsReloadMode reloadStatus) {
CmsSitemapChange change = getChangeForEdit(entry, propertyChanges);
change.setName(newUrlName);
final CmsUUID entryId = entry.getId();
final boolean newStatus = keepNewStatus && entry.isNew();
final CmsUUID updateTarget = ((reloadStatus == CmsReloadMode.reloadParent))
? getParentEntry(entry).getId()
: entryId;
Command callback = new Command() {
public void execute() {
if ((reloadStatus == CmsReloadMode.reloadParent) || (reloadStatus == CmsReloadMode.reloadEntry)) {
updateEntry(updateTarget);
}
getEntryById(entryId).setNew(newStatus);
}
};
commitChange(change, callback);
}
/**
* Ensure the uniqueness of a given URL-name within the children of the given parent site-map entry.<p>
*
* @param parent the parent entry
* @param newName the proposed name
*
* @return the unique name
*/
public String ensureUniqueName(CmsClientSitemapEntry parent, String newName) {
return ensureUniqueName(parent.getSitePath(), newName);
}
/**
* Ensure the uniqueness of a given URL-name within the children of the given parent folder.<p>
*
* @param parentFolder the parent folder
* @param newName the proposed name
*
* @return the unique name
*/
public String ensureUniqueName(final String parentFolder, String newName) {
// using lower case folder names
final String lowerCaseName = newName.toLowerCase();
CmsRpcAction<String> action = new CmsRpcAction<String>() {
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
*/
@Override
public void execute() {
start(0, false);
CmsCoreProvider.getService().getUniqueFileName(parentFolder, lowerCaseName, this);
}
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
*/
@Override
protected void onResponse(String result) {
stop(false);
}
};
return action.executeSync();
}
/**
* Applies the given property modification.<p>
*
* @param propMod the property modification to apply
*/
public void executePropertyModification(CmsPropertyModification propMod) {
CmsClientSitemapEntry entry = getEntryById(propMod.getId());
if (entry != null) {
Map<String, CmsClientProperty> props = getPropertiesForId(propMod.getId());
if (props != null) {
propMod.updatePropertyInMap(props);
entry.setOwnProperties(props);
}
}
}
/**
* Retrieves the child entries of the given node from the server.<p>
*
* @param entryId the entry id
* @param setOpen if the entry should be opened
* @param callback the callback to execute after the children have been loaded
*/
public void getChildren(
final CmsUUID entryId,
final boolean setOpen,
final AsyncCallback<CmsClientSitemapEntry> callback) {
CmsRpcAction<CmsClientSitemapEntry> getChildrenAction = new CmsRpcAction<CmsClientSitemapEntry>() {
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
*/
@Override
public void execute() {
// Make the call to the sitemap service
start(500, false);
// loading grand children as well
getService().getChildren(getEntryPoint(), entryId, 2, this);
}
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
*/
@Override
public void onResponse(CmsClientSitemapEntry result) {
CmsClientSitemapEntry target = getEntryById(entryId);
if (target == null) {
// this might happen after an automated deletion
stop(false);
return;
}
target.setSubEntries(result.getSubEntries(), CmsSitemapController.this);
CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(target.getId());
target.update(result);
target.initializeAll(CmsSitemapController.this);
item.updateEntry(target);
m_eventBus.fireEventFromSource(new CmsSitemapLoadEvent(target, setOpen), CmsSitemapController.this);
stop(false);
if (callback != null) {
callback.onSuccess(result);
}
}
};
getChildrenAction.execute();
}
/**
* Returns the sitemap data.<p>
*
* @return the sitemap data
*/
public CmsSitemapData getData() {
return m_data;
}
/**
* Returns the detail page info for a given entry id.<p>
*
* @param id a sitemap entry id
*
* @return the detail page info for that id
*/
public CmsDetailPageInfo getDetailPageInfo(CmsUUID id) {
return m_allDetailPageInfos.get(id);
}
/**
* Returns the detail page table.<p>
*
* @return the detail page table
*/
public CmsDetailPageTable getDetailPageTable() {
return m_detailPageTable;
}
/**
* Gets the effective value of a property value for a sitemap entry.<p>
*
* @param entry the sitemap entry
* @param name the name of the property
*
* @return the effective value
*/
public String getEffectiveProperty(CmsClientSitemapEntry entry, String name) {
CmsClientProperty prop = getEffectivePropertyObject(entry, name);
if (prop == null) {
return null;
}
return prop.getEffectiveValue();
}
/**
* Gets the value of a property which is effective at a given sitemap entry.<p>
*
* @param entry the sitemap entry
* @param name the name of the property
* @return the effective property value
*/
public CmsClientProperty getEffectivePropertyObject(CmsClientSitemapEntry entry, String name) {
Map<String, CmsClientProperty> dfProps = entry.getDefaultFileProperties();
CmsClientProperty result = safeLookup(dfProps, name);
if (!CmsClientProperty.isPropertyEmpty(result)) {
return result.withOrigin(entry.getSitePath());
}
result = safeLookup(entry.getOwnProperties(), name);
if (!CmsClientProperty.isPropertyEmpty(result)) {
return result.withOrigin(entry.getSitePath());
}
return getInheritedPropertyObject(entry, name);
}
/**
* Returns all entries with an id from a given list.<p>
*
* @param ids a list of sitemap entry ids
*
* @return all entries whose id is contained in the id list
*/
public Map<CmsUUID, CmsClientSitemapEntry> getEntriesById(Collection<CmsUUID> ids) {
// TODO: use some map of id -> entry instead
List<CmsClientSitemapEntry> entriesToProcess = new ArrayList<CmsClientSitemapEntry>();
Map<CmsUUID, CmsClientSitemapEntry> result = new HashMap<CmsUUID, CmsClientSitemapEntry>();
entriesToProcess.add(m_data.getRoot());
while (!entriesToProcess.isEmpty()) {
CmsClientSitemapEntry entry = entriesToProcess.remove(entriesToProcess.size() - 1);
if (ids.contains(entry.getId())) {
result.put(entry.getId(), entry);
if (result.size() == ids.size()) {
return result;
}
}
entriesToProcess.addAll(entry.getSubEntries());
}
return result;
}
/**
* Returns the tree entry with the given path.<p>
*
* @param entryPath the path to look for
*
* @return the tree entry with the given path, or <code>null</code> if not found
*/
public CmsClientSitemapEntry getEntry(String entryPath) {
return m_entriesByPath.get(entryPath);
}
/**
* Finds an entry by id.<p>
*
* @param id the id of the entry to find
*
* @return the found entry, or null if the entry wasn't found
*/
public CmsClientSitemapEntry getEntryById(CmsUUID id) {
return m_entriesById.get(id);
}
/**
* Gets the value for a property which a sitemap entry would inherit if it didn't have its own properties.<p>
*
* @param entry the sitemap entry
* @param name the property name
* @return the inherited property value
*/
public String getInheritedProperty(CmsClientSitemapEntry entry, String name) {
CmsClientProperty prop = getInheritedPropertyObject(entry, name);
if (prop == null) {
return null;
}
return prop.getEffectiveValue();
}
/**
* Gets the property object which would be inherited by a sitemap entry.<p>
*
* @param entry the sitemap entry
* @param name the name of the property
* @return the property object which would be inherited
*/
public CmsClientProperty getInheritedPropertyObject(CmsClientSitemapEntry entry, String name) {
CmsClientSitemapEntry currentEntry = entry;
while (currentEntry != null) {
currentEntry = getParentEntry(currentEntry);
if (currentEntry != null) {
CmsClientProperty folderProp = currentEntry.getOwnProperties().get(name);
if (!CmsClientProperty.isPropertyEmpty(folderProp)) {
return folderProp.withOrigin(currentEntry.getSitePath());
}
}
}
CmsClientProperty parentProp = getParentProperties().get(name);
if (!CmsClientProperty.isPropertyEmpty(parentProp)) {
String origin = parentProp.getOrigin();
String siteRoot = CmsCoreProvider.get().getSiteRoot();
if (origin.startsWith(siteRoot)) {
origin = origin.substring(siteRoot.length());
}
return parentProp.withOrigin(origin);
}
return null;
}
/**
* Returns a list of all descendant sitemap entries of a given path which have already been loaded on the client.<p>
*
* @param path the path for which the descendants should be collected
*
* @return the list of descendant sitemap entries
*/
public List<CmsClientSitemapEntry> getLoadedDescendants(String path) {
LinkedList<CmsClientSitemapEntry> remainingEntries = new LinkedList<CmsClientSitemapEntry>();
List<CmsClientSitemapEntry> result = new ArrayList<CmsClientSitemapEntry>();
CmsClientSitemapEntry entry = getEntry(path);
remainingEntries.add(entry);
while (remainingEntries.size() > 0) {
CmsClientSitemapEntry currentEntry = remainingEntries.removeFirst();
result.add(currentEntry);
for (CmsClientSitemapEntry subEntry : currentEntry.getSubEntries()) {
remainingEntries.add(subEntry);
}
}
return result;
}
/**
* Returns the no edit reason or <code>null</code> if editing is allowed.<p>
*
* @param entry the entry to get the no edit reason for
*
* @return the no edit reason
*/
public String getNoEditReason(CmsClientSitemapEntry entry) {
String reason = null;
if ((entry.getLock() != null) && (entry.getLock().getLockOwner() != null) && !entry.getLock().isOwnedByUser()) {
reason = Messages.get().key(Messages.GUI_DISABLED_LOCKED_BY_1, entry.getLock().getLockOwner());
}
if (entry.hasBlockingLockedChildren()) {
reason = Messages.get().key(Messages.GUI_DISABLED_BLOCKING_LOCKED_CHILDREN_0);
}
return reason;
}
/**
* Returns the parent entry of a sitemap entry, or null if it is the root entry.<p>
*
* @param entry a sitemap entry
*
* @return the parent entry or null
*/
public CmsClientSitemapEntry getParentEntry(CmsClientSitemapEntry entry) {
String path = entry.getSitePath();
String parentPath = CmsResource.getParentFolder(path);
if (parentPath == null) {
return null;
}
return getEntry(parentPath);
}
/**
* Gets the properties for a given structure id.<p>
*
* @param id the structure id of a sitemap entry
*
* @return the properties for that structure id
*/
public Map<String, CmsClientProperty> getPropertiesForId(CmsUUID id) {
return m_propertyMaps.get(id);
}
/**
* Returns the sitemap service instance.<p>
*
* @return the sitemap service instance
*/
public I_CmsSitemapServiceAsync getService() {
if (m_service == null) {
m_service = GWT.create(I_CmsSitemapService.class);
String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.sitemap.CmsVfsSitemapService.gwt");
((ServiceDefTarget)m_service).setServiceEntryPoint(serviceUrl);
}
return m_service;
}
/**
* Leaves the current sitemap to open the parent sitemap.<p>
*/
public void gotoParentSitemap() {
openSiteMap(getData().getParentSitemap());
}
/**
* Checks whether this entry belongs to a detail page.<p>
*
* @param entry the entry to check
*
* @return true if this entry belongs to a detail page
*/
public boolean isDetailPage(CmsClientSitemapEntry entry) {
return (entry.getDetailpageTypeName() != null) || isDetailPage(entry.getId());
}
/**
* Returns true if the id is the id of a detail page.<p>
*
* @param id the sitemap entry id
* @return true if the id is the id of a detail page entry
*/
public boolean isDetailPage(CmsUUID id) {
return m_allDetailPageInfos.containsKey(id);
}
/**
* Checks if the current sitemap is editable.<p>
*
* @return <code>true</code> if the current sitemap is editable
*/
public boolean isEditable() {
return CmsStringUtil.isEmptyOrWhitespaceOnly(m_data.getNoEditReason());
}
/**
* Checks whether a string is the name of a hidden property.<p>
*
* A hidden property is a property which should not appear in the property editor
* because it requires special treatment.<p>
*
* @param propertyName the property name which should be checked
*
* @return true if the argument is the name of a hidden property
*/
public boolean isHiddenProperty(String propertyName) {
if (propertyName.equals("secure") && !m_data.isSecure()) {
// "secure" property should not be editable in a site for which no secure server is configured
return true;
}
return m_hiddenProperties.contains(propertyName);
}
/**
* Checks if the given site path is the sitemap root.<p>
*
* @param sitePath the site path to check
*
* @return <code>true</code> if the given site path is the sitemap root
*/
public boolean isRoot(String sitePath) {
return m_data.getRoot().getSitePath().equals(sitePath);
}
/**
* Ask to save the page before leaving, if necessary.<p>
*
* @param target the leaving target
*/
public void leaveEditor(String target) {
Window.Location.assign(CmsCoreProvider.get().link(target));
}
/**
* Merges a subsitemap at the given id back into this sitemap.<p>
*
* @param entryId the id of the sub sitemap entry
*/
public void mergeSubSitemap(final CmsUUID entryId) {
CmsRpcAction<CmsSitemapChange> mergeAction = new CmsRpcAction<CmsSitemapChange>() {
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
*/
@Override
public void execute() {
start(0, true);
getService().mergeSubSitemap(getEntryPoint(), entryId, this);
}
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
*/
@Override
protected void onResponse(CmsSitemapChange result) {
stop(false);
applyChange(result);
}
};
mergeAction.execute();
}
/**
* Moves the given sitemap entry with all its descendants to the new position.<p>
*
* @param entry the sitemap entry to move
* @param toPath the destination path
* @param position the new position between its siblings
*/
public void move(CmsClientSitemapEntry entry, String toPath, int position) {
// check for valid data
if (!isValidEntryAndPath(entry, toPath)) {
// invalid data, do nothing
CmsDebugLog.getInstance().printLine("invalid data, doing nothing");
return;
}
// check for relevance
if (isChangedPosition(entry, toPath, position)) {
// only register real changes
CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify);
change.setDefaultFileId(entry.getDefaultFileId());
if (!toPath.equals(entry.getSitePath())) {
change.setParentId(getEntry(CmsResource.getParentFolder(toPath)).getId());
change.setName(CmsResource.getName(toPath));
}
if (CmsSitemapView.getInstance().isNavigationMode()) {
change.setPosition(position);
}
change.setLeafType(entry.isLeafType());
CmsSitemapClipboardData data = getData().getClipboardData().copy();
data.addModified(entry);
change.setClipBoardData(data);
commitChange(change, null);
}
}
/**
* Opens the site-map specified.<p>
*
* @param sitePath the site path to the site-map folder
*/
public void openSiteMap(String sitePath) {
Map<String, String> parameter = new HashMap<String, String>();
parameter.put(CmsCoreData.PARAM_PATH, sitePath);
parameter.put(CmsCoreData.PARAM_RETURNCODE, getData().getReturnCode());
FormElement form = CmsDomUtil.generateHiddenForm(
CmsCoreProvider.get().link(CmsCoreProvider.get().getUri()),
Method.post,
Target.TOP,
parameter);
RootPanel.getBodyElement().appendChild(form);
form.submit();
}
/**
* Recomputes properties for all sitemap entries.<p>
*/
public void recomputeProperties() {
CmsClientSitemapEntry root = getData().getRoot();
recomputeProperties(root);
}
/**
* @see org.opencms.ade.sitemap.shared.I_CmsSitemapController#registerEntry(org.opencms.ade.sitemap.shared.CmsClientSitemapEntry)
*/
public void registerEntry(CmsClientSitemapEntry entry) {
if (m_entriesById.containsKey(entry.getId())) {
CmsClientSitemapEntry oldEntry = m_entriesById.get(entry.getId());
oldEntry.update(entry);
if (oldEntry != m_entriesByPath.get(oldEntry.getSitePath())) {
m_entriesByPath.put(oldEntry.getSitePath(), oldEntry);
}
} else {
m_entriesById.put(entry.getId(), entry);
m_entriesByPath.put(entry.getSitePath(), entry);
}
}
/**
* @see org.opencms.ade.sitemap.shared.I_CmsSitemapController#registerPathChange(org.opencms.ade.sitemap.shared.CmsClientSitemapEntry, java.lang.String)
*/
public void registerPathChange(CmsClientSitemapEntry entry, String oldPath) {
m_entriesById.put(entry.getId(), entry);
m_entriesByPath.remove(oldPath);
m_entriesByPath.put(entry.getSitePath(), entry);
}
/**
* Removes the entry with the given site-path from navigation.<p>
*
* @param entryId the entry id
*/
public void removeFromNavigation(CmsUUID entryId) {
CmsClientSitemapEntry entry = getEntryById(entryId);
CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(entry.getSitePath()));
CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.remove);
change.setParentId(parent.getId());
change.setDefaultFileId(entry.getDefaultFileId());
CmsSitemapClipboardData data = CmsSitemapView.getInstance().getController().getData().getClipboardData().copy();
data.addModified(entry);
change.setClipBoardData(data);
//TODO: handle detail page delete
commitChange(change, null);
}
/**
* @see org.opencms.ade.sitemap.shared.I_CmsSitemapController#replaceProperties(org.opencms.util.CmsUUID, java.util.Map)
*/
public Map<String, CmsClientProperty> replaceProperties(CmsUUID id, Map<String, CmsClientProperty> properties) {
if ((id == null) || (properties == null)) {
return null;
}
Map<String, CmsClientProperty> props = m_propertyMaps.get(id);
if (props == null) {
props = properties;
m_propertyMaps.put(id, props);
} else {
props.clear();
props.putAll(properties);
}
return props;
}
/**
* Undeletes the resource with the given structure id.<p>
*
* @param entryId the entry id
* @param sitePath the site-path
*/
public void undelete(CmsUUID entryId, String sitePath) {
CmsSitemapChange change = new CmsSitemapChange(entryId, sitePath, ChangeType.undelete);
CmsSitemapClipboardData data = CmsSitemapView.getInstance().getController().getData().getClipboardData().copy();
data.getDeletions().remove(entryId);
change.setClipBoardData(data);
commitChange(change, null);
}
/**
* Updates the given entry.<p>
*
* @param entryId the entry id
*/
public void updateEntry(CmsUUID entryId) {
getChildren(entryId, CmsSitemapTreeItem.getItemById(entryId).isOpen(), null);
}
/**
* Updates the given entry.<p>
*
* @param sitePath the entry sitepath
*/
public void updateEntry(String sitePath) {
CmsClientSitemapEntry entry = getEntry(sitePath);
getChildren(entry.getId(), CmsSitemapTreeItem.getItemById(entry.getId()).isOpen(), null);
}
/**
* Updates the given entry only, not evaluating any child changes.<p>
*
* @param entryId the entry id
*/
public void updateSingleEntry(final CmsUUID entryId) {
CmsRpcAction<CmsClientSitemapEntry> getChildrenAction = new CmsRpcAction<CmsClientSitemapEntry>() {
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
*/
@Override
public void execute() {
getService().getChildren(getEntryPoint(), entryId, 0, this);
}
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
*/
@Override
public void onResponse(CmsClientSitemapEntry result) {
CmsClientSitemapEntry target = getEntryById(entryId);
if (target == null) {
// this might happen after an automated deletion
stop(false);
return;
}
target.update(result);
CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(target.getId());
item.updateEntry(target);
}
};
getChildrenAction.execute();
}
/**
* Fires a sitemap change event.<p>
*
* @param change the change event to fire
*/
protected void applyChange(CmsSitemapChange change) {
// update the clip board data
if (change.getClipBoardData() != null) {
getData().getClipboardData().setDeletions(change.getClipBoardData().getDeletions());
getData().getClipboardData().setModifications(change.getClipBoardData().getModifications());
}
switch (change.getChangeType()) {
case bumpDetailPage:
CmsDetailPageTable detailPageTable = getData().getDetailPageTable();
if (detailPageTable.contains(change.getEntryId())) {
detailPageTable.bump(change.getEntryId());
}
break;
case clipboardOnly:
// nothing to do
break;
case remove:
CmsClientSitemapEntry entry = getEntryById(change.getEntryId());
entry.setInNavigation(false);
CmsPropertyModification propMod = new CmsPropertyModification(
entry.getId(),
CmsClientProperty.PROPERTY_NAVTEXT,
null,
true);
executePropertyModification(propMod);
propMod = new CmsPropertyModification(entry.getId(), CmsClientProperty.PROPERTY_NAVTEXT, null, true);
executePropertyModification(propMod);
entry.normalizeProperties();
break;
case undelete:
case create:
CmsClientSitemapEntry newEntry = change.getUpdatedEntry();
getEntryById(change.getParentId()).insertSubEntry(newEntry, change.getPosition(), this);
newEntry.initializeAll(this);
if (isDetailPage(newEntry)) {
CmsDetailPageInfo info = getDetailPageInfo(newEntry.getId());
if (info == null) {
info = new CmsDetailPageInfo(
newEntry.getId(),
newEntry.getSitePath(),
newEntry.getDetailpageTypeName());
}
addDetailPageInfo(info);
}
break;
case delete:
removeEntry(change.getEntryId(), change.getParentId());
break;
case modify:
if (change.hasNewParent() || change.hasChangedPosition()) {
CmsClientSitemapEntry moved = getEntryById(change.getEntryId());
String oldSitepath = moved.getSitePath();
CmsClientSitemapEntry sourceParent = getEntry(CmsResource.getParentFolder(oldSitepath));
sourceParent.removeSubEntry(moved.getId());
CmsClientSitemapEntry destParent = change.hasNewParent()
? getEntryById(change.getParentId())
: sourceParent;
if (change.getPosition() < destParent.getSubEntries().size()) {
destParent.insertSubEntry(moved, change.getPosition(), this);
} else {
// inserting as last entry of the parent list
destParent.addSubEntry(moved, this);
}
if (change.hasNewParent()) {
cleanupOldPaths(oldSitepath, destParent.getSitePath());
}
}
if (change.hasChangedName()) {
CmsClientSitemapEntry changed = getEntryById(change.getEntryId());
String oldSitepath = changed.getSitePath();
CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(oldSitepath));
String newSitepath = CmsStringUtil.joinPaths(parent.getSitePath(), change.getName());
changed.updateSitePath(newSitepath, this);
cleanupOldPaths(oldSitepath, newSitepath);
}
if (change.hasChangedProperties()) {
for (CmsPropertyModification modification : change.getPropertyChanges()) {
executePropertyModification(modification);
}
getEntryById(change.getEntryId()).normalizeProperties();
}
if (change.getUpdatedEntry() != null) {
CmsClientSitemapEntry oldEntry = getEntryById(change.getEntryId());
CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(oldEntry.getSitePath()));
removeEntry(change.getEntryId(), parent.getId());
parent.insertSubEntry(change.getUpdatedEntry(), oldEntry.getPosition(), this);
change.getUpdatedEntry().initializeAll(this);
}
break;
default:
}
if (change.getChangeType() != ChangeType.delete) {
recomputeProperties();
}
m_eventBus.fireEventFromSource(new CmsSitemapChangeEvent(change), this);
}
/**
* Cleans up wrong path references.<p>
*
* @param oldSitepath the old sitepath
* @param newSitepath the new sitepath
*/
private void cleanupOldPaths(String oldSitepath, String newSitepath) {
// use a separate list to avoid concurrent changes
List<CmsClientSitemapEntry> entries = new ArrayList<CmsClientSitemapEntry>(m_entriesById.values());
for (CmsClientSitemapEntry entry : entries) {
if (entry.getSitePath().startsWith(oldSitepath)) {
String currentPath = entry.getSitePath();
String updatedSitePath = CmsStringUtil.joinPaths(
newSitepath,
currentPath.substring(oldSitepath.length()));
entry.updateSitePath(updatedSitePath, this);
}
}
}
/**
* Adds a change to the queue.<p>
*
* @param change the change to commit
* @param callback the callback to execute after the change has been applied
*/
protected void commitChange(final CmsSitemapChange change, final Command callback) {
if (change != null) {
// save the sitemap
CmsRpcAction<CmsSitemapChange> saveAction = new CmsRpcAction<CmsSitemapChange>() {
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
*/
@Override
public void execute() {
setLoadingMessage(Messages.get().key(Messages.GUI_SAVING_0));
start(0, true);
getService().saveSync(getEntryPoint(), change, this);
}
/**
* @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
*/
@Override
public void onResponse(CmsSitemapChange result) {
stop(true);
applyChange(result);
if (callback != null) {
callback.execute();
}
}
};
saveAction.execute();
}
}
/**
* Creates a change object for an edit operation.<p>
*
* @param entry the edited sitemap entry
* @param propertyChanges the list of property changes
*
* @return the change object
*/
protected CmsSitemapChange getChangeForEdit(
CmsClientSitemapEntry entry,
List<CmsPropertyModification> propertyChanges) {
CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify);
change.setDefaultFileId(entry.getDefaultFileId());
change.setLeafType(entry.isLeafType());
List<CmsPropertyModification> propertyChangeData = new ArrayList<CmsPropertyModification>();
for (CmsPropertyModification propChange : propertyChanges) {
propertyChangeData.add(propChange);
}
change.setPropertyChanges(propertyChangeData);
CmsSitemapClipboardData data = getData().getClipboardData().copy();
data.addModified(entry);
change.setClipBoardData(data);
return change;
}
/**
* Returns the URI of the current sitemap.<p>
*
* @return the URI of the current sitemap
*/
protected String getEntryPoint() {
return m_data.getRoot().getSitePath();
}
/**
* Helper method for getting the full path of a sitemap entry whose URL name is being edited.<p>
*
* @param entry the sitemap entry
* @param newUrlName the new url name of the sitemap entry
*
* @return the new full site path of the sitemap entry
*/
protected String getPath(CmsClientSitemapEntry entry, String newUrlName) {
if (newUrlName.equals("")) {
return entry.getSitePath();
}
return CmsResource.getParentFolder(entry.getSitePath()) + newUrlName + "/";
}
/**
* Returns the sitemap service instance.<p>
*
* @return the sitemap service instance
*/
protected I_CmsVfsServiceAsync getVfsService() {
if (m_vfsService == null) {
m_vfsService = CmsCoreProvider.getVfsService();
}
return m_vfsService;
}
/**
* Initializes the detail page information.<p>
*/
protected void initDetailPageInfos() {
for (CmsUUID id : m_data.getDetailPageTable().getAllIds()) {
CmsDetailPageInfo info = m_data.getDetailPageTable().get(id);
m_allDetailPageInfos.put(id, info);
}
}
/**
* Recomputes the properties for a client sitemap entry.<p>
*
* @param entry the entry for whose descendants the properties should be recomputed
*/
protected void recomputeProperties(CmsClientSitemapEntry entry) {
for (I_CmsPropertyUpdateHandler handler : m_propertyUpdateHandlers) {
handler.handlePropertyUpdate(entry);
}
for (CmsClientSitemapEntry child : entry.getSubEntries()) {
recomputeProperties(child);
}
}
/**
* Returns the properties above the sitemap root.<p>
*
* @return the map of properties of the root's parent
*/
private Map<String, CmsClientProperty> getParentProperties() {
return m_data.getParentProperties();
}
/**
* Checks if the given path and position indicate a changed position for the entry.<p>
*
* @param entry the sitemap entry to move
* @param toPath the destination path
* @param position the new position between its siblings
*
* @return <code>true</code> if this is a position change
*/
private boolean isChangedPosition(CmsClientSitemapEntry entry, String toPath, int position) {
return (!entry.getSitePath().equals(toPath) || (entry.getPosition() != position));
}
/**
* Validates the entry and the given path.<p>
*
* @param entry the entry
* @param toPath the path
*
* @return <code>true</code> if entry and path are valid
*/
private boolean isValidEntryAndPath(CmsClientSitemapEntry entry, String toPath) {
return ((toPath != null)
&& (CmsResource.getParentFolder(toPath) != null)
&& (entry != null)
&& (getEntry(CmsResource.getParentFolder(toPath)) != null) && (getEntry(entry.getSitePath()) != null));
}
/**
* Removes all children of the given entry recursively from the data model.<p>
*
* @param entry the entry
*/
private void removeAllChildren(CmsClientSitemapEntry entry) {
for (CmsClientSitemapEntry child : entry.getSubEntries()) {
removeAllChildren(child);
m_entriesById.remove(child.getId());
m_entriesByPath.remove(child.getSitePath());
// apply to detailpage table
CmsDetailPageTable detailPageTable = getData().getDetailPageTable();
if (detailPageTable.contains(child.getId())) {
detailPageTable.remove(child.getId());
}
}
entry.getSubEntries().clear();
}
/**
* Removes the given entry and it's descendants from the modified list.<p>
*
* @param entry the entry
* @param data the clip board data
*/
private void removeDeletedFromModified(CmsClientSitemapEntry entry, CmsSitemapClipboardData data) {
data.removeModified(entry);
for (CmsClientSitemapEntry child : entry.getSubEntries()) {
removeDeletedFromModified(child, data);
}
}
/**
* Removes the given entry from the data model.<p>
*
* @param entryId the id of the entry to remove
* @param parentId the parent entry id
*/
private void removeEntry(CmsUUID entryId, CmsUUID parentId) {
CmsClientSitemapEntry entry = getEntryById(entryId);
CmsClientSitemapEntry deleteParent = getEntryById(parentId);
removeAllChildren(entry);
deleteParent.removeSubEntry(entry.getPosition());
m_entriesById.remove(entryId);
m_entriesByPath.remove(entry.getSitePath());
// apply to detailpage table
CmsDetailPageTable detailPageTable = getData().getDetailPageTable();
if (detailPageTable.contains(entryId)) {
detailPageTable.remove(entryId);
}
}
}