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.apps.search;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.main.CmsException;
033import org.opencms.main.OpenCms;
034import org.opencms.ui.A_CmsUI;
035import org.opencms.ui.CmsVaadinUtils;
036import org.opencms.ui.FontOpenCms;
037import org.opencms.ui.I_CmsDialogContext;
038import org.opencms.ui.I_CmsDialogContext.ContextType;
039import org.opencms.ui.apps.A_CmsWorkplaceApp;
040import org.opencms.ui.apps.CmsAppWorkplaceUi;
041import org.opencms.ui.apps.CmsFileExplorer;
042import org.opencms.ui.apps.I_CmsAppUIContext;
043import org.opencms.ui.apps.I_CmsCachableApp;
044import org.opencms.ui.apps.I_CmsContextProvider;
045import org.opencms.ui.apps.Messages;
046import org.opencms.ui.apps.projects.CmsProjectManagerConfiguration;
047import org.opencms.ui.apps.search.CmsSourceSearchForm.SearchType;
048import org.opencms.ui.components.CmsErrorDialog;
049import org.opencms.ui.components.CmsFileTable;
050import org.opencms.ui.components.CmsFileTableDialogContext;
051import org.opencms.ui.report.CmsReportOverlay;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.util.CmsUUID;
054
055import java.io.ByteArrayInputStream;
056import java.text.SimpleDateFormat;
057import java.util.Collections;
058import java.util.Date;
059import java.util.HashSet;
060import java.util.LinkedHashMap;
061import java.util.List;
062import java.util.Set;
063
064import org.apache.commons.codec.DecoderException;
065import org.apache.commons.codec.net.URLCodec;
066
067import com.vaadin.server.Sizeable.Unit;
068import com.vaadin.server.StreamResource;
069import com.vaadin.ui.Component;
070import com.vaadin.ui.HorizontalSplitPanel;
071import com.vaadin.ui.UI;
072import com.vaadin.ui.themes.ValoTheme;
073import com.vaadin.v7.event.FieldEvents.TextChangeEvent;
074import com.vaadin.v7.event.FieldEvents.TextChangeListener;
075import com.vaadin.v7.ui.TextField;
076import com.vaadin.v7.ui.VerticalLayout;
077
078/**
079 * The source search app.<p>
080 */
081public class CmsSourceSearchApp extends A_CmsWorkplaceApp implements I_CmsCachableApp {
082
083    /** The folder key. */
084    public static final String FOLDER = "f";
085
086    /** The ignore subsites key. */
087    public static final String IGNORE_SUBSITES = "igss";
088
089    /** The index key. */
090    public static final String INDEX = "i";
091
092    /** The locale key. */
093    public static final String LOCALE = "l";
094
095    /** The project ley. */
096    public static final String PROJECT = "p";
097
098    /** The property key. */
099    public static final String PROPERTY = "pr";
100
101    /** The query key. */
102    public static final String QUERY = "q";
103
104    /** The replace pattern key. */
105    public static final String REPLACE_PATTERN = "rp";
106
107    /** The resource type key. */
108    public static final String RESOURCE_TYPE = "rt";
109
110    /** The search pattern key. */
111    public static final String SEARCH_PATTERN = "sp";
112
113    /** The type key. */
114    public static final String SEARCH_TYPE = "t";
115
116    /** The site root key. */
117    public static final String SITE_ROOT = "s";
118
119    /** The XPath key. */
120    public static final String XPATH = "x";
121
122    /** The serial version id. */
123    private static final long serialVersionUID = 4675966043824229258L;
124
125    /** The results file table. */
126    CmsFileTable m_resultTable;
127
128    /** The currently selected result list resources. */
129    private List<CmsResource> m_currentResources;
130
131    /** The current state string. */
132    private String m_currentState;
133
134    /**Layout showing empty result message.*/
135    private VerticalLayout m_infoEmptyResult;
136
137    /**Layout showing introduction message.*/
138    private VerticalLayout m_infoIntroLayout;
139
140    /** The current search report. */
141    private CmsReportOverlay m_report;
142
143    /** The result table filter input. */
144    private TextField m_resultTableFilter;
145
146    /** The search form. */
147    private CmsSourceSearchForm m_searchForm;
148
149    /** The search and replace thread. */
150    private CmsSearchReplaceThread m_thread;
151
152    /**
153     * Generates the state string for the given search settings.<p>
154     *
155     * @param settings the search settings
156     *
157     * @return the state string
158     */
159    public static String generateState(CmsSearchReplaceSettings settings) {
160
161        String state = "";
162        state = A_CmsWorkplaceApp.addParamToState(state, SITE_ROOT, settings.getSiteRoot());
163        state = A_CmsWorkplaceApp.addParamToState(state, SEARCH_TYPE, settings.getType().name());
164        state = A_CmsWorkplaceApp.addParamToState(state, SEARCH_PATTERN, settings.getSearchpattern());
165        if (!settings.getPaths().isEmpty()) {
166            state = A_CmsWorkplaceApp.addParamToState(state, FOLDER, settings.getPaths().get(0));
167        }
168        state = A_CmsWorkplaceApp.addParamToState(state, RESOURCE_TYPE, settings.getTypes());
169        state = A_CmsWorkplaceApp.addParamToState(state, LOCALE, settings.getLocale());
170        state = A_CmsWorkplaceApp.addParamToState(state, QUERY, settings.getQuery());
171        state = A_CmsWorkplaceApp.addParamToState(state, INDEX, settings.getSource());
172        state = A_CmsWorkplaceApp.addParamToState(state, XPATH, settings.getXpath());
173        state = A_CmsWorkplaceApp.addParamToState(state, IGNORE_SUBSITES, String.valueOf(settings.ignoreSubSites()));
174        state = A_CmsWorkplaceApp.addParamToState(state, PROPERTY, settings.getProperty().getName());
175
176        return state;
177    }
178
179    /**
180     * Returns the settings for the given state.<p>
181     *
182     * @param state the state
183     *
184     * @return the search settings
185     */
186    static CmsSearchReplaceSettings getSettingsFromState(String state) {
187
188        try {
189            state = new URLCodec().decode(state);
190        } catch (DecoderException e1) {
191            //
192        }
193        CmsSearchReplaceSettings settings = null;
194        String typeString = A_CmsWorkplaceApp.getParamFromState(state, SEARCH_TYPE);
195        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(typeString)) {
196            SearchType type = SearchType.valueOf(typeString);
197            settings = new CmsSearchReplaceSettings();
198            settings.setType(type);
199            settings.setIgnoreSubSites(
200                Boolean.parseBoolean(A_CmsWorkplaceApp.getParamFromState(state, IGNORE_SUBSITES)));
201            settings.setSiteRoot(A_CmsWorkplaceApp.getParamFromState(state, SITE_ROOT).replace("%2F", "/"));
202            settings.setPaths(
203                Collections.singletonList(A_CmsWorkplaceApp.getParamFromState(state, FOLDER).replace("%2F", "/")));
204            String resType = A_CmsWorkplaceApp.getParamFromState(state, RESOURCE_TYPE);
205            if (resType != null) {
206                settings.setTypes(resType);
207            }
208            String project = A_CmsWorkplaceApp.getParamFromState(state, PROJECT);
209            if (project != null) {
210                settings.setProject(project);
211            }
212            settings.setSearchpattern(A_CmsWorkplaceApp.getParamFromState(state, SEARCH_PATTERN).replace("%2F", "/"));
213            if (type.isContentValuesOnly()) {
214                settings.setOnlyContentValues(true);
215                String locale = A_CmsWorkplaceApp.getParamFromState(state, LOCALE);
216                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(locale)) {
217                    settings.setLocale(locale);
218                }
219                settings.setXpath(A_CmsWorkplaceApp.getParamFromState(state, XPATH).replace("%2F", "/"));
220            }
221            if (type.isSolrSearch()) {
222                settings.setQuery(A_CmsWorkplaceApp.getParamFromState(state, QUERY).replace("%2F", "/"));
223                settings.setSource(A_CmsWorkplaceApp.getParamFromState(state, INDEX));
224            }
225            if (type.isPropertySearch()) {
226                try {
227                    settings.setProperty(
228                        A_CmsUI.getCmsObject().readPropertyDefinition(
229                            A_CmsWorkplaceApp.getParamFromState(state, PROPERTY)));
230                } catch (CmsException e) {
231                    //
232                }
233            }
234        }
235        return settings;
236    }
237
238    /**
239     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext)
240     */
241    @Override
242    public void initUI(I_CmsAppUIContext context) {
243
244        context.addPublishButton(changed -> {/* do nothing */});
245        super.initUI(context);
246    }
247
248    /**
249     * @see org.opencms.ui.apps.I_CmsCachableApp#isCachable()
250     */
251    public boolean isCachable() {
252
253        return true;
254    }
255
256    /**
257     * @see org.opencms.ui.apps.I_CmsCachableApp#onRestoreFromCache()
258     */
259    public void onRestoreFromCache() {
260
261        if (m_resultTable.getItemCount() < CmsFileExplorer.UPDATE_FOLDER_THRESHOLD) {
262            m_resultTable.update(m_resultTable.getAllIds(), false);
263        } else {
264            if (m_currentResources != null) {
265                Set<CmsUUID> ids = new HashSet<CmsUUID>();
266                for (CmsResource res : m_currentResources) {
267                    ids.add(res.getStructureId());
268                }
269                m_resultTable.update(ids, false);
270            }
271        }
272    }
273
274    /**
275     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#onStateChange(java.lang.String)
276     */
277    @Override
278    public void onStateChange(String state) {
279
280        if ((m_currentState == null) || !m_currentState.equals(state)) {
281            super.onStateChange(state);
282        }
283    }
284
285    /**
286     * Displays the search result.<p>
287     */
288    protected void displayResult() {
289
290        if (m_thread.getMatchedResources().isEmpty()) {
291            m_resultTable.setVisible(false);
292            m_infoIntroLayout.setVisible(false);
293            m_infoEmptyResult.setVisible(true);
294            m_resultTable.fillTable(A_CmsUI.getCmsObject(), m_thread.getMatchedResources());
295            m_searchForm.setDownload(null);
296        } else {
297            m_resultTable.setVisible(true);
298            m_infoIntroLayout.setVisible(false);
299            m_infoEmptyResult.setVisible(false);
300            m_resultTable.fillTable(A_CmsUI.getCmsObject(), m_thread.getMatchedResources());
301            SimpleDateFormat fmt = new SimpleDateFormat("hhmmss");
302            String timeStr = fmt.format(new Date());
303            String filename = "opencms_sourcesearch_" + timeStr + ".csv";
304            StreamResource downloadResource = new StreamResource(
305                () -> new ByteArrayInputStream(m_resultTable.generateCsv()),
306                filename);
307            m_searchForm.setDownload(downloadResource);
308        }
309        m_searchForm.removeComponent(m_report);
310        m_report = null;
311    }
312
313    /**
314     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getBreadCrumbForState(java.lang.String)
315     */
316    @Override
317    protected LinkedHashMap<String, String> getBreadCrumbForState(String state) {
318
319        return null;
320    }
321
322    /**
323     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getComponentForState(java.lang.String)
324     */
325    @Override
326    protected Component getComponentForState(String state) {
327
328        m_rootLayout.setMainHeightFull(true);
329        HorizontalSplitPanel sp = new HorizontalSplitPanel();
330        sp.setSizeFull();
331        m_searchForm = new CmsSourceSearchForm(this);
332        sp.setFirstComponent(m_searchForm);
333        VerticalLayout result = new VerticalLayout();
334        result.setSizeFull();
335        m_infoIntroLayout = CmsVaadinUtils.getInfoLayout(Messages.GUI_SOURCESEARCH_INTRO_0);
336        m_infoEmptyResult = CmsVaadinUtils.getInfoLayout(Messages.GUI_SOURCESEARCH_EMPTY_0);
337        m_resultTable = new CmsFileTable(null);
338
339        result.addComponent(m_resultTable);
340        result.addComponent(m_infoEmptyResult);
341        result.addComponent(m_infoIntroLayout);
342
343        m_resultTable.setVisible(false);
344        m_infoEmptyResult.setVisible(false);
345        m_infoIntroLayout.setVisible(true);
346
347        m_resultTable.applyWorkplaceAppSettings();
348        m_resultTable.setContextProvider(new I_CmsContextProvider() {
349
350            /**
351             * @see org.opencms.ui.apps.I_CmsContextProvider#getDialogContext()
352             */
353            public I_CmsDialogContext getDialogContext() {
354
355                CmsFileTableDialogContext context = new CmsFileTableDialogContext(
356                    CmsProjectManagerConfiguration.APP_ID,
357                    ContextType.fileTable,
358                    m_resultTable,
359                    m_resultTable.getSelectedResources());
360                storeCurrentFileSelection(m_resultTable.getSelectedResources());
361                context.setEditableProperties(CmsFileExplorer.INLINE_EDIT_PROPERTIES);
362                return context;
363            }
364        });
365        m_resultTable.setSizeFull();
366        if (m_resultTableFilter == null) {
367            m_resultTableFilter = new TextField();
368            m_resultTableFilter.setIcon(FontOpenCms.FILTER);
369            m_resultTableFilter.setInputPrompt(
370                Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_FILTER_0));
371            m_resultTableFilter.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON);
372            m_resultTableFilter.setWidth("200px");
373            m_resultTableFilter.addTextChangeListener(new TextChangeListener() {
374
375                private static final long serialVersionUID = 1L;
376
377                public void textChange(TextChangeEvent event) {
378
379                    m_resultTable.filterTable(event.getText());
380
381                }
382            });
383            m_infoLayout.addComponent(m_resultTableFilter);
384        }
385
386        sp.setSecondComponent(result);
387        sp.setSplitPosition(CmsFileExplorer.LAYOUT_SPLIT_POSITION, Unit.PIXELS);
388
389        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(state)) {
390            CmsSearchReplaceSettings settings = getSettingsFromState(state);
391            if (settings != null) {
392                m_currentState = state;
393                m_searchForm.initFormValues(settings);
394                search(settings, false);
395            }
396        }
397        return sp;
398    }
399
400    /**
401     * @see org.opencms.ui.apps.A_CmsWorkplaceApp#getSubNavEntries(java.lang.String)
402     */
403    @Override
404    protected List<NavEntry> getSubNavEntries(String state) {
405
406        return null;
407    }
408
409    /**
410     * Executes the search.<p>
411     *
412     * @param settings the search settings
413     * @param updateState <code>true</code> to create a new history entry
414     */
415    protected void search(CmsSearchReplaceSettings settings, boolean updateState) {
416
417        if (updateState) {
418            String state = generateState(settings);
419            CmsAppWorkplaceUi.get().changeCurrentAppState(state);
420            m_currentState = state;
421        }
422
423        CmsObject cms;
424        try {
425            cms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
426            if (settings.getSiteRoot() != null) {
427                cms.getRequestContext().setSiteRoot(settings.getSiteRoot());
428            }
429
430            m_thread = new CmsSearchReplaceThread(A_CmsUI.get().getHttpSession(), cms, settings);
431            if (m_report != null) {
432                m_searchForm.removeComponent(m_report);
433            }
434            m_report = new CmsReportOverlay(m_thread);
435            m_report.addReportFinishedHandler(new Runnable() {
436
437                public void run() {
438
439                    displayResult();
440                }
441            });
442            m_searchForm.addComponent(m_report);
443            m_report.setTitle(CmsVaadinUtils.getMessageText(Messages.GUI_SOURCESEARCH_REPORT_TITLE_0));
444            m_thread.start();
445            m_resultTableFilter.clear();
446        } catch (CmsException e) {
447            CmsErrorDialog.showErrorDialog(e);
448        }
449    }
450
451    /**
452     * Stores the currently selected resources list.<p>
453     *
454     * @param resources the currently selected resources
455     */
456    void storeCurrentFileSelection(List<CmsResource> resources) {
457
458        m_currentResources = resources;
459    }
460}