001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.sitemap.client.control;
029
030import org.opencms.ade.detailpage.CmsDetailPageInfo;
031import org.opencms.ade.sitemap.client.CmsSitemapTreeItem;
032import org.opencms.ade.sitemap.client.CmsSitemapView;
033import org.opencms.ade.sitemap.client.Messages;
034import org.opencms.ade.sitemap.client.edit.CmsLocaleComparePropertyHandler;
035import org.opencms.ade.sitemap.client.edit.CmsNavModePropertyEditor;
036import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry;
037import org.opencms.ade.sitemap.shared.CmsDetailPageTable;
038import org.opencms.ade.sitemap.shared.CmsGalleryFolderEntry;
039import org.opencms.ade.sitemap.shared.CmsGalleryType;
040import org.opencms.ade.sitemap.shared.CmsLocaleComparePropertyData;
041import org.opencms.ade.sitemap.shared.CmsModelInfo;
042import org.opencms.ade.sitemap.shared.CmsModelPageEntry;
043import org.opencms.ade.sitemap.shared.CmsNewResourceInfo;
044import org.opencms.ade.sitemap.shared.CmsSitemapCategoryData;
045import org.opencms.ade.sitemap.shared.CmsSitemapChange;
046import org.opencms.ade.sitemap.shared.CmsSitemapChange.ChangeType;
047import org.opencms.ade.sitemap.shared.CmsSitemapClipboardData;
048import org.opencms.ade.sitemap.shared.CmsSitemapData;
049import org.opencms.ade.sitemap.shared.CmsSitemapData.EditorMode;
050import org.opencms.ade.sitemap.shared.I_CmsSitemapController;
051import org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService;
052import org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapServiceAsync;
053import org.opencms.file.CmsResource;
054import org.opencms.gwt.client.CmsCoreProvider;
055import org.opencms.gwt.client.property.A_CmsPropertyEditor;
056import org.opencms.gwt.client.property.CmsPropertySubmitHandler;
057import org.opencms.gwt.client.property.CmsReloadMode;
058import org.opencms.gwt.client.property.definition.CmsPropertyDefinitionButton;
059import org.opencms.gwt.client.rpc.CmsRpcAction;
060import org.opencms.gwt.client.rpc.CmsRpcPrefetcher;
061import org.opencms.gwt.client.ui.CmsDeleteWarningDialog;
062import org.opencms.gwt.client.ui.CmsErrorDialog;
063import org.opencms.gwt.client.ui.input.form.CmsDialogFormHandler;
064import org.opencms.gwt.client.ui.input.form.CmsFormDialog;
065import org.opencms.gwt.client.ui.input.form.I_CmsFormSubmitHandler;
066import org.opencms.gwt.client.ui.tree.CmsLazyTreeItem.LoadState;
067import org.opencms.gwt.client.util.CmsDebugLog;
068import org.opencms.gwt.client.util.I_CmsSimpleCallback;
069import org.opencms.gwt.shared.CmsCoreData;
070import org.opencms.gwt.shared.CmsListInfoBean;
071import org.opencms.gwt.shared.property.CmsClientProperty;
072import org.opencms.gwt.shared.property.CmsPropertyModification;
073import org.opencms.gwt.shared.rpc.I_CmsVfsServiceAsync;
074import org.opencms.util.CmsStringUtil;
075import org.opencms.util.CmsUUID;
076import org.opencms.xml.content.CmsXmlContentProperty;
077
078import java.util.ArrayList;
079import java.util.Collection;
080import java.util.Collections;
081import java.util.HashMap;
082import java.util.HashSet;
083import java.util.LinkedHashMap;
084import java.util.LinkedList;
085import java.util.List;
086import java.util.Map;
087import java.util.Set;
088
089import com.google.common.collect.Maps;
090import com.google.gwt.core.client.GWT;
091import com.google.gwt.dom.client.Style;
092import com.google.gwt.event.shared.HandlerRegistration;
093import com.google.gwt.event.shared.SimpleEventBus;
094import com.google.gwt.user.client.Command;
095import com.google.gwt.user.client.DOM;
096import com.google.gwt.user.client.Window;
097import com.google.gwt.user.client.rpc.AsyncCallback;
098import com.google.gwt.user.client.rpc.SerializationException;
099import com.google.gwt.user.client.rpc.ServiceDefTarget;
100
101/**
102 * Sitemap editor controller.<p>
103 *
104 * @since 8.0.0
105 */
106public class CmsSitemapController implements I_CmsSitemapController {
107
108    /** The name to use for new entries. */
109    public static final String NEW_ENTRY_NAME = "page";
110
111    /** A map of *all* detail page info beans, indexed by page id. */
112    protected Map<CmsUUID, CmsDetailPageInfo> m_allDetailPageInfos = new HashMap<CmsUUID, CmsDetailPageInfo>();
113
114    /** The sitemap data. */
115    protected CmsSitemapData m_data;
116
117    /** The detail page table. */
118    protected CmsDetailPageTable m_detailPageTable;
119
120    /** The event bus. */
121    protected SimpleEventBus m_eventBus;
122
123    /** The category data. */
124    CmsSitemapCategoryData m_categoryData;
125
126    /** The entry data model. */
127    private Map<CmsUUID, CmsClientSitemapEntry> m_entriesById;
128
129    /** The sitemap entries by path. */
130    private Map<String, CmsClientSitemapEntry> m_entriesByPath;
131
132    /** The gallery type names by id. */
133    private Map<Integer, CmsGalleryType> m_galleryTypes;
134
135    /** The set of names of hidden properties. */
136    private Set<String> m_hiddenProperties;
137
138    /** The map of property maps by structure id. */
139    private Map<CmsUUID, Map<String, CmsClientProperty>> m_propertyMaps = Maps.newHashMap();
140
141    /** The list of property update handlers. */
142    private List<I_CmsPropertyUpdateHandler> m_propertyUpdateHandlers = new ArrayList<I_CmsPropertyUpdateHandler>();
143
144    /** The sitemap service instance. */
145    private I_CmsSitemapServiceAsync m_service;
146
147    /** The vfs service. */
148    private I_CmsVfsServiceAsync m_vfsService;
149
150    /**
151     * Constructor.<p>
152     */
153    public CmsSitemapController() {
154
155        m_entriesById = new HashMap<CmsUUID, CmsClientSitemapEntry>();
156        m_entriesByPath = new HashMap<String, CmsClientSitemapEntry>();
157        m_galleryTypes = new HashMap<Integer, CmsGalleryType>();
158        try {
159            m_data = (CmsSitemapData)CmsRpcPrefetcher.getSerializedObjectFromDictionary(
160                getService(),
161                CmsSitemapData.DICT_NAME);
162        } catch (SerializationException e) {
163            CmsErrorDialog.handleException(
164                new Exception(
165                    "Deserialization of sitemap data failed. This may be caused by expired java-script resources, please clear your browser cache and try again.",
166                    e));
167        }
168
169        m_hiddenProperties = new HashSet<String>();
170        if (m_data != null) {
171            m_detailPageTable = m_data.getDetailPageTable();
172            m_data.getRoot().initializeAll(this);
173            m_eventBus = new SimpleEventBus();
174            initDetailPageInfos();
175        }
176    }
177
178    /**
179     * Opens the property editor for locale compare mode.<p>
180     *
181     * @param data the data used by the property editor
182     * @param ownId the id
183     * @param defaultFileId the default file id
184     * @param noEditReason the reason the properties can't be edited
185     */
186    public static void editPropertiesForLocaleCompareMode(
187        final CmsLocaleComparePropertyData data,
188        final CmsUUID ownId,
189        final CmsUUID defaultFileId,
190        final String noEditReason) {
191
192        final CmsUUID infoId;
193        infoId = defaultFileId;
194        Set<CmsUUID> idsForPropertyConfig = new HashSet<CmsUUID>();
195        idsForPropertyConfig.add(defaultFileId);
196        idsForPropertyConfig.add(ownId);
197
198        final List<CmsUUID> propertyConfigIds = new ArrayList<CmsUUID>(idsForPropertyConfig);
199
200        CmsRpcAction<CmsListInfoBean> action = new CmsRpcAction<CmsListInfoBean>() {
201
202            @Override
203            public void execute() {
204
205                start(0, true);
206                CmsCoreProvider.getVfsService().getPageInfo(infoId, this);
207            }
208
209            @Override
210            protected void onResponse(CmsListInfoBean infoResult) {
211
212                stop(false);
213                final CmsLocaleComparePropertyHandler handler = new CmsLocaleComparePropertyHandler(data);
214                handler.setPageInfo(infoResult);
215
216                CmsRpcAction<Map<CmsUUID, Map<String, CmsXmlContentProperty>>> propertyAction = new CmsRpcAction<Map<CmsUUID, Map<String, CmsXmlContentProperty>>>() {
217
218                    @Override
219                    public void execute() {
220
221                        start(0, true);
222                        CmsCoreProvider.getVfsService().getDefaultProperties(propertyConfigIds, this);
223                    }
224
225                    @Override
226                    protected void onResponse(Map<CmsUUID, Map<String, CmsXmlContentProperty>> propertyResult) {
227
228                        stop(false);
229                        Map<String, CmsXmlContentProperty> propConfig = new LinkedHashMap<String, CmsXmlContentProperty>();
230                        for (Map<String, CmsXmlContentProperty> defaultProps : propertyResult.values()) {
231                            propConfig.putAll(defaultProps);
232                        }
233                        propConfig.putAll(CmsSitemapView.getInstance().getController().getData().getProperties());
234                        A_CmsPropertyEditor editor = new CmsNavModePropertyEditor(propConfig, handler);
235                        editor.setPropertyNames(
236                            CmsSitemapView.getInstance().getController().getData().getAllPropertyNames());
237                        final CmsFormDialog dialog = new CmsFormDialog(handler.getDialogTitle(), editor.getForm());
238                        CmsPropertyDefinitionButton defButton = new CmsPropertyDefinitionButton() {
239
240                            /**
241                             * @see org.opencms.gwt.client.property.definition.CmsPropertyDefinitionButton#onBeforeEditPropertyDefinition()
242                             */
243                            @Override
244                            public void onBeforeEditPropertyDefinition() {
245
246                                dialog.hide();
247                            }
248
249                        };
250                        defButton.getElement().getStyle().setFloat(Style.Float.LEFT);
251                        defButton.installOnDialog(dialog);
252                        CmsDialogFormHandler formHandler = new CmsDialogFormHandler();
253                        formHandler.setDialog(dialog);
254                        I_CmsFormSubmitHandler submitHandler = new CmsPropertySubmitHandler(handler);
255                        formHandler.setSubmitHandler(submitHandler);
256                        dialog.setFormHandler(formHandler);
257                        editor.initializeWidgets(dialog);
258                        dialog.centerHorizontally(50);
259                        dialog.catchNotifications();
260                        if (noEditReason != null) {
261                            editor.disableInput(noEditReason, false);
262                            dialog.getOkButton().disable(noEditReason);
263                        }
264
265                    }
266                };
267                propertyAction.execute();
268            }
269
270        };
271        action.execute();
272    }
273
274    /**
275     * Helper method for looking up a value in a map which may be null.<p>
276     *
277     * @param <A> the key type
278     * @param <B> the value type
279     * @param map the map (which may be null)
280     * @param key the map key
281     *
282     * @return the value of the map at the given key, or null if the map is null
283     */
284    public static <A, B> B safeLookup(Map<A, B> map, A key) {
285
286        if (map == null) {
287            return null;
288        }
289        return map.get(key);
290    }
291
292    /**
293     * Adds a new change event handler.<p>
294     *
295     * @param handler the handler to add
296     *
297     * @return the handler registration
298     */
299    public HandlerRegistration addChangeHandler(I_CmsSitemapChangeHandler handler) {
300
301        return m_eventBus.addHandlerToSource(CmsSitemapChangeEvent.getType(), this, handler);
302    }
303
304    /**
305     * Adds a new detail page information bean.<p>
306     *
307     * @param info the detail page information bean to add
308     */
309    public void addDetailPageInfo(CmsDetailPageInfo info) {
310
311        m_detailPageTable.add(info);
312        m_allDetailPageInfos.put(info.getId(), info);
313    }
314
315    /**
316     * Adds a new load event handler.<p>
317     *
318     * @param handler the handler to add
319     *
320     * @return the handler registration
321     */
322    public HandlerRegistration addLoadHandler(I_CmsSitemapLoadHandler handler) {
323
324        return m_eventBus.addHandlerToSource(CmsSitemapLoadEvent.getType(), this, handler);
325    }
326
327    /**
328     * Adds a handler for property changes caused by user edits.<p>
329     *
330     * @param handler a new handler for property updates caused by the user
331     */
332    public void addPropertyUpdateHandler(I_CmsPropertyUpdateHandler handler) {
333
334        m_propertyUpdateHandlers.add(handler);
335
336    }
337
338    /**
339     * Adds the entry to the navigation.<p>
340     *
341     * @param entry the entry
342     */
343    public void addToNavigation(CmsClientSitemapEntry entry) {
344
345        entry.setInNavigation(true);
346        CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify);
347        CmsPropertyModification mod = new CmsPropertyModification(
348            entry.getId(),
349            CmsClientProperty.PROPERTY_NAVTEXT,
350            entry.getTitle(),
351            true);
352        change.setPropertyChanges(Collections.singletonList(mod));
353        change.setPosition(entry.getPosition());
354        commitChange(change, null);
355    }
356
357    /**
358     * Makes the given sitemap entry the default detail page for its detail page type.<p>
359     *
360     * @param entry an entry representing a detail page
361     */
362    public void bump(CmsClientSitemapEntry entry) {
363
364        CmsDetailPageTable table = getDetailPageTable().copy();
365        table.makeDefault(entry.getId());
366        CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.bumpDetailPage);
367        change.setDetailPageInfos(table.toList());
368        commitChange(change, null);
369    }
370
371    /**
372     * Changes the given category.<p>
373     *
374     * @param id the category id
375     * @param title the new category title
376     * @param name the new category name
377     */
378    public void changeCategory(final CmsUUID id, final String title, final String name) {
379
380        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
381
382            @Override
383            public void execute() {
384
385                start(200, true);
386                getService().changeCategory(getEntryPoint(), id, title, name, this);
387            }
388
389            @Override
390            protected void onResponse(Void result) {
391
392                stop(false);
393                loadCategories(true, null);
394            }
395
396        };
397        action.execute();
398    }
399
400    /**
401     * Clears the deleted clip-board list and commits the change.<p>
402     */
403    public void clearDeletedList() {
404
405        CmsSitemapClipboardData clipboardData = getData().getClipboardData().copy();
406        clipboardData.getDeletions().clear();
407        CmsSitemapChange change = new CmsSitemapChange(null, null, ChangeType.clipboardOnly);
408        change.setClipBoardData(clipboardData);
409        commitChange(change, null);
410    }
411
412    /**
413     * Clears the modified clip-board list and commits the change.<p>
414     */
415    public void clearModifiedList() {
416
417        CmsSitemapClipboardData clipboardData = getData().getClipboardData().copy();
418        clipboardData.getModifications().clear();
419        CmsSitemapChange change = new CmsSitemapChange(null, null, ChangeType.clipboardOnly);
420        change.setClipBoardData(clipboardData);
421        commitChange(change, null);
422    }
423
424    /**
425     * Registers a new sitemap entry.<p>
426     *
427     * @param newEntry the new entry
428     * @param parentId the parent entry id
429     * @param resourceTypeId the resource type id
430     * @param copyResourceId the copy resource id
431     * @param parameter an additional parameter which may contain more information needed to create the new resource
432     * @param isNewSitemap a flag controlling whether a new sitemap should be created
433     */
434    public void create(
435        CmsClientSitemapEntry newEntry,
436        CmsUUID parentId,
437        int resourceTypeId,
438        CmsUUID copyResourceId,
439        String parameter,
440        boolean isNewSitemap) {
441
442        assert (getEntry(newEntry.getSitePath()) == null);
443
444        CmsSitemapChange change = new CmsSitemapChange(null, newEntry.getSitePath(), ChangeType.create);
445        change.setDefaultFileId(newEntry.getDefaultFileId());
446        change.setParentId(parentId);
447        change.setName(newEntry.getName());
448        change.setPosition(newEntry.getPosition());
449        change.setOwnInternalProperties(newEntry.getOwnProperties());
450        change.setDefaultFileInternalProperties(newEntry.getDefaultFileProperties());
451        change.setTitle(newEntry.getTitle());
452        change.setCreateParameter(parameter);
453        change.setNewResourceTypeId(resourceTypeId);
454        if (isNewSitemap) {
455            change.setCreateSitemapFolderType(newEntry.getResourceTypeName());
456        }
457        change.setNewCopyResourceId(copyResourceId);
458        if (isDetailPage(newEntry)) {
459            CmsDetailPageTable table = getDetailPageTable().copy();
460            if (!table.contains(newEntry.getId())) {
461                CmsDetailPageInfo info = new CmsDetailPageInfo(
462                    newEntry.getId(),
463                    newEntry.getSitePath(),
464                    newEntry.getDetailpageTypeName(),
465                    /* qualifier = */null,
466                    newEntry.getVfsModeIcon());
467                table.add(info);
468            }
469            change.setDetailPageInfos(table.toList());
470        }
471        CmsSitemapClipboardData data = getData().getClipboardData().copy();
472        data.addModified(newEntry);
473        change.setClipBoardData(data);
474        commitChange(change, null);
475    }
476
477    /**
478     * Creates a new category.<p>
479     *
480     * @param id the parent category id, or the null uuid for a new top-level category
481     * @param title the title of the category
482     * @param name the name of the category
483     */
484    public void createCategory(final CmsUUID id, final String title, final String name) {
485
486        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
487
488            @Override
489            public void execute() {
490
491                start(200, true);
492                getService().createCategory(getEntryPoint(), id, title, name, this);
493            }
494
495            @Override
496            protected void onResponse(Void result) {
497
498                stop(false);
499                loadCategories(true, id);
500
501            }
502        };
503        action.execute();
504    }
505
506    /**
507     * Creates a new gallery folder of the given type.<p>
508     *
509     * @param parentId the parent folder id
510     * @param galleryTypeId the folder type id
511     * @param title the folder title
512     */
513    public void createNewGallery(final CmsUUID parentId, final int galleryTypeId, final String title) {
514
515        final String parentFolder = parentId != null
516        ? getEntryById(parentId).getSitePath()
517        : CmsStringUtil.joinPaths(m_data.getRoot().getSitePath(), m_data.getDefaultGalleryFolder());
518
519        CmsRpcAction<CmsGalleryFolderEntry> action = new CmsRpcAction<CmsGalleryFolderEntry>() {
520
521            @Override
522            public void execute() {
523
524                getService().createNewGalleryFolder(parentFolder, title, galleryTypeId, this);
525            }
526
527            @Override
528            protected void onResponse(CmsGalleryFolderEntry result) {
529
530                CmsSitemapView.getInstance().displayNewGallery(result);
531
532            }
533        };
534        action.execute();
535    }
536
537    /**
538     * Creates a new model page.<p>
539     *
540     * @param title the title of the model page
541     * @param description the description of the model page
542     * @param copyId the structure id of the resource which should be used as a copy model for the new page
543     * @param isModelGroup in case of a model group page
544     */
545    public void createNewModelPage(
546        final String title,
547        final String description,
548        final CmsUUID copyId,
549        final boolean isModelGroup) {
550
551        CmsRpcAction<CmsModelPageEntry> action = new CmsRpcAction<CmsModelPageEntry>() {
552
553            @Override
554            public void execute() {
555
556                start(200, true);
557
558                getService().createNewModelPage(getEntryPoint(), title, description, copyId, isModelGroup, this);
559
560            }
561
562            @Override
563            protected void onResponse(final CmsModelPageEntry result) {
564
565                stop(false);
566                if (isModelGroup) {
567                    CmsSitemapView.getInstance().displayNewModelPage(result, true);
568                } else {
569                    loadNewElementInfo(new AsyncCallback<Void>() {
570
571                        public void onFailure(Throwable caught) {
572
573                            // nothing to do
574
575                        }
576
577                        public void onSuccess(Void v) {
578
579                            CmsSitemapView.getInstance().displayNewModelPage(result, false);
580                        }
581                    });
582                }
583
584            }
585
586        };
587        action.execute();
588    }
589
590    /**
591     * Creates a sitemap folder.<p>
592     *
593     * @param newEntry the new entry
594     * @param parentId the entry parent id
595     * @param sitemapType the resource type for the subsitemap folder
596     */
597    public void createSitemapSubEntry(final CmsClientSitemapEntry newEntry, CmsUUID parentId, String sitemapType) {
598
599        CmsUUID structureId = m_data.getDefaultNewElementInfo().getCopyResourceId();
600        newEntry.setResourceTypeName(sitemapType);
601        create(newEntry, parentId, m_data.getDefaultNewElementInfo().getId(), structureId, null, true);
602    }
603
604    /**
605     * Creates a new sub-entry which is a subsitemap.<p>
606     *
607     * @param parent the parent entry
608     * @param sitemapFolderType the sitemap folder type
609     */
610    public void createSitemapSubEntry(final CmsClientSitemapEntry parent, final String sitemapFolderType) {
611
612        CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(parent.getId());
613        AsyncCallback<CmsClientSitemapEntry> callback = new AsyncCallback<CmsClientSitemapEntry>() {
614
615            public void onFailure(Throwable caught) {
616
617                // do nothing
618            }
619
620            public void onSuccess(CmsClientSitemapEntry result) {
621
622                makeNewEntry(parent, new I_CmsSimpleCallback<CmsClientSitemapEntry>() {
623
624                    public void execute(CmsClientSitemapEntry newEntry) {
625
626                        newEntry.setResourceTypeName(sitemapFolderType);
627                        createSitemapSubEntry(newEntry, parent.getId(), sitemapFolderType);
628                    }
629                });
630            }
631        };
632        if (item.getLoadState().equals(LoadState.UNLOADED)) {
633            getChildren(parent.getId(), true, callback);
634        } else {
635            callback.onSuccess(parent);
636        }
637    }
638
639    /**
640     * Creates a new sub-entry of an existing sitemap entry.<p>
641     *
642     * @param parent the entry to which a new sub-entry should be added
643     */
644    public void createSubEntry(final CmsClientSitemapEntry parent) {
645
646        createSubEntry(parent, null);
647    }
648
649    /**
650     * Creates a new sub-entry of an existing sitemap entry.<p>
651     *
652     * @param parent the entry to which a new sub-entry should be added
653     * @param structureId the structure id of the model page (if null, uses default model page)
654     */
655    public void createSubEntry(final CmsClientSitemapEntry parent, final CmsUUID structureId) {
656
657        CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(parent.getId());
658        AsyncCallback<CmsClientSitemapEntry> callback = new AsyncCallback<CmsClientSitemapEntry>() {
659
660            public void onFailure(Throwable caught) {
661
662                // nothing to do
663            }
664
665            public void onSuccess(CmsClientSitemapEntry result) {
666
667                makeNewEntry(parent, new I_CmsSimpleCallback<CmsClientSitemapEntry>() {
668
669                    public void execute(CmsClientSitemapEntry newEntry) {
670
671                        createSubEntry(newEntry, parent.getId(), structureId);
672                    }
673                });
674
675            }
676
677        };
678
679        if (item.getLoadState().equals(LoadState.UNLOADED)) {
680            getChildren(parent.getId(), true, callback);
681        } else {
682            callback.onSuccess(parent);
683        }
684    }
685
686    /**
687     * Registers a new sitemap entry.<p>
688     *
689     * @param newEntry the new entry
690     * @param parentId the parent entry id
691     * @param structureId the structure id of the model page (if null, uses default model page)
692     */
693    public void createSubEntry(final CmsClientSitemapEntry newEntry, CmsUUID parentId, CmsUUID structureId) {
694
695        if (structureId == null) {
696            structureId = m_data.getDefaultNewElementInfo().getCopyResourceId();
697        }
698        create(newEntry, parentId, m_data.getDefaultNewElementInfo().getId(), structureId, null, false);
699    }
700
701    /**
702     * Creates a sub-sitemap from the subtree of the current sitemap starting at the given entry.<p>
703     *
704     * @param entryId the id of the entry
705     */
706    public void createSubSitemap(final CmsUUID entryId) {
707
708        CmsRpcAction<CmsSitemapChange> subSitemapAction = new CmsRpcAction<CmsSitemapChange>() {
709
710            /**
711             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
712             */
713            @Override
714            public void execute() {
715
716                start(0, true);
717                getService().createSubSitemap(entryId, this);
718            }
719
720            /**
721             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
722             */
723            @Override
724            protected void onResponse(CmsSitemapChange result) {
725
726                stop(false);
727                applyChange(result);
728            }
729        };
730        subSitemapAction.execute();
731    }
732
733    /**
734     * Deletes the given entry and all its descendants.<p>
735     *
736     * @param sitePath the site path of the entry to delete
737     */
738    public void delete(String sitePath) {
739
740        CmsClientSitemapEntry entry = getEntry(sitePath);
741        CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(entry.getSitePath()));
742        CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.delete);
743        change.setParentId(parent.getId());
744        change.setDefaultFileId(entry.getDefaultFileId());
745        CmsSitemapClipboardData data = CmsSitemapView.getInstance().getController().getData().getClipboardData().copy();
746        if (!entry.isNew()) {
747            data.addDeleted(entry);
748            removeDeletedFromModified(entry, data);
749        }
750        change.setClipBoardData(data);
751        CmsDetailPageTable detailPageTable = CmsSitemapView.getInstance().getController().getData().getDetailPageTable();
752        CmsUUID id = entry.getId();
753        if (detailPageTable.contains(id)) {
754            CmsDetailPageTable copyTable = detailPageTable.copy();
755            copyTable.remove(id);
756            change.setDetailPageInfos(copyTable.toList());
757        }
758        commitChange(change, null);
759    }
760
761    /**
762     * Deletes a category.<p>
763     *
764     * @param id the id of the category
765     */
766    public void deleteCategory(final CmsUUID id) {
767
768        CmsDeleteWarningDialog deleteWarningDialog = new CmsDeleteWarningDialog(id) {
769
770            @Override
771            protected void onAfterDeletion() {
772
773                CmsSitemapView.getInstance().getController().loadCategories(true, null);
774            }
775        };
776        deleteWarningDialog.loadAndShow(null);
777    }
778
779    /**
780     * Disables the given model page entry within the configuration.<p>
781     *
782     * @param id the entry id
783     * @param disable <code>true</code> to disable the entry
784     */
785    public void disableModelPage(final CmsUUID id, final boolean disable) {
786
787        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
788
789            @Override
790            public void execute() {
791
792                start(200, true);
793                getService().disableModelPage(getEntryPoint(), id, disable, this);
794
795            }
796
797            @Override
798            protected void onResponse(Void result) {
799
800                stop(false);
801                CmsSitemapView.getInstance().updateModelPageDisabledState(id, disable);
802            }
803        };
804        action.execute();
805
806    }
807
808    /**
809     * Edits the given sitemap entry.<p>
810     *
811     * @param entry the sitemap entry to update
812     * @param propertyChanges the property changes
813     * @param reloadStatus a value indicating which entries need to be reloaded after the change
814     */
815    public void edit(
816        CmsClientSitemapEntry entry,
817        List<CmsPropertyModification> propertyChanges,
818        final CmsReloadMode reloadStatus) {
819
820        CmsSitemapChange change = getChangeForEdit(entry, propertyChanges);
821        final String updateTarget = ((reloadStatus == CmsReloadMode.reloadParent))
822        ? getParentEntry(entry).getSitePath()
823        : entry.getSitePath();
824        Command callback = new Command() {
825
826            public void execute() {
827
828                if ((reloadStatus == CmsReloadMode.reloadParent) || (reloadStatus == CmsReloadMode.reloadEntry)) {
829                    updateEntry(updateTarget);
830                }
831            }
832        };
833        if (change != null) {
834            commitChange(change, callback);
835        }
836    }
837
838    /**
839     * Edits an entry and changes its URL name.<p>
840     *
841     * @param entry the entry which is being edited
842     * @param newUrlName the new URL name of the entry
843     * @param propertyChanges the property changes
844     * @param keepNewStatus <code>true</code> if the entry should keep it's new status
845     * @param reloadStatus a value indicating which entries need to be reloaded after the change
846     */
847    public void editAndChangeName(
848        final CmsClientSitemapEntry entry,
849        String newUrlName,
850        List<CmsPropertyModification> propertyChanges,
851        final boolean keepNewStatus,
852        final CmsReloadMode reloadStatus) {
853
854        CmsSitemapChange change = getChangeForEdit(entry, propertyChanges);
855        change.setName(newUrlName);
856        final CmsUUID entryId = entry.getId();
857        final boolean newStatus = keepNewStatus && entry.isNew();
858
859        final CmsUUID updateTarget = ((reloadStatus == CmsReloadMode.reloadParent))
860        ? getParentEntry(entry).getId()
861        : entryId;
862        Command callback = new Command() {
863
864            public void execute() {
865
866                if ((reloadStatus == CmsReloadMode.reloadParent) || (reloadStatus == CmsReloadMode.reloadEntry)) {
867                    updateEntry(updateTarget);
868                }
869                getEntryById(entryId).setNew(newStatus);
870            }
871
872        };
873
874        commitChange(change, callback);
875    }
876
877    /**
878     * Ensure the uniqueness of a given URL-name within the children of the given parent site-map entry.<p>
879     *
880     * @param parent the parent entry
881     * @param newName the proposed name
882     * @param callback the callback to execute
883     */
884    public void ensureUniqueName(CmsClientSitemapEntry parent, String newName, I_CmsSimpleCallback<String> callback) {
885
886        ensureUniqueName(parent.getSitePath(), newName, callback);
887    }
888
889    /**
890     * Ensure the uniqueness of a given URL-name within the children of the given parent folder.<p>
891     *
892     * @param parentFolder the parent folder
893     * @param newName the proposed name
894     * @param callback the callback to execute
895     */
896    public void ensureUniqueName(
897        final String parentFolder,
898        String newName,
899        final I_CmsSimpleCallback<String> callback) {
900
901        // using lower case folder names
902        final String lowerCaseName = newName.toLowerCase();
903        CmsRpcAction<String> action = new CmsRpcAction<String>() {
904
905            /**
906             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
907             */
908            @Override
909            public void execute() {
910
911                start(0, false);
912                CmsCoreProvider.getService().getUniqueFileName(parentFolder, lowerCaseName, this);
913            }
914
915            /**
916             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
917             */
918            @Override
919            protected void onResponse(String result) {
920
921                stop(false);
922                callback.execute(result);
923            }
924
925        };
926        action.execute();
927    }
928
929    /**
930     * Applies the given property modification.<p>
931     *
932     * @param propMod the property modification to apply
933     */
934    public void executePropertyModification(CmsPropertyModification propMod) {
935
936        CmsClientSitemapEntry entry = getEntryById(propMod.getId());
937        if (entry != null) {
938            Map<String, CmsClientProperty> props = getPropertiesForId(propMod.getId());
939            if (props != null) {
940                propMod.updatePropertyInMap(props);
941                entry.setOwnProperties(props);
942            }
943        }
944    }
945
946    /**
947     * Gets the category data.<p>
948     *
949     * @return the category data
950     */
951    public CmsSitemapCategoryData getCategoryData() {
952
953        return m_categoryData;
954    }
955
956    /**
957     * Retrieves the child entries of the given node from the server.<p>
958     *
959     * @param entryId the entry id
960     * @param setOpen if the entry should be opened
961     *
962     * @param callback the callback to execute after the children have been loaded
963     */
964    public void getChildren(
965        final CmsUUID entryId,
966        final boolean setOpen,
967        final AsyncCallback<CmsClientSitemapEntry> callback) {
968
969        getChildren(entryId, setOpen, false, callback);
970
971    }
972
973    /**
974     * Retrieves the child entries of the given node from the server.<p>
975     *
976     * @param entryId the entry id
977     * @param setOpen if the entry should be opened
978     * @param continueIfParentNotLoaded if false, and the entry identified by the id has not been loaded, stop; else store the entry in memory
979     *
980     * @param callback the callback to execute after the children have been loaded
981     */
982    public void getChildren(
983        final CmsUUID entryId,
984        final boolean setOpen,
985        final boolean continueIfParentNotLoaded,
986        final AsyncCallback<CmsClientSitemapEntry> callback) {
987
988        CmsRpcAction<CmsClientSitemapEntry> getChildrenAction = new CmsRpcAction<CmsClientSitemapEntry>() {
989
990            /**
991            * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
992            */
993            @Override
994            public void execute() {
995
996                // Make the call to the sitemap service
997                start(500, false);
998                // loading grand children as well
999                getService().getChildren(getEntryPoint(), entryId, 2, this);
1000            }
1001
1002            /**
1003            * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1004            */
1005            @Override
1006            public void onResponse(CmsClientSitemapEntry result) {
1007
1008                CmsClientSitemapEntry target = getEntryById(entryId);
1009                if ((target == null) && !continueIfParentNotLoaded) {
1010                    // this might happen after an automated deletion
1011                    stop(false);
1012                    return;
1013                }
1014                CmsSitemapTreeItem item = null;
1015                if (target != null) {
1016                    target.setSubEntries(result.getSubEntries(), CmsSitemapController.this);
1017                    item = CmsSitemapTreeItem.getItemById(target.getId());
1018                    target.update(result);
1019                } else {
1020                    target = result;
1021                }
1022
1023                target.initializeAll(CmsSitemapController.this);
1024                if (item != null) {
1025
1026                    item.updateEntry(target);
1027                }
1028                m_eventBus.fireEventFromSource(new CmsSitemapLoadEvent(target, setOpen), CmsSitemapController.this);
1029                stop(false);
1030                if (callback != null) {
1031                    callback.onSuccess(result);
1032                }
1033            }
1034        };
1035        getChildrenAction.execute();
1036    }
1037
1038    /**
1039    * Returns the sitemap data.<p>
1040    *
1041    * @return the sitemap data
1042    */
1043    public CmsSitemapData getData() {
1044
1045        return m_data;
1046    }
1047
1048    /**
1049     * Returns the detail page info for a given entry id.<p>
1050     *
1051     * @param id a sitemap entry id
1052     *
1053     * @return the detail page info for that id
1054     */
1055    public CmsDetailPageInfo getDetailPageInfo(CmsUUID id) {
1056
1057        return m_allDetailPageInfos.get(id);
1058    }
1059
1060    /**
1061     * Returns the detail page table.<p>
1062     *
1063     * @return the detail page table
1064     */
1065    public CmsDetailPageTable getDetailPageTable() {
1066
1067        return m_detailPageTable;
1068    }
1069
1070    /**
1071     * Gets the effective value of a property value for a sitemap entry.<p>
1072     *
1073     * @param entry the sitemap entry
1074     * @param name the name of the property
1075     *
1076     * @return the effective value
1077     */
1078    public String getEffectiveProperty(CmsClientSitemapEntry entry, String name) {
1079
1080        CmsClientProperty prop = getEffectivePropertyObject(entry, name);
1081        if (prop == null) {
1082            return null;
1083        }
1084        return prop.getEffectiveValue();
1085    }
1086
1087    /**
1088     * Gets the value of a property which is effective at a given sitemap entry.<p>
1089     *
1090     * @param entry the sitemap entry
1091     * @param name the name of the property
1092     * @return the effective property value
1093     */
1094    public CmsClientProperty getEffectivePropertyObject(CmsClientSitemapEntry entry, String name) {
1095
1096        Map<String, CmsClientProperty> dfProps = entry.getDefaultFileProperties();
1097        CmsClientProperty result = safeLookup(dfProps, name);
1098        if (!CmsClientProperty.isPropertyEmpty(result)) {
1099            return result.withOrigin(entry.getSitePath());
1100        }
1101        result = safeLookup(entry.getOwnProperties(), name);
1102        if (!CmsClientProperty.isPropertyEmpty(result)) {
1103            return result.withOrigin(entry.getSitePath());
1104        }
1105        return getInheritedPropertyObject(entry, name);
1106    }
1107
1108    /**
1109     * Returns all entries with an id from a given list.<p>
1110     *
1111     * @param ids a list of sitemap entry ids
1112     *
1113     * @return all entries whose id is contained in the id list
1114     */
1115    public Map<CmsUUID, CmsClientSitemapEntry> getEntriesById(Collection<CmsUUID> ids) {
1116
1117        // TODO: use some map of id -> entry instead
1118        List<CmsClientSitemapEntry> entriesToProcess = new ArrayList<CmsClientSitemapEntry>();
1119        Map<CmsUUID, CmsClientSitemapEntry> result = new HashMap<CmsUUID, CmsClientSitemapEntry>();
1120
1121        entriesToProcess.add(m_data.getRoot());
1122        while (!entriesToProcess.isEmpty()) {
1123            CmsClientSitemapEntry entry = entriesToProcess.remove(entriesToProcess.size() - 1);
1124            if (ids.contains(entry.getId())) {
1125                result.put(entry.getId(), entry);
1126                if (result.size() == ids.size()) {
1127                    return result;
1128                }
1129            }
1130            entriesToProcess.addAll(entry.getSubEntries());
1131        }
1132        return result;
1133    }
1134
1135    /**
1136     * Returns the tree entry with the given path.<p>
1137     *
1138     * @param entryPath the path to look for
1139     *
1140     * @return the tree entry with the given path, or <code>null</code> if not found
1141     */
1142    public CmsClientSitemapEntry getEntry(String entryPath) {
1143
1144        return m_entriesByPath.get(entryPath);
1145    }
1146
1147    /**
1148     * Finds an entry by id.<p>
1149     *
1150     * @param id the id of the entry to find
1151     *
1152     * @return the found entry, or null if the entry wasn't found
1153     */
1154    public CmsClientSitemapEntry getEntryById(CmsUUID id) {
1155
1156        return m_entriesById.get(id);
1157    }
1158
1159    /**
1160     * Returns the gallery type with the given id.<p>
1161     *
1162     * @param typeId the type id
1163     *
1164     * @return the gallery type
1165     */
1166    public CmsGalleryType getGalleryType(Integer typeId) {
1167
1168        return m_galleryTypes.get(typeId);
1169    }
1170
1171    /**
1172     * Gets the value for a property which a sitemap entry would inherit if it didn't have its own properties.<p>
1173     *
1174     * @param entry the sitemap entry
1175     * @param name the property name
1176     * @return the inherited property value
1177     */
1178    public String getInheritedProperty(CmsClientSitemapEntry entry, String name) {
1179
1180        CmsClientProperty prop = getInheritedPropertyObject(entry, name);
1181        if (prop == null) {
1182            return null;
1183        }
1184        return prop.getEffectiveValue();
1185    }
1186
1187    /**
1188     * Gets the property object which would be inherited by a sitemap entry.<p>
1189     *
1190     * @param entry the sitemap entry
1191     * @param name the name of the property
1192     * @return the property object which would be inherited
1193     */
1194    public CmsClientProperty getInheritedPropertyObject(CmsClientSitemapEntry entry, String name) {
1195
1196        CmsClientSitemapEntry currentEntry = entry;
1197        while (currentEntry != null) {
1198            currentEntry = getParentEntry(currentEntry);
1199            if (currentEntry != null) {
1200                CmsClientProperty folderProp = currentEntry.getOwnProperties().get(name);
1201                if (!CmsClientProperty.isPropertyEmpty(folderProp)) {
1202                    return folderProp.withOrigin(currentEntry.getSitePath());
1203                }
1204            }
1205        }
1206        CmsClientProperty parentProp = getParentProperties().get(name);
1207        if (!CmsClientProperty.isPropertyEmpty(parentProp)) {
1208            String origin = parentProp.getOrigin();
1209            String siteRoot = CmsCoreProvider.get().getSiteRoot();
1210            if (origin.startsWith(siteRoot)) {
1211                origin = origin.substring(siteRoot.length());
1212            }
1213            return parentProp.withOrigin(origin);
1214        }
1215        return null;
1216
1217    }
1218
1219    /**
1220     * Returns a list of all descendant sitemap entries of a given path which have already been loaded on the client.<p>
1221     *
1222     * @param path the path for which the descendants should be collected
1223     *
1224     * @return the list of descendant sitemap entries
1225     */
1226    public List<CmsClientSitemapEntry> getLoadedDescendants(String path) {
1227
1228        LinkedList<CmsClientSitemapEntry> remainingEntries = new LinkedList<CmsClientSitemapEntry>();
1229        List<CmsClientSitemapEntry> result = new ArrayList<CmsClientSitemapEntry>();
1230        CmsClientSitemapEntry entry = getEntry(path);
1231        remainingEntries.add(entry);
1232        while (remainingEntries.size() > 0) {
1233            CmsClientSitemapEntry currentEntry = remainingEntries.removeFirst();
1234            result.add(currentEntry);
1235            for (CmsClientSitemapEntry subEntry : currentEntry.getSubEntries()) {
1236                remainingEntries.add(subEntry);
1237            }
1238        }
1239
1240        return result;
1241
1242    }
1243
1244    /**
1245     * Returns the no edit reason or <code>null</code> if editing is allowed.<p>
1246     *
1247     * @param entry the entry to get the no edit reason for
1248     *
1249     * @return the no edit reason
1250     */
1251    public String getNoEditReason(CmsClientSitemapEntry entry) {
1252
1253        return getNoEditReason(entry, true);
1254    }
1255
1256    /**
1257     * Returns the no edit reason or <code>null</code> if editing is allowed.<p>
1258     *
1259     * @param entry the entry to get the no edit reason for
1260     * @param checkChildLocks true if locks of children should be checked
1261     *
1262     * @return the no edit reason
1263     */
1264    public String getNoEditReason(CmsClientSitemapEntry entry, boolean checkChildLocks) {
1265
1266        String reason = entry.getNoEditReason();
1267        if (CmsStringUtil.isEmptyOrWhitespaceOnly(reason)) {
1268            reason = null;
1269            if ((entry.getLock() != null)
1270                && (entry.getLock().getLockOwner() != null)
1271                && !entry.getLock().isOwnedByUser()) {
1272                reason = Messages.get().key(Messages.GUI_DISABLED_LOCKED_BY_1, entry.getLock().getLockOwner());
1273
1274            }
1275            if (checkChildLocks && entry.hasBlockingLockedChildren()) {
1276                reason = Messages.get().key(Messages.GUI_DISABLED_BLOCKING_LOCKED_CHILDREN_0);
1277            }
1278        }
1279        return reason;
1280    }
1281
1282    /**
1283     * Returns the parent entry of a sitemap entry, or null if it is the root entry.<p>
1284     *
1285     * @param entry a sitemap entry
1286     *
1287     * @return the parent entry or null
1288     */
1289    public CmsClientSitemapEntry getParentEntry(CmsClientSitemapEntry entry) {
1290
1291        String path = entry.getSitePath();
1292        String parentPath = CmsResource.getParentFolder(path);
1293        if (parentPath == null) {
1294            return null;
1295        }
1296        return getEntry(parentPath);
1297    }
1298
1299    /**
1300     * Gets the properties for a given structure id.<p>
1301     *
1302     * @param id the structure id of a sitemap entry
1303     *
1304     * @return the properties for that structure id
1305     */
1306    public Map<String, CmsClientProperty> getPropertiesForId(CmsUUID id) {
1307
1308        return m_propertyMaps.get(id);
1309    }
1310
1311    /**
1312     * Returns the sitemap service instance.<p>
1313     *
1314     * @return the sitemap service instance
1315     */
1316    public I_CmsSitemapServiceAsync getService() {
1317
1318        if (m_service == null) {
1319            m_service = GWT.create(I_CmsSitemapService.class);
1320            String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.sitemap.CmsVfsSitemapService.gwt");
1321            ((ServiceDefTarget)m_service).setServiceEntryPoint(serviceUrl);
1322        }
1323        return m_service;
1324    }
1325
1326    /**
1327     * Leaves the current sitemap to open the parent sitemap.<p>
1328     */
1329    public void gotoParentSitemap() {
1330
1331        openSiteMap(getData().getParentSitemap());
1332    }
1333
1334    /**
1335     * Hides the entry within the site navigation.<p>
1336     *
1337     * Hidden entries will still be visible in the navigation mode of the sitemap editor.
1338     * They will also have a NavText and a NavPos. Only when using the NavBuilder get navigation for folder method,
1339     * they will not be included.<p>
1340     *
1341     * @param entryId the entry id
1342     */
1343    public void hideInNavigation(CmsUUID entryId) {
1344
1345        CmsClientSitemapEntry entry = getEntryById(entryId);
1346        CmsSitemapChange change = getChangeForEdit(
1347            entry,
1348            Collections.singletonList(
1349                new CmsPropertyModification(
1350                    entryId.toString()
1351                        + "/"
1352                        + CmsClientProperty.PROPERTY_NAVINFO
1353                        + "/"
1354                        + CmsClientProperty.PATH_STRUCTURE_VALUE,
1355                    CmsClientSitemapEntry.HIDDEN_NAVIGATION_ENTRY)));
1356        commitChange(change, null);
1357    }
1358
1359    /**
1360     * Checks whether this entry belongs to a detail page.<p>
1361     *
1362     * @param entry the entry to check
1363     *
1364     * @return true if this entry belongs to a detail page
1365     */
1366    public boolean isDetailPage(CmsClientSitemapEntry entry) {
1367
1368        return (entry.getDetailpageTypeName() != null) || isDetailPage(entry.getId());
1369    }
1370
1371    /**
1372     * Returns true if the id is the id of a detail page.<p>
1373     *
1374     * @param id the sitemap entry id
1375     * @return true if the id is the id of a detail page entry
1376     */
1377    public boolean isDetailPage(CmsUUID id) {
1378
1379        return m_allDetailPageInfos.containsKey(id);
1380    }
1381
1382    /**
1383     * Checks if the current sitemap is editable.<p>
1384     *
1385     * @return <code>true</code> if the current sitemap is editable
1386     */
1387    public boolean isEditable() {
1388
1389        return CmsStringUtil.isEmptyOrWhitespaceOnly(m_data.getNoEditReason());
1390    }
1391
1392    /**
1393     * Checks whether a string is the name of a hidden property.<p>
1394     *
1395     * A hidden property is a property which should not appear in the property editor
1396     * because it requires special treatment.<p>
1397     *
1398     * @param propertyName the property name which should be checked
1399     *
1400     * @return true if the argument is the name of a hidden property
1401     */
1402    public boolean isHiddenProperty(String propertyName) {
1403
1404        if (propertyName.equals("secure") && !m_data.isSecure()) {
1405            // "secure" property should not be editable in a site for which no secure server is configured
1406            return true;
1407        }
1408
1409        return m_hiddenProperties.contains(propertyName);
1410    }
1411
1412    /**
1413     * Returns true if the locale comparison mode is enabled.<p>
1414     *
1415     * @return true if the locale comparison mode is enabled
1416     */
1417    public boolean isLocaleComparisonEnabled() {
1418
1419        return m_data.isLocaleComparisonEnabled();
1420    }
1421
1422    /**
1423     * Checks if the given site path is the sitemap root.<p>
1424     *
1425     * @param sitePath the site path to check
1426     *
1427     * @return <code>true</code> if the given site path is the sitemap root
1428     */
1429    public boolean isRoot(String sitePath) {
1430
1431        return m_data.getRoot().getSitePath().equals(sitePath);
1432    }
1433
1434    /**
1435     * Ask to save the page before leaving, if necessary.<p>
1436     *
1437     * @param target the leaving target
1438     */
1439    public void leaveEditor(String target) {
1440
1441        CmsUUID baseId = getData().getRoot().getId();
1442        CmsRpcAction<String> action = new CmsRpcAction<String>() {
1443
1444            @Override
1445            public void execute() {
1446
1447                start(0, false);
1448                getService().getResourceLink(baseId, target, this);
1449            }
1450
1451            @Override
1452            protected void onResponse(String link) {
1453
1454                Window.Location.assign(link);
1455            }
1456
1457        };
1458        action.execute();
1459    }
1460
1461    /**
1462     * Loads and displays the category data.<p>
1463     *
1464     * @param openLocalCategories true if the local category tree should be opened
1465     * @param openItemId the id of the item to open
1466     */
1467    public void loadCategories(final boolean openLocalCategories, final CmsUUID openItemId) {
1468
1469        CmsRpcAction<CmsSitemapCategoryData> action = new CmsRpcAction<CmsSitemapCategoryData>() {
1470
1471            @Override
1472            public void execute() {
1473
1474                start(200, false);
1475                getService().getCategoryData(getEntryPoint(), this);
1476            }
1477
1478            @Override
1479            protected void onResponse(CmsSitemapCategoryData result) {
1480
1481                stop(false);
1482                m_categoryData = result;
1483                CmsSitemapView.getInstance().displayCategoryData(result, openLocalCategories, openItemId);
1484            }
1485        };
1486        action.execute();
1487    }
1488
1489    /**
1490     * Loads all available galleries for the current sub site.<p>
1491     */
1492    public void loadGalleries() {
1493
1494        CmsRpcAction<Map<CmsGalleryType, List<CmsGalleryFolderEntry>>> action = new CmsRpcAction<Map<CmsGalleryType, List<CmsGalleryFolderEntry>>>() {
1495
1496            @Override
1497            public void execute() {
1498
1499                start(500, false);
1500                getService().getGalleryData(m_data.getRoot().getSitePath(), this);
1501            }
1502
1503            @Override
1504            protected void onResponse(Map<CmsGalleryType, List<CmsGalleryFolderEntry>> result) {
1505
1506                storeGalleryTypes(result.keySet());
1507                CmsSitemapView.getInstance().displayGalleries(result);
1508                stop(false);
1509            }
1510        };
1511        action.execute();
1512    }
1513
1514    /**
1515     * Loads the model pages.<p>
1516     */
1517    public void loadModelPages() {
1518
1519        CmsRpcAction<CmsModelInfo> action = new CmsRpcAction<CmsModelInfo>() {
1520
1521            @Override
1522            public void execute() {
1523
1524                start(500, false);
1525                getService().getModelInfos(m_data.getRoot().getId(), this);
1526
1527            }
1528
1529            @Override
1530            protected void onResponse(CmsModelInfo result) {
1531
1532                stop(false);
1533                CmsSitemapView.getInstance().displayModelPages(result);
1534            }
1535        };
1536        action.execute();
1537    }
1538
1539    /**
1540     * Loads the new element info.<p>
1541     *
1542     * @param callback the callback to call when done
1543     */
1544    public void loadNewElementInfo(final AsyncCallback<Void> callback) {
1545
1546        CmsRpcAction<List<CmsNewResourceInfo>> newResourceInfoAction = new CmsRpcAction<List<CmsNewResourceInfo>>() {
1547
1548            @Override
1549            public void execute() {
1550
1551                start(200, true);
1552
1553                getService().getNewElementInfo(m_data.getRoot().getSitePath(), this);
1554            }
1555
1556            @Override
1557            protected void onResponse(List<CmsNewResourceInfo> result) {
1558
1559                stop(false);
1560
1561                m_data.setNewElementInfos(result);
1562                if (callback != null) {
1563                    callback.onSuccess(null);
1564                }
1565            }
1566
1567        };
1568        newResourceInfoAction.execute();
1569    }
1570
1571    /**
1572     * Loads all entries on the given path.<p>
1573     *
1574     * @param sitePath the site path
1575     */
1576    public void loadPath(final String sitePath) {
1577
1578        loadPath(sitePath, null);
1579    }
1580
1581    /**
1582     * Loads the sitemap entry for the given site path.<p>
1583     *
1584     * @param sitePath the site path
1585     * @param callback the callback
1586     */
1587    public void loadPath(final String sitePath, final AsyncCallback<CmsClientSitemapEntry> callback) {
1588
1589        loadPath(sitePath, false, callback);
1590    }
1591
1592    /**
1593     * Loads all entries on the given path.<p>
1594     *
1595     * @param sitePath the site path
1596     * @param continueIfNotLoaded parameter passed to getChildren
1597     * @param callback the callback to execute when done
1598     */
1599    public void loadPath(
1600        final String sitePath,
1601        final boolean continueIfNotLoaded,
1602        final AsyncCallback<CmsClientSitemapEntry> callback) {
1603
1604        if (getEntry(sitePath) != null) {
1605            CmsClientSitemapEntry entry = getEntry(sitePath);
1606            getChildren(entry.getId(), CmsSitemapTreeItem.getItemById(entry.getId()).isOpen(), callback);
1607        } else {
1608            String parentPath = CmsResource.getParentFolder(sitePath);
1609            CmsUUID idToLoad = null;
1610            CmsClientSitemapEntry entry = getEntry(parentPath);
1611            while (entry == null) {
1612                parentPath = CmsResource.getParentFolder(parentPath);
1613                if (parentPath == null) {
1614                    break;
1615                } else {
1616                    entry = getEntry(parentPath);
1617                }
1618            }
1619            if (entry != null) {
1620                idToLoad = entry.getId();
1621            } else {
1622                idToLoad = m_data.getSiteRootId();
1623            }
1624            CmsSitemapTreeItem treeItem = CmsSitemapTreeItem.getItemById(idToLoad);
1625            boolean open = true;
1626            if (treeItem != null) {
1627                open = treeItem.isOpen();
1628            }
1629            getChildren(idToLoad, open, continueIfNotLoaded, new AsyncCallback<CmsClientSitemapEntry>() {
1630
1631                public void onFailure(Throwable caught) {
1632
1633                    // nothing to do
1634                }
1635
1636                public void onSuccess(CmsClientSitemapEntry result) {
1637
1638                    // check if target entry is loaded
1639                    CmsClientSitemapEntry target = getEntry(sitePath);
1640                    if (target == null) {
1641                        loadPath(sitePath, continueIfNotLoaded, callback);
1642                    } else {
1643                        if (callback != null) {
1644                            callback.onSuccess(target);
1645                        }
1646                    }
1647                }
1648            });
1649        }
1650    }
1651
1652    /**
1653    * Merges a subsitemap at the given id back into this sitemap.<p>
1654    *
1655    * @param entryId the id of the sub sitemap entry
1656    */
1657    public void mergeSubSitemap(final CmsUUID entryId) {
1658
1659        CmsRpcAction<CmsSitemapChange> mergeAction = new CmsRpcAction<CmsSitemapChange>() {
1660
1661            /**
1662             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
1663             */
1664            @Override
1665            public void execute() {
1666
1667                start(0, true);
1668                getService().mergeSubSitemap(getEntryPoint(), entryId, this);
1669            }
1670
1671            /**
1672             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
1673             */
1674            @Override
1675            protected void onResponse(CmsSitemapChange result) {
1676
1677                stop(false);
1678                applyChange(result);
1679
1680            }
1681        };
1682        mergeAction.execute();
1683
1684    }
1685
1686    /**
1687     * Moves the given sitemap entry with all its descendants to the new position.<p>
1688     *
1689     * @param entry the sitemap entry to move
1690     * @param toPath the destination path
1691     * @param position the new position between its siblings
1692     */
1693    public void move(CmsClientSitemapEntry entry, String toPath, int position) {
1694
1695        // check for valid data
1696        if (!isValidEntryAndPath(entry, toPath)) {
1697            // invalid data, do nothing
1698            CmsDebugLog.getInstance().printLine("invalid data, doing nothing");
1699            return;
1700        }
1701        // check for relevance
1702        if (isChangedPosition(entry, toPath, position)) {
1703            // only register real changes
1704            CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify);
1705            change.setDefaultFileId(entry.getDefaultFileId());
1706            if (!toPath.equals(entry.getSitePath())) {
1707                change.setParentId(getEntry(CmsResource.getParentFolder(toPath)).getId());
1708                change.setName(CmsResource.getName(toPath));
1709            }
1710            if (CmsSitemapView.getInstance().isNavigationMode()) {
1711                change.setPosition(position);
1712            }
1713            change.setLeafType(entry.isLeafType());
1714            CmsSitemapClipboardData data = getData().getClipboardData().copy();
1715            data.addModified(entry);
1716            change.setClipBoardData(data);
1717            commitChange(change, null);
1718        }
1719    }
1720
1721    /**
1722     * Opens the property dialog for the locale comparison mode.<p>
1723     *
1724     * @param structureId the structure id of the sitemap entry to edit
1725     * @param rootId the structure id of the resource comparing to the tree root in sitemap compare mode
1726     */
1727    public void openPropertyDialogForVaadin(final CmsUUID structureId, final CmsUUID rootId) {
1728
1729        CmsRpcAction<CmsLocaleComparePropertyData> action = new CmsRpcAction<CmsLocaleComparePropertyData>() {
1730
1731            @SuppressWarnings("synthetic-access")
1732            @Override
1733            public void execute() {
1734
1735                start(0, false);
1736                m_service.loadPropertyDataForLocaleCompareView(structureId, rootId, this);
1737            }
1738
1739            @Override
1740            protected void onResponse(CmsLocaleComparePropertyData result) {
1741
1742                stop(false);
1743                CmsSitemapController.editPropertiesForLocaleCompareMode(
1744                    result,
1745                    structureId,
1746                    result.getDefaultFileId(),
1747                    null);
1748            }
1749        };
1750        action.execute();
1751
1752    }
1753
1754    /**
1755     * Opens the site-map specified.<p>
1756     *
1757     * @param sitePath the site path to the site-map folder
1758     */
1759    public void openSiteMap(String sitePath) {
1760
1761        openSiteMap(sitePath, false);
1762    }
1763
1764    /**
1765     * Opens the site-map specified.<p>
1766     *
1767     * @param sitePath the site path to the site-map folder
1768     * @param siteChange in case the site was changed
1769     */
1770    public void openSiteMap(String sitePath, boolean siteChange) {
1771
1772        String uri = CmsCoreProvider.get().link(
1773            CmsCoreProvider.get().getUri()) + "?" + CmsCoreData.PARAM_PATH + "=" + sitePath;
1774        if (!siteChange) {
1775            uri += "&" + CmsCoreData.PARAM_RETURNCODE + "=" + getData().getReturnCode();
1776        }
1777        Window.Location.replace(uri);
1778    }
1779
1780    /**
1781     * Recomputes properties for all sitemap entries.<p>
1782     */
1783    public void recomputeProperties() {
1784
1785        CmsClientSitemapEntry root = getData().getRoot();
1786        recomputeProperties(root);
1787    }
1788
1789    /**
1790     * Refreshes the root entry.<p>
1791     *
1792     * @param callback the callback to call after the entry has been refreshed
1793     */
1794    public void refreshRoot(AsyncCallback<Void> callback) {
1795
1796        updateEntry(getData().getRoot().getId(), callback);
1797    }
1798
1799    /**
1800     * @see org.opencms.ade.sitemap.shared.I_CmsSitemapController#registerEntry(org.opencms.ade.sitemap.shared.CmsClientSitemapEntry)
1801     */
1802    public void registerEntry(CmsClientSitemapEntry entry) {
1803
1804        if (m_entriesById.containsKey(entry.getId())) {
1805            CmsClientSitemapEntry oldEntry = m_entriesById.get(entry.getId());
1806            oldEntry.update(entry);
1807            if (oldEntry != m_entriesByPath.get(oldEntry.getSitePath())) {
1808                m_entriesByPath.put(oldEntry.getSitePath(), oldEntry);
1809            }
1810        } else {
1811            m_entriesById.put(entry.getId(), entry);
1812            m_entriesByPath.put(entry.getSitePath(), entry);
1813        }
1814
1815    }
1816
1817    /**
1818     * @see org.opencms.ade.sitemap.shared.I_CmsSitemapController#registerPathChange(org.opencms.ade.sitemap.shared.CmsClientSitemapEntry, java.lang.String)
1819     */
1820    public void registerPathChange(CmsClientSitemapEntry entry, String oldPath) {
1821
1822        m_entriesById.put(entry.getId(), entry);
1823        m_entriesByPath.remove(oldPath);
1824        m_entriesByPath.put(entry.getSitePath(), entry);
1825    }
1826
1827    /**
1828     * Removes the entry with the given site-path from navigation.<p>
1829     *
1830     * @param entryId the entry id
1831     */
1832    public void removeFromNavigation(CmsUUID entryId) {
1833
1834        CmsClientSitemapEntry entry = getEntryById(entryId);
1835        CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(entry.getSitePath()));
1836        CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.remove);
1837        change.setParentId(parent.getId());
1838        change.setDefaultFileId(entry.getDefaultFileId());
1839        CmsSitemapClipboardData data = CmsSitemapView.getInstance().getController().getData().getClipboardData().copy();
1840        data.addModified(entry);
1841        change.setClipBoardData(data);
1842        //TODO: handle detail page delete
1843
1844        commitChange(change, null);
1845    }
1846
1847    /**
1848     * Removes a model page from the sitemap's configuration.<p>
1849     *
1850     * @param id the structure id of the model page
1851     *
1852     * @param asyncCallback the callback to call when done
1853     */
1854    public void removeModelPage(final CmsUUID id, final AsyncCallback<Void> asyncCallback) {
1855
1856        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
1857
1858            @Override
1859            public void execute() {
1860
1861                start(200, true);
1862                getService().removeModelPage(getEntryPoint(), id, this);
1863
1864            }
1865
1866            @Override
1867            protected void onResponse(Void result) {
1868
1869                stop(false);
1870                loadNewElementInfo(null);
1871                asyncCallback.onSuccess(null);
1872
1873            }
1874        };
1875        action.execute();
1876
1877    }
1878
1879    /**
1880     * @see org.opencms.ade.sitemap.shared.I_CmsSitemapController#replaceProperties(org.opencms.util.CmsUUID, java.util.Map)
1881     */
1882    public Map<String, CmsClientProperty> replaceProperties(CmsUUID id, Map<String, CmsClientProperty> properties) {
1883
1884        if ((id == null) || (properties == null)) {
1885            return null;
1886        }
1887        Map<String, CmsClientProperty> props = m_propertyMaps.get(id);
1888        if (props == null) {
1889            props = properties;
1890            m_propertyMaps.put(id, props);
1891        } else {
1892            props.clear();
1893            props.putAll(properties);
1894        }
1895        return props;
1896
1897    }
1898
1899    /**
1900     * Sets the editor mode in the user session.<p>
1901     *
1902     * @param editorMode the editor mode
1903     */
1904    public void setEditorModeInSession(final EditorMode editorMode) {
1905
1906        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
1907
1908            @Override
1909            public void execute() {
1910
1911                getService().setEditorMode(editorMode, this);
1912            }
1913
1914            @Override
1915            protected void onResponse(Void result) {
1916
1917                // nothing to do
1918
1919            }
1920        };
1921        action.execute();
1922    }
1923
1924    /**
1925     * Shows a formerly hidden entry in the navigation.<p>
1926     *
1927     * @see #hideInNavigation(CmsUUID)
1928     *
1929     * @param entryId the entry id
1930     */
1931    public void showInNavigation(CmsUUID entryId) {
1932
1933        CmsClientSitemapEntry entry = getEntryById(entryId);
1934        CmsSitemapChange change = getChangeForEdit(
1935            entry,
1936            Collections.singletonList(
1937                new CmsPropertyModification(
1938                    entryId.toString()
1939                        + "/"
1940                        + CmsClientProperty.PROPERTY_NAVINFO
1941                        + "/"
1942                        + CmsClientProperty.PATH_STRUCTURE_VALUE,
1943                    "")));
1944        commitChange(change, null);
1945    }
1946
1947    /**
1948     * Undeletes the resource with the given structure id.<p>
1949     *
1950     * @param entryId the entry id
1951     * @param sitePath the site-path
1952     */
1953    public void undelete(CmsUUID entryId, String sitePath) {
1954
1955        CmsSitemapChange change = new CmsSitemapChange(entryId, sitePath, ChangeType.undelete);
1956        CmsSitemapClipboardData data = CmsSitemapView.getInstance().getController().getData().getClipboardData().copy();
1957        data.getDeletions().remove(entryId);
1958        change.setClipBoardData(data);
1959        commitChange(change, null);
1960    }
1961
1962    /**
1963     * Updates the given entry.<p>
1964     *
1965     * @param entryId the entry id
1966      */
1967    public void updateEntry(CmsUUID entryId) {
1968
1969        getChildren(entryId, CmsSitemapTreeItem.getItemById(entryId).isOpen(), null);
1970    }
1971
1972    /**
1973     * Updates the given entry.<p>
1974     *
1975     * @param entryId the entry id
1976     * @param callback the callback to call after the entry has been updated
1977      */
1978    public void updateEntry(CmsUUID entryId, final AsyncCallback<Void> callback) {
1979
1980        getChildren(
1981            entryId,
1982            CmsSitemapTreeItem.getItemById(entryId).isOpen(),
1983            new AsyncCallback<CmsClientSitemapEntry>() {
1984
1985                public void onFailure(Throwable caught) {
1986
1987                    // nothing to do
1988
1989                }
1990
1991                public void onSuccess(CmsClientSitemapEntry result) {
1992
1993                    if (callback != null) {
1994                        callback.onSuccess(null);
1995                    }
1996                }
1997            });
1998    }
1999
2000    /**
2001    * Updates the given entry.<p>
2002    *
2003    * @param sitePath the entry sitepath
2004     */
2005    public void updateEntry(String sitePath) {
2006
2007        CmsClientSitemapEntry entry = getEntry(sitePath);
2008        if ((entry != null) && (CmsSitemapTreeItem.getItemById(entry.getId()) != null)) {
2009            getChildren(entry.getId(), CmsSitemapTreeItem.getItemById(entry.getId()).isOpen(), null);
2010        }
2011    }
2012
2013    /**
2014     * Updates the given entry only, not evaluating any child changes.<p>
2015     *
2016     * @param entryId the entry id
2017     */
2018    public void updateSingleEntry(final CmsUUID entryId) {
2019
2020        CmsRpcAction<CmsClientSitemapEntry> getChildrenAction = new CmsRpcAction<CmsClientSitemapEntry>() {
2021
2022            /**
2023            * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2024            */
2025            @Override
2026            public void execute() {
2027
2028                getService().getChildren(getEntryPoint(), entryId, 0, this);
2029            }
2030
2031            /**
2032            * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2033            */
2034            @Override
2035            public void onResponse(CmsClientSitemapEntry result) {
2036
2037                CmsClientSitemapEntry target = getEntryById(entryId);
2038                if (target == null) {
2039                    // this might happen after an automated deletion
2040                    stop(false);
2041                    return;
2042                }
2043                target.update(result);
2044                CmsSitemapTreeItem item = CmsSitemapTreeItem.getItemById(target.getId());
2045                item.updateEntry(target);
2046            }
2047        };
2048        getChildrenAction.execute();
2049    }
2050
2051    /**
2052     * Fires a sitemap change event.<p>
2053     *
2054     * @param change the change event to fire
2055     */
2056    protected void applyChange(CmsSitemapChange change) {
2057
2058        // update the clip board data
2059        if (change.getClipBoardData() != null) {
2060            getData().getClipboardData().setDeletions(change.getClipBoardData().getDeletions());
2061            getData().getClipboardData().setModifications(change.getClipBoardData().getModifications());
2062        }
2063        switch (change.getChangeType()) {
2064            case bumpDetailPage:
2065                CmsDetailPageTable detailPageTable = getData().getDetailPageTable();
2066                if (detailPageTable.contains(change.getEntryId())) {
2067                    detailPageTable.makeDefault(change.getEntryId());
2068                }
2069                break;
2070            case clipboardOnly:
2071                // nothing to do
2072                break;
2073            case remove:
2074                CmsClientSitemapEntry entry = getEntryById(change.getEntryId());
2075                entry.setInNavigation(false);
2076                CmsPropertyModification propMod = new CmsPropertyModification(
2077                    entry.getId(),
2078                    CmsClientProperty.PROPERTY_NAVTEXT,
2079                    null,
2080                    true);
2081                executePropertyModification(propMod);
2082                propMod = new CmsPropertyModification(entry.getId(), CmsClientProperty.PROPERTY_NAVTEXT, null, true);
2083                executePropertyModification(propMod);
2084                entry.normalizeProperties();
2085                break;
2086            case undelete:
2087                updateEntry(change.getParentId());
2088                break;
2089            case create:
2090                CmsClientSitemapEntry newEntry = change.getUpdatedEntry();
2091                getEntryById(change.getParentId()).insertSubEntry(newEntry, change.getPosition(), this);
2092                newEntry.initializeAll(this);
2093                if (isDetailPage(newEntry)) {
2094                    CmsDetailPageInfo info = getDetailPageInfo(newEntry.getId());
2095                    if (info == null) {
2096                        info = new CmsDetailPageInfo(
2097                            newEntry.getId(),
2098                            newEntry.getSitePath(),
2099                            newEntry.getDetailpageTypeName(),
2100                            /* qualifier = */null,
2101                            newEntry.getVfsModeIcon());
2102                    }
2103                    addDetailPageInfo(info);
2104                }
2105                break;
2106            case delete:
2107                removeEntry(change.getEntryId(), change.getParentId());
2108                break;
2109            case modify:
2110                if (change.hasNewParent() || change.hasChangedPosition()) {
2111                    CmsClientSitemapEntry moved = getEntryById(change.getEntryId());
2112                    String oldSitepath = moved.getSitePath();
2113                    CmsClientSitemapEntry sourceParent = getEntry(CmsResource.getParentFolder(oldSitepath));
2114                    sourceParent.removeSubEntry(moved.getId());
2115                    CmsClientSitemapEntry destParent = change.hasNewParent()
2116                    ? getEntryById(change.getParentId())
2117                    : sourceParent;
2118                    if (change.getPosition() < destParent.getSubEntries().size()) {
2119                        destParent.insertSubEntry(moved, change.getPosition(), this);
2120                    } else {
2121                        // inserting as last entry of the parent list
2122                        destParent.addSubEntry(moved, this);
2123                    }
2124                    if (change.hasNewParent()) {
2125                        cleanupOldPaths(oldSitepath, destParent.getSitePath());
2126                    }
2127                }
2128                if (change.hasChangedName()) {
2129                    CmsClientSitemapEntry changed = getEntryById(change.getEntryId());
2130                    String oldSitepath = changed.getSitePath();
2131                    String parentPath = CmsResource.getParentFolder(oldSitepath);
2132                    String newSitepath = CmsStringUtil.joinPaths(parentPath, change.getName());
2133                    changed.updateSitePath(newSitepath, this);
2134                    cleanupOldPaths(oldSitepath, newSitepath);
2135                }
2136                if (change.hasChangedProperties()) {
2137                    for (CmsPropertyModification modification : change.getPropertyChanges()) {
2138                        executePropertyModification(modification);
2139                    }
2140                    getEntryById(change.getEntryId()).normalizeProperties();
2141                }
2142                if (change.getUpdatedEntry() != null) {
2143                    CmsClientSitemapEntry oldEntry = getEntryById(change.getEntryId());
2144                    CmsClientSitemapEntry parent = getEntry(CmsResource.getParentFolder(oldEntry.getSitePath()));
2145                    removeEntry(change.getEntryId(), parent.getId());
2146                    parent.insertSubEntry(change.getUpdatedEntry(), oldEntry.getPosition(), this);
2147                    change.getUpdatedEntry().initializeAll(this);
2148                }
2149                break;
2150            default:
2151        }
2152        if (change.getChangeType() != ChangeType.delete) {
2153            recomputeProperties();
2154        }
2155        m_eventBus.fireEventFromSource(new CmsSitemapChangeEvent(change), this);
2156    }
2157
2158    /**
2159    * Adds a change to the queue.<p>
2160    *
2161    * @param change the change to commit
2162    * @param callback the callback to execute after the change has been applied
2163    */
2164    protected void commitChange(final CmsSitemapChange change, final Command callback) {
2165
2166        if (change != null) {
2167            // save the sitemap
2168            CmsRpcAction<CmsSitemapChange> saveAction = new CmsRpcAction<CmsSitemapChange>() {
2169
2170                /**
2171                * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
2172                */
2173                @Override
2174                public void execute() {
2175
2176                    start(0, true);
2177                    getService().save(getEntryPoint(), change, this);
2178
2179                }
2180
2181                @Override
2182                public void onFailure(Throwable t) {
2183                    // An error after a drag/drop operation can cause click events to get sent to the sitemap entry's move handle
2184                    // instead of the error dialog's buttons. We use releaseCapture() to fix this.
2185                    DOM.releaseCapture(DOM.getCaptureElement());
2186                    super.onFailure(t);
2187                }
2188
2189                /**
2190                * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
2191                */
2192                @Override
2193                public void onResponse(CmsSitemapChange result) {
2194
2195                    stop(false);
2196
2197                    applyChange(result);
2198                    if (callback != null) {
2199                        callback.execute();
2200                    }
2201                }
2202            };
2203            saveAction.execute();
2204        }
2205    }
2206
2207    /**
2208     * Creates a change object for an edit operation.<p>
2209     *
2210     * @param entry the edited sitemap entry
2211     * @param propertyChanges the list of property changes
2212     *
2213     * @return the change object
2214     */
2215    protected CmsSitemapChange getChangeForEdit(
2216        CmsClientSitemapEntry entry,
2217        List<CmsPropertyModification> propertyChanges) {
2218
2219        CmsSitemapChange change = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify);
2220        change.setDefaultFileId(entry.getDefaultFileId());
2221        change.setLeafType(entry.isLeafType());
2222        List<CmsPropertyModification> propertyChangeData = new ArrayList<CmsPropertyModification>();
2223        for (CmsPropertyModification propChange : propertyChanges) {
2224            propertyChangeData.add(propChange);
2225        }
2226        change.setPropertyChanges(propertyChangeData);
2227
2228        CmsSitemapClipboardData data = getData().getClipboardData().copy();
2229        data.addModified(entry);
2230        change.setClipBoardData(data);
2231        return change;
2232    }
2233
2234    /**
2235     * Returns the URI of the current sitemap.<p>
2236     *
2237     * @return the URI of the current sitemap
2238     */
2239    protected String getEntryPoint() {
2240
2241        return m_data.getRoot().getSitePath();
2242
2243    }
2244
2245    /**
2246     * Helper method for getting the full path of a sitemap entry whose URL name is being edited.<p>
2247     *
2248     * @param entry the sitemap entry
2249     * @param newUrlName the new url name of the sitemap entry
2250     *
2251     * @return the new full site path of the sitemap entry
2252     */
2253    protected String getPath(CmsClientSitemapEntry entry, String newUrlName) {
2254
2255        if (newUrlName.equals("")) {
2256            return entry.getSitePath();
2257        }
2258        return CmsResource.getParentFolder(entry.getSitePath()) + newUrlName + "/";
2259    }
2260
2261    /**
2262     * Returns the sitemap service instance.<p>
2263     *
2264     * @return the sitemap service instance
2265     */
2266    protected I_CmsVfsServiceAsync getVfsService() {
2267
2268        if (m_vfsService == null) {
2269            m_vfsService = CmsCoreProvider.getVfsService();
2270        }
2271        return m_vfsService;
2272    }
2273
2274    /**
2275     * Initializes the detail page information.<p>
2276     */
2277    protected void initDetailPageInfos() {
2278
2279        for (CmsUUID id : m_data.getDetailPageTable().getAllIds()) {
2280            CmsDetailPageInfo info = m_data.getDetailPageTable().get(id);
2281            m_allDetailPageInfos.put(id, info);
2282        }
2283    }
2284
2285    /**
2286     * Creates a new client sitemap entry bean to use for the RPC call which actually creates the entry on the server side.<p>
2287     *
2288     * @param parent the parent entry
2289     * @param callback the callback to execute
2290     */
2291    protected void makeNewEntry(
2292        final CmsClientSitemapEntry parent,
2293        final I_CmsSimpleCallback<CmsClientSitemapEntry> callback) {
2294
2295        ensureUniqueName(parent, NEW_ENTRY_NAME, new I_CmsSimpleCallback<String>() {
2296
2297            public void execute(String urlName) {
2298
2299                CmsClientSitemapEntry newEntry = new CmsClientSitemapEntry();
2300                //newEntry.setTitle(urlName);
2301                newEntry.setName(urlName);
2302                String sitePath = parent.getSitePath() + urlName + "/";
2303                newEntry.setSitePath(sitePath);
2304                newEntry.setVfsPath(null);
2305                newEntry.setPosition(0);
2306                newEntry.setNew(true);
2307                newEntry.setInNavigation(true);
2308                newEntry.setResourceTypeName("folder");
2309                newEntry.getOwnProperties().put(
2310                    CmsClientProperty.PROPERTY_TITLE,
2311                    new CmsClientProperty(CmsClientProperty.PROPERTY_TITLE, NEW_ENTRY_NAME, NEW_ENTRY_NAME));
2312                callback.execute(newEntry);
2313            }
2314        });
2315
2316    }
2317
2318    /**
2319     * Recomputes the properties for a client sitemap entry.<p>
2320     *
2321     * @param entry the entry for whose descendants the properties should be recomputed
2322     */
2323    protected void recomputeProperties(CmsClientSitemapEntry entry) {
2324
2325        for (I_CmsPropertyUpdateHandler handler : m_propertyUpdateHandlers) {
2326            handler.handlePropertyUpdate(entry);
2327        }
2328        for (CmsClientSitemapEntry child : entry.getSubEntries()) {
2329            recomputeProperties(child);
2330        }
2331    }
2332
2333    /**
2334     * Store the gallery type information.<p>
2335     *
2336     * @param galleryTypes the gallery types
2337     */
2338    void storeGalleryTypes(Collection<CmsGalleryType> galleryTypes) {
2339
2340        m_galleryTypes.clear();
2341        for (CmsGalleryType type : galleryTypes) {
2342            m_galleryTypes.put(Integer.valueOf(type.getTypeId()), type);
2343        }
2344    }
2345
2346    /**
2347     * Cleans up wrong path references.<p>
2348     *
2349     * @param oldSitepath the old sitepath
2350     * @param newSitepath the new sitepath
2351     */
2352    private void cleanupOldPaths(String oldSitepath, String newSitepath) {
2353
2354        // use a separate list to avoid concurrent changes
2355        List<CmsClientSitemapEntry> entries = new ArrayList<CmsClientSitemapEntry>(m_entriesById.values());
2356        for (CmsClientSitemapEntry entry : entries) {
2357            if (entry.getSitePath().startsWith(oldSitepath)) {
2358                String currentPath = entry.getSitePath();
2359                String partAfterOldSitePath = currentPath.substring(oldSitepath.length());
2360
2361                String updatedSitePath = "".equals(partAfterOldSitePath) ? newSitepath : CmsStringUtil.joinPaths(
2362                    newSitepath,
2363                    partAfterOldSitePath);
2364                entry.updateSitePath(updatedSitePath, this);
2365            }
2366        }
2367    }
2368
2369    /**
2370     * Returns the properties above the sitemap root.<p>
2371     *
2372     * @return the map of properties of the root's parent
2373     */
2374    private Map<String, CmsClientProperty> getParentProperties() {
2375
2376        return m_data.getParentProperties();
2377    }
2378
2379    /**
2380     * Checks if the given path and position indicate a changed position for the entry.<p>
2381     *
2382     * @param entry the sitemap entry to move
2383     * @param toPath the destination path
2384     * @param position the new position between its siblings
2385     *
2386     * @return <code>true</code> if this is a position change
2387     */
2388    private boolean isChangedPosition(CmsClientSitemapEntry entry, String toPath, int position) {
2389
2390        return (!entry.getSitePath().equals(toPath) || (entry.getPosition() != position));
2391    }
2392
2393    /**
2394     * Validates the entry and the given path.<p>
2395     *
2396     * @param entry the entry
2397     * @param toPath the path
2398     *
2399     * @return <code>true</code> if entry and path are valid
2400     */
2401    private boolean isValidEntryAndPath(CmsClientSitemapEntry entry, String toPath) {
2402
2403        return ((toPath != null)
2404            && (CmsResource.getParentFolder(toPath) != null)
2405            && (entry != null)
2406            && (getEntry(CmsResource.getParentFolder(toPath)) != null)
2407            && (getEntry(entry.getSitePath()) != null));
2408    }
2409
2410    /**
2411     * Removes all children of the given entry recursively from the data model.<p>
2412     *
2413     * @param entry the entry
2414     */
2415    private void removeAllChildren(CmsClientSitemapEntry entry) {
2416
2417        for (CmsClientSitemapEntry child : entry.getSubEntries()) {
2418            removeAllChildren(child);
2419            m_entriesById.remove(child.getId());
2420            m_entriesByPath.remove(child.getSitePath());
2421            // apply to detailpage table
2422            CmsDetailPageTable detailPageTable = getData().getDetailPageTable();
2423            if (detailPageTable.contains(child.getId())) {
2424                detailPageTable.remove(child.getId());
2425            }
2426        }
2427        entry.getSubEntries().clear();
2428    }
2429
2430    /**
2431     * Removes the given entry and it's descendants from the modified list.<p>
2432     *
2433     * @param entry the entry
2434     * @param data the clip board data
2435     */
2436    private void removeDeletedFromModified(CmsClientSitemapEntry entry, CmsSitemapClipboardData data) {
2437
2438        data.removeModified(entry);
2439        for (CmsClientSitemapEntry child : entry.getSubEntries()) {
2440            removeDeletedFromModified(child, data);
2441        }
2442    }
2443
2444    /**
2445     * Removes the given entry from the data model.<p>
2446     *
2447     * @param entryId the id of the entry to remove
2448     * @param parentId the parent entry id
2449     */
2450    private void removeEntry(CmsUUID entryId, CmsUUID parentId) {
2451
2452        CmsClientSitemapEntry entry = getEntryById(entryId);
2453        CmsClientSitemapEntry deleteParent = getEntryById(parentId);
2454        removeAllChildren(entry);
2455        deleteParent.removeSubEntry(entry.getPosition());
2456        m_entriesById.remove(entryId);
2457        m_entriesByPath.remove(entry.getSitePath());
2458        // apply to detailpage table
2459        CmsDetailPageTable detailPageTable = getData().getDetailPageTable();
2460        if (detailPageTable.contains(entryId)) {
2461            detailPageTable.remove(entryId);
2462        }
2463    }
2464}