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