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