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.ui.components.fileselect;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.main.CmsException;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036import org.opencms.site.CmsSite;
037import org.opencms.ui.A_CmsUI;
038import org.opencms.ui.CmsVaadinUtils;
039import org.opencms.ui.components.CmsResourceTableProperty;
040import org.opencms.util.CmsStringUtil;
041import org.opencms.util.CmsUUID;
042import org.opencms.workplace.CmsWorkplace;
043
044import java.util.Arrays;
045import java.util.Collections;
046import java.util.List;
047
048import org.apache.commons.logging.Log;
049
050import com.google.common.collect.Lists;
051import com.vaadin.ui.Component;
052import com.vaadin.ui.CustomComponent;
053import com.vaadin.v7.data.Container;
054import com.vaadin.v7.data.Property.ValueChangeEvent;
055import com.vaadin.v7.data.Property.ValueChangeListener;
056import com.vaadin.v7.data.util.IndexedContainer;
057import com.vaadin.v7.shared.ui.combobox.FilteringMode;
058import com.vaadin.v7.ui.ComboBox;
059
060/**
061 * Dialog with a site selector and file tree which can be used to select resources.<p>
062 */
063public class CmsResourceSelectDialog extends CustomComponent {
064
065    /**
066     * Class for site select options.<p>
067     */
068    public static class Options {
069
070        /**Indexed container.*/
071        private IndexedContainer m_siteSelectionContainer;
072
073        /**
074         * Returns the siteSelectionContainer.<p>
075         *
076         * @return the siteSelectionContainer
077         */
078        public IndexedContainer getSiteSelectionContainer() {
079
080            return m_siteSelectionContainer;
081        }
082
083        /**
084         * Sets the siteSelectionContainer.<p>
085         *
086         * @param siteSelectionContainer the siteSelectionContainer to set
087         */
088        public void setSiteSelectionContainer(IndexedContainer siteSelectionContainer) {
089
090            m_siteSelectionContainer = siteSelectionContainer;
091        }
092
093    }
094
095    /**
096     * Converts resource selection to path (string) selection - either as root paths or site paths.<p>
097     */
098    class PathSelectionAdapter implements I_CmsSelectionHandler<CmsResource> {
099
100        /** The wrapped string selection handler. */
101        private I_CmsSelectionHandler<String> m_pathHandler;
102
103        /** If true, pass site paths to the wrapped path handler, else root paths. */
104        private boolean m_useSitePaths;
105
106        /**
107         * Creates a new instance.<p>
108         *
109         * @param pathHandler the selection handler to call
110         * @param useSitePaths true if we want changes as site paths
111         */
112        public PathSelectionAdapter(I_CmsSelectionHandler<String> pathHandler, boolean useSitePaths) {
113
114            m_pathHandler = pathHandler;
115            m_useSitePaths = useSitePaths;
116        }
117
118        /**
119         * @see org.opencms.ui.components.fileselect.I_CmsSelectionHandler#onSelection(java.lang.Object)
120         */
121        @SuppressWarnings("synthetic-access")
122        public void onSelection(CmsResource selected) {
123
124            String path = selected.getRootPath();
125            if (m_useSitePaths) {
126                try {
127                    CmsObject cms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
128                    cms.getRequestContext().setSiteRoot(m_siteRoot);
129                    path = cms.getRequestContext().removeSiteRoot(path);
130                } catch (CmsException e) {
131                    LOG.error(e.getLocalizedMessage(), e);
132
133                }
134            }
135            m_pathHandler.onSelection(path);
136        }
137    }
138
139    /** The property used for the site caption. */
140    public static final String PROPERTY_SITE_CAPTION = "caption";
141
142    /** Logger instance for this class. */
143    private static final Log LOG = CmsLog.getLog(CmsResourceSelectDialog.class);
144
145    /** Serial version id. */
146    private static final long serialVersionUID = 1L;
147
148    /** The CMS context. */
149    protected CmsObject m_currentCms;
150
151    /** The resource filter. */
152    protected CmsResourceFilter m_filter;
153
154    /** The resource initially displayed at the root of the tree. */
155    protected CmsResource m_root;
156
157    /** The file tree (wrapped in an array, because Vaadin Declarative tries to bind it otherwise) .*/
158    private CmsResourceTreeTable m_fileTree;
159
160    /** Boolean flag indicating whether the tree is currently filtered. */
161    private boolean m_isSitemapView = true;
162
163    /** The site root. */
164    private String m_siteRoot;
165
166    /** Contains the data for the tree. */
167    private CmsResourceTreeContainer m_treeData;
168
169    /**
170     * Creates a new instance.<p>
171     *
172     * @param filter the resource filter to use
173     *
174     * @throws CmsException if something goes wrong
175     */
176    public CmsResourceSelectDialog(CmsResourceFilter filter)
177    throws CmsException {
178
179        this(filter, A_CmsUI.getCmsObject());
180    }
181
182    /**
183     * public constructor with given CmsObject.<p>
184     *
185     * @param filter filter the resource filter to use
186     * @param cms CmsObejct to use
187     * @throws CmsException if something goes wrong
188     */
189    public CmsResourceSelectDialog(CmsResourceFilter filter, CmsObject cms)
190    throws CmsException {
191
192        this(filter, cms, new Options());
193    }
194
195    /**
196     * public constructor.<p>
197     *
198     * @param filter resource filter
199     * @param cms CmsObject
200     * @param options options
201     * @throws CmsException exception
202     */
203    public CmsResourceSelectDialog(CmsResourceFilter filter, CmsObject cms, Options options)
204    throws CmsException {
205
206        m_filter = filter;
207        setCompositionRoot(new CmsResourceSelectDialogContents());
208        IndexedContainer container = options.getSiteSelectionContainer() != null
209        ? options.getSiteSelectionContainer()
210        : CmsVaadinUtils.getAvailableSitesContainer(cms, PROPERTY_SITE_CAPTION);
211        getSiteSelector().setContainerDataSource(container);
212
213        if (!cms.existsResource("/", CmsResourceFilter.IGNORE_EXPIRATION)) {
214            cms = OpenCms.initCmsObject(cms);
215            cms.getRequestContext().setSiteRoot("/system/");
216        }
217        m_siteRoot = cms.getRequestContext().getSiteRoot();
218
219        getSiteSelector().setValue(
220            CmsVaadinUtils.getPathItemId(getSiteSelector().getContainerDataSource(), m_siteRoot));
221        getSiteSelector().setNullSelectionAllowed(false);
222        getSiteSelector().setItemCaptionPropertyId(PROPERTY_SITE_CAPTION);
223        getSiteSelector().setFilteringMode(FilteringMode.CONTAINS);
224        getSiteSelector().addValueChangeListener(new ValueChangeListener() {
225
226            /** Serial version id. */
227            private static final long serialVersionUID = 1L;
228
229            public void valueChange(ValueChangeEvent event) {
230
231                String site = (String)(event.getProperty().getValue());
232                onSiteChange(site);
233            }
234        });
235
236        CmsResource root = cms.readResource("/");
237        m_fileTree = createTree(cms, root);
238        m_fileTree.setColumnExpandRatio(CmsResourceTreeTable.CAPTION_FOLDERS, 5);
239        m_fileTree.setColumnExpandRatio(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT, 1);
240        m_treeData = m_fileTree.getTreeContainer();
241        updateRoot(cms, root);
242
243        getContents().getTreeContainer().addComponent(m_fileTree);
244        ((Component)m_fileTree).setSizeFull();
245        updateView();
246    }
247
248    /**
249     * Adds a resource selection handler.<p>
250     *
251     * @param handler the handler
252     */
253    public void addSelectionHandler(I_CmsSelectionHandler<CmsResource> handler) {
254
255        m_fileTree.addResourceSelectionHandler(handler);
256    }
257
258    /**
259     * Disables the option to select resources from other sites.<p>
260     */
261    public void disableSiteSwitch() {
262
263        getSiteSelector().setEnabled(false);
264    }
265
266    /**
267     * Opens the given path.<p>
268     *
269     * @param path the path to open
270     */
271    public void openPath(String path) {
272
273        if (!CmsStringUtil.isPrefixPath(m_root.getRootPath(), path)) {
274            CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(path);
275            if (site != null) {
276                // the given path is a root path switch to the determined site
277                getSiteSelector().setValue(site.getSiteRoot());
278                path = m_currentCms.getRequestContext().removeSiteRoot(path);
279            } else if (OpenCms.getSiteManager().startsWithShared(path)) {
280                getSiteSelector().setValue(OpenCms.getSiteManager().getSharedFolder());
281            } else if (path.startsWith(CmsWorkplace.VFS_PATH_SYSTEM)) {
282                Container container = getSiteSelector().getContainerDataSource();
283                String newSiteRoot = null;
284                for (String possibleSiteRoot : Arrays.asList("", "/", "/system", "/system/")) {
285                    if (container.containsId(possibleSiteRoot)) {
286                        newSiteRoot = possibleSiteRoot;
287                        break;
288                    }
289                }
290                if (newSiteRoot == null) {
291                    LOG.warn(
292                        "Couldn't open path in site selector because neither root site nor system folder are in the site selector. path="
293                            + path);
294                    return;
295                }
296                getSiteSelector().setValue(newSiteRoot);
297            }
298        }
299        if (!"/".equals(path)) {
300            List<CmsUUID> idsToOpen = Lists.newArrayList();
301            try {
302                CmsResource currentFolder = m_currentCms.readResource(CmsResource.getParentFolder(path));
303                if (!m_root.getStructureId().equals(currentFolder.getStructureId())) {
304                    idsToOpen.add(currentFolder.getStructureId());
305                    CmsResource parentFolder = null;
306
307                    do {
308                        try {
309                            parentFolder = m_currentCms.readParentFolder(currentFolder.getStructureId());
310                            idsToOpen.add(parentFolder.getStructureId());
311                            currentFolder = parentFolder;
312                        } catch (CmsException | NullPointerException e) {
313                            LOG.info(e.getLocalizedMessage(), e);
314                            break;
315                        }
316                    } while (!parentFolder.getStructureId().equals(m_root.getStructureId()));
317                    // we need to iterate from "top" to "bottom", so we reverse the list of folders
318                    Collections.reverse(idsToOpen);
319
320                    for (CmsUUID id : idsToOpen) {
321                        m_fileTree.expandItem(id);
322                    }
323                }
324            } catch (CmsException e) {
325                LOG.debug("Can not read parent folder of current path.", e);
326            }
327        }
328    }
329
330    /**
331     * Switches between the folders and sitemap view of the tree.<p>
332     *
333     * @param showSitemapView <code>true</code> to show the sitemap view
334     */
335    public void showSitemapView(boolean showSitemapView) {
336
337        if (m_isSitemapView != showSitemapView) {
338            m_isSitemapView = showSitemapView;
339            updateView();
340        }
341    }
342
343    /**
344     * Displays the start resource by opening all nodes in the tree leading to it.<p>
345     *
346     * @param startResource the resource which should be shown in the tree
347     */
348    public void showStartResource(CmsResource startResource) {
349
350        openPath(startResource.getRootPath());
351    }
352
353    /**
354     * Creates the resource tree for the given root.<p>
355     *
356     * @param cms the CMS context
357     * @param root the root resource
358     * @return the resource tree
359     */
360    protected CmsResourceTreeTable createTree(CmsObject cms, CmsResource root) {
361
362        return new CmsResourceTreeTable(cms, root, m_filter);
363    }
364
365    /**
366     * Gets the content panel of this dialog.<p>
367     *
368     * @return content panel of this dialog
369     */
370    protected CmsResourceSelectDialogContents getContents() {
371
372        return ((CmsResourceSelectDialogContents)getCompositionRoot());
373    }
374
375    /**
376     * Gets the file tree.<p>
377     *
378     * @return the file tree
379     */
380    protected CmsResourceTreeTable getFileTree() {
381
382        return m_fileTree;
383    }
384
385    /**
386     * Called when the user changes the site.<p>
387     *
388     * @param site the new site root
389     */
390    protected void onSiteChange(String site) {
391
392        try {
393            m_treeData.removeAllItems();
394            CmsObject rootCms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
395            rootCms.getRequestContext().setSiteRoot("");
396            CmsResource siteRootResource = rootCms.readResource(site);
397
398            m_treeData.initRoot(rootCms, siteRootResource);
399            m_fileTree.expandItem(siteRootResource.getStructureId());
400            m_siteRoot = site;
401            updateRoot(rootCms, siteRootResource);
402        } catch (CmsException e) {
403            LOG.error(e.getLocalizedMessage(), e);
404        }
405    }
406
407    /**
408     * Updates the current site root resource.<p>
409     *
410     * @param rootCms the CMS context
411     * @param siteRootResource the resource corresponding to a site root
412     */
413    protected void updateRoot(CmsObject rootCms, CmsResource siteRootResource) {
414
415        m_root = siteRootResource;
416        m_currentCms = rootCms;
417        updateView();
418    }
419
420    /**
421     * Updates the filtering state.<p>
422     */
423    protected void updateView() {
424
425        m_fileTree.showSitemapView(m_isSitemapView);
426    }
427
428    /**
429     * Gets the site selector.<p>
430     *
431     * @return the site selector
432     */
433    private ComboBox getSiteSelector() {
434
435        return getContents().getSiteSelector();
436    }
437
438}