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 GmbH & Co. KG, 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.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsProject;
033import org.opencms.file.CmsProperty;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.file.types.CmsResourceTypeXmlContent;
037import org.opencms.i18n.CmsLocaleManager;
038import org.opencms.jsp.CmsJspTagContainer;
039import org.opencms.loader.CmsLoaderException;
040import org.opencms.lock.CmsLock;
041import org.opencms.main.CmsException;
042import org.opencms.main.CmsLog;
043import org.opencms.main.OpenCms;
044import org.opencms.report.A_CmsReportThread;
045import org.opencms.report.I_CmsReport;
046import org.opencms.search.CmsSearchException;
047import org.opencms.search.solr.CmsSolrIndex;
048import org.opencms.search.solr.CmsSolrQuery;
049import org.opencms.ui.apps.Messages;
050import org.opencms.ui.apps.search.CmsSourceSearchForm.SearchType;
051import org.opencms.util.CmsRequestUtil;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.xml.CmsXmlUtils;
054import org.opencms.xml.containerpage.CmsContainerElementBean;
055import org.opencms.xml.containerpage.CmsXmlContainerPage;
056import org.opencms.xml.containerpage.CmsXmlContainerPageFactory;
057import org.opencms.xml.content.CmsXmlContent;
058import org.opencms.xml.content.CmsXmlContentFactory;
059import org.opencms.xml.types.I_CmsXmlContentValue;
060
061import java.util.ArrayList;
062import java.util.Collections;
063import java.util.HashSet;
064import java.util.Iterator;
065import java.util.LinkedHashSet;
066import java.util.List;
067import java.util.Locale;
068import java.util.Set;
069import java.util.regex.Matcher;
070import java.util.regex.Pattern;
071
072import javax.servlet.http.HttpSession;
073
074import org.apache.commons.logging.Log;
075
076/**
077 * Searches in sources.
078 * <p>
079 *
080 * @since 7.5.3
081 */
082public class CmsSearchReplaceThread extends A_CmsReportThread {
083
084    /** The log object for this class. */
085    private static final Log LOG = CmsLog.getLog(CmsSearchReplaceThread.class);
086
087    /** The number of Solr search results to be processed at maximum. */
088    private static final int MAX_PROCESSED_SOLR_RESULTS = 10000;
089
090    /** Number of errors while searching. */
091    private int m_errorSearch;
092
093    /** Number of errors while updating. */
094    private int m_errorUpdate;
095
096    /** Number of locked files during updating. */
097    private int m_lockedFiles;
098
099    /** The found resources. */
100    private Set<CmsResource> m_matchedResources = new LinkedHashSet<CmsResource>();
101
102    /** The replace flag. */
103    private boolean m_replace;
104
105    /** Settings. */
106    private CmsSearchReplaceSettings m_settings;
107
108    /**
109     * Creates a replace html tag Thread.<p>
110     *
111     * @param session the current session
112     * @param cms the current cms object
113     * @param settings the settings needed to perform the operation.
114     */
115    public CmsSearchReplaceThread(HttpSession session, CmsObject cms, CmsSearchReplaceSettings settings) {
116
117        super(cms, "searchAndReplace");
118        initHtmlReport(cms.getRequestContext().getLocale());
119        m_settings = settings;
120    }
121
122    /**
123     * Creates a replace html tag Thread.<p>
124     *
125     * @param session the current session
126     * @param cms the current cms object
127     * @param settings the settings needed to perform the operation.
128     */
129    public CmsSearchReplaceThread(
130        HttpSession session,
131        CmsObject cms,
132        CmsSearchReplaceSettings settings,
133        I_CmsReport report) {
134
135        super(cms, "searchAndReplace");
136        m_report = report;
137        m_settings = settings;
138    }
139
140    /**
141     * Returns the matched resources.<p>
142     *
143     * @return the matched resources
144     */
145    public List<CmsResource> getMatchedResources() {
146
147        if (m_replace) {
148            // re-read the resources to include changes
149            List<CmsResource> result = new ArrayList<CmsResource>();
150            for (CmsResource resource : m_matchedResources) {
151                try {
152                    result.add(getCms().readResource(resource.getStructureId()));
153                } catch (CmsException e) {
154                    LOG.error(e.getLocalizedMessage(), e);
155                }
156            }
157            return result;
158        } else {
159            return new ArrayList<CmsResource>(m_matchedResources);
160        }
161    }
162
163    /**
164     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
165     */
166    @Override
167    public String getReportUpdate() {
168
169        return getReport().getReportUpdate();
170    }
171
172    /**
173     * @see java.lang.Runnable#run()
174     */
175    @Override
176    public void run() {
177
178        // get the report
179        I_CmsReport report = getReport();
180        boolean isError = false;
181        report.println(
182            Messages.get().container(Messages.RPT_SOURCESEARCH_BEGIN_SEARCH_THREAD_0),
183            I_CmsReport.FORMAT_HEADLINE);
184        // write parameters to report
185        report.println(Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_0), I_CmsReport.FORMAT_HEADLINE);
186        // the paths
187        if (!m_settings.getPaths().isEmpty()) {
188            // iterate over the paths
189            Iterator<String> iter = m_settings.getPaths().iterator();
190            while (iter.hasNext()) {
191                String path = iter.next();
192                report.println(
193                    Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_RESOURCE_PATH_1, path),
194                    I_CmsReport.FORMAT_NOTE);
195            }
196        } else {
197            // no paths selected
198            isError = true;
199            report.println(
200                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_EMPTY_RESOURCE_PATHS_0),
201                I_CmsReport.FORMAT_ERROR);
202        }
203        // the search pattern
204        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_settings.getSearchpattern())) {
205            // there is a search pattern
206            report.println(
207                Messages.get().container(
208                    Messages.RPT_SOURCESEARCH_PARAMETERS_SEARCHPATTERN_1,
209                    CmsStringUtil.escapeHtml(m_settings.getSearchpattern())),
210                I_CmsReport.FORMAT_NOTE);
211        } else {
212            // empty search pattern
213            isError = true;
214            report.println(
215                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_EMPTY_SEARCHPATTERN_0),
216                I_CmsReport.FORMAT_ERROR);
217        }
218        // the replace pattern
219        report.println(
220            Messages.get().container(
221                Messages.RPT_SOURCESEARCH_PARAMETERS_REPLACEPATTERN_1,
222                CmsStringUtil.escapeHtml(m_settings.getReplacepattern())),
223            I_CmsReport.FORMAT_NOTE);
224        // the project
225        report.println(
226            Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_PROJECT_1, m_settings.getProject()),
227            I_CmsReport.FORMAT_NOTE);
228        // remarks for search/replace dependent od the replace pattern and the selected project
229        // in the online project search is possible only
230        // in other projects there is replaced, if the replace pattern is not empty
231        if (CmsStringUtil.isEmpty(m_settings.getReplacepattern()) && !m_settings.isForceReplace()) {
232            // empty replace pattern, search only
233            report.println(
234                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_EMPTY_REPLACEPATTERN_0),
235                I_CmsReport.FORMAT_NOTE);
236        } else {
237            // not empty replace pattern, search and replace
238            m_replace = true;
239            report.println(
240                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_NOTEMPTY_REPLACEPATTERN_0),
241                I_CmsReport.FORMAT_NOTE);
242        }
243
244        // make an OpenCms object copy if replace is active
245        CmsObject cmsObject = getCms();
246        if (m_replace && !m_settings.getProject().equals(cmsObject.getRequestContext().getCurrentProject().getName())) {
247            try {
248                cmsObject = OpenCms.initCmsObject(getCms());
249                CmsProject cmsProject = getCms().readProject(m_settings.getProject());
250                cmsObject.getRequestContext().setCurrentProject(cmsProject);
251            } catch (CmsException e) {
252                report.println(
253                    Messages.get().container(Messages.RPT_SOURCESEARCH_INIT_CMS_OBJECT_FAILED_0),
254                    I_CmsReport.FORMAT_NOTE);
255                m_replace = false;
256            }
257        }
258
259        // search the resources and replace the patterns
260        if (!isError) {
261            List<CmsResource> resources = searchResources();
262
263            if (resources.isEmpty()) {
264                // no resources found, so search is not possible
265                report.println(
266                    Messages.get().container(Messages.RPT_SOURCESEARCH_NO_FILES_TO_SEARCH_IN_0),
267                    I_CmsReport.FORMAT_NOTE);
268            } else {
269
270                report.println(
271                    Messages.get().container(
272                        Messages.RPT_SOURCESEARCH_NR_OF_FILES_TO_SEARCH_IN_1,
273                        new Integer(resources.size())),
274                    I_CmsReport.FORMAT_NOTE);
275                if (m_replace) {
276                    // start searching and replacing
277                    report.println(
278                        Messages.get().container(Messages.RPT_SOURCESEARCH_START_SEARCHING_REPLACING_0),
279                        I_CmsReport.FORMAT_HEADLINE);
280                } else {
281                    // start searching
282                    report.println(
283                        Messages.get().container(Messages.RPT_SOURCESEARCH_START_SEARCHING_0),
284                        I_CmsReport.FORMAT_HEADLINE);
285                }
286
287                if (m_settings.getType().isPropertySearch()) {
288                    searchProperties(resources);
289                } else {
290                    searchAndReplace(resources);
291                }
292            }
293
294        } else {
295            // do not show the resources, because there were errors while searching
296        }
297
298        report.println(
299            Messages.get().container(Messages.RPT_SOURCESEARCH_END_SEARCH_THREAD_0),
300            I_CmsReport.FORMAT_HEADLINE);
301    }
302
303    /**
304     * Search the resources.<p>
305     *
306     * @param resources the relevant resources
307     */
308    protected void searchAndReplace(List<CmsResource> resources) {
309
310        // the file counter
311        int counter = 0;
312        int resCount = resources.size();
313        I_CmsReport report = getReport();
314        // iterate over the files in the selected path
315        for (CmsResource resource : resources) {
316
317            try {
318
319                // get the content
320                CmsFile file = getCms().readFile(resource);
321                byte[] contents = file.getContents();
322
323                // report the current resource
324                ++counter;
325                report(report, counter, resCount, resource);
326
327                // search and replace
328                byte[] result = null;
329                boolean xpath = false;
330                if ((CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_settings.getXpath())
331                    || m_settings.isOnlyContentValues()) && CmsResourceTypeXmlContent.isXmlContent(resource)) {
332                    xpath = true;
333                }
334                if (!xpath) {
335                    result = replaceInContent(file, contents);
336                } else {
337                    result = replaceInXml(file);
338                }
339
340                if ((result != null) && (contents != null) && !contents.equals(result)) {
341                    // rewrite the content
342                    writeContent(file, result);
343                } else {
344                    getReport().println();
345                }
346
347            } catch (Exception e) {
348                report.print(
349                    org.opencms.report.Messages.get().container(Messages.RPT_SOURCESEARCH_COULD_NOT_READ_FILE_0),
350                    I_CmsReport.FORMAT_ERROR);
351                report.addError(e);
352                report.println(
353                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
354                    I_CmsReport.FORMAT_ERROR);
355                m_errorSearch += 1;
356                LOG.error(
357                    org.opencms.report.Messages.get().container(Messages.RPT_SOURCESEARCH_COULD_NOT_READ_FILE_0),
358                    e);
359                continue;
360            }
361        }
362
363        // report results
364        reportResults(resources.size());
365    }
366
367    /**
368     * Locks the current resource.<p>
369     *
370     * @param cms the current CmsObject
371     * @param cmsResource the resource to lock
372     * @param report the report
373     *
374     * @return <code>true</code> if the given resource was locked was successfully
375     *
376     * @throws CmsException if some goes wrong
377     */
378    private boolean lockResource(CmsObject cms, CmsResource cmsResource, I_CmsReport report) throws CmsException {
379
380        CmsLock lock = cms.getLock(cms.getSitePath(cmsResource));
381        // check the lock
382        if ((lock != null)
383            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
384            && lock.isOwnedInProjectBy(
385                cms.getRequestContext().getCurrentUser(),
386                cms.getRequestContext().getCurrentProject())) {
387            // prove is current lock from current user in current project
388            return true;
389        } else if ((lock != null) && !lock.isUnlocked() && !lock.isOwnedBy(cms.getRequestContext().getCurrentUser())) {
390            // the resource is not locked by the current user, so can not lock it
391            m_lockedFiles += 1;
392            return false;
393        } else if ((lock != null)
394            && !lock.isUnlocked()
395            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
396            && !lock.isOwnedInProjectBy(
397                cms.getRequestContext().getCurrentUser(),
398                cms.getRequestContext().getCurrentProject())) {
399            // prove is current lock from current user but not in current project
400            // file is locked by current user but not in current project
401            // change the lock
402            cms.changeLock(cms.getSitePath(cmsResource));
403        } else if ((lock != null) && lock.isUnlocked()) {
404            // lock resource from current user in current project
405            cms.lockResource(cms.getSitePath(cmsResource));
406        }
407        lock = cms.getLock(cms.getSitePath(cmsResource));
408        if ((lock != null)
409            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
410            && !lock.isOwnedInProjectBy(
411                cms.getRequestContext().getCurrentUser(),
412                cms.getRequestContext().getCurrentProject())) {
413            // resource could not be locked
414            m_lockedFiles += 1;
415
416            return false;
417        }
418        // resource is locked successfully
419        return true;
420    }
421
422    /**
423     * Renames a nested container within a container page XML.<p>
424     *
425     * @param targetContainerPage the target container page
426     * @param layoutResource the container element resource generating the nested container
427     * @param oldName the old container name
428     * @param newName the new container name
429     *
430     * @return the changed content bytes
431     *
432     * @throws Exception in case unmarshalling of the container page fails
433     */
434    private byte[] renameNestedContainers(
435        CmsFile targetContainerPage,
436        CmsResource layoutResource,
437        String oldName,
438        String newName)
439    throws Exception {
440
441        byte[] contents = targetContainerPage.getContents();
442        Set<String> replaceElementIds = new HashSet<String>();
443        try {
444            CmsXmlContainerPage page = CmsXmlContainerPageFactory.unmarshal(getCms(), targetContainerPage);
445            for (CmsContainerElementBean element : page.getContainerPage(getCms()).getElements()) {
446                if (element.getId().equals(layoutResource.getStructureId()) && (element.getInstanceId() != null)) {
447                    replaceElementIds.add(element.getInstanceId());
448                }
449            }
450            if (replaceElementIds.size() > 0) {
451                String encoding = CmsLocaleManager.getResourceEncoding(getCms(), targetContainerPage);
452                String content = new String(contents, encoding);
453                for (String instanceId : replaceElementIds) {
454                    Pattern patt = Pattern.compile(
455                        CmsJspTagContainer.getNestedContainerName(oldName, instanceId, null));
456                    Matcher m = patt.matcher(content);
457                    StringBuffer sb = new StringBuffer(content.length());
458                    while (m.find()) {
459                        m.appendReplacement(
460                            sb,
461                            Matcher.quoteReplacement(
462                                CmsJspTagContainer.getNestedContainerName(newName, instanceId, null)));
463                    }
464                    m.appendTail(sb);
465                    content = sb.toString();
466                }
467                contents = content.getBytes(encoding);
468            }
469        } catch (Exception e) {
470            LOG.error(e.getLocalizedMessage(), e);
471            throw e;
472        }
473        return contents;
474    }
475
476    /**
477     * Performs the replacement in content.<p>
478     *
479     * @param file the file object
480     * @param contents the byte content
481     *
482     * @return the new content if a replacement has been performed
483     *
484     * @throws Exception if something goes wrong
485     */
486    private byte[] replaceInContent(CmsFile file, byte[] contents) throws Exception {
487
488        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_settings.getLocale())) {
489            Locale contentLocale = CmsLocaleManager.getMainLocale(getCms(), file);
490            if (!contentLocale.toString().equalsIgnoreCase(m_settings.getLocale())) {
491                // content does not match the requested locale, skip it
492                getReport().println(
493                    Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
494                    I_CmsReport.FORMAT_NOTE);
495                return null;
496            }
497        }
498
499        String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
500        String content = new String(contents, encoding);
501
502        if (CmsSourceSearchForm.REGEX_ALL.equals(m_settings.getSearchpattern()) & !m_replace) {
503            m_matchedResources.add(file);
504            getReport().print(Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0), I_CmsReport.FORMAT_OK);
505            return null;
506        }
507
508        Matcher matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(content);
509
510        if (matcher.find()) {
511            // search pattern did match here, so take this file in the list with matches resources
512            m_matchedResources.add(file);
513            getReport().print(Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0), I_CmsReport.FORMAT_OK);
514            if (m_replace) {
515                if (m_settings.getType().equals(SearchType.renameContainer)) {
516
517                    return renameNestedContainers(
518                        file,
519                        m_settings.getElementResource(),
520                        m_settings.getReplacepattern().split(";")[0],
521                        m_settings.getReplacepattern().split(";")[1]);
522                }
523                return matcher.replaceAll(m_settings.getReplacepattern()).getBytes(encoding);
524            }
525        } else {
526            // search pattern did not match
527            getReport().print(
528                Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
529                I_CmsReport.FORMAT_NOTE);
530        }
531        return null;
532    }
533
534    /**
535     * Performs a replacement for XML contents.<p>
536     *
537     * @param cmsFile the file to operate on
538     *
539     * @return the marshaled content
540     * @throws Exception if something goes wrong
541     */
542    private byte[] replaceInXml(CmsFile cmsFile) throws Exception {
543
544        Exception e = null;
545        CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), cmsFile);
546        Pattern pattern = Pattern.compile(m_settings.getSearchpattern());
547        // loop over the locales of the content
548        boolean modified = false;
549        boolean matched = false;
550        String requestedLocale = m_settings.getLocale();
551        for (Locale locale : xmlContent.getLocales()) {
552            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(requestedLocale)
553                && !locale.toString().equalsIgnoreCase(requestedLocale)) {
554                // does not match the requested locale, skip it
555                continue;
556            }
557            // loop over the available element paths of the current content locale
558            List<String> paths = xmlContent.getNames(locale);
559            for (String xpath : paths) {
560                // try to get the value extraction for the current element path
561                I_CmsXmlContentValue value = xmlContent.getValue(xpath, locale);
562                if (value.isSimpleType()) {
563                    try {
564                        String currPath = value.getPath();
565                        if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_settings.getXpath())
566                            || currPath.equals(m_settings.getXpath())
567                            || (CmsXmlUtils.removeXpath(currPath).equals(m_settings.getXpath()))) {
568                            // xpath match
569                            String oldVal = value.getStringValue(getCms());
570                            Matcher matcher = pattern.matcher(oldVal);
571                            matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(oldVal);
572                            if (matcher.find()) {
573                                matched = true;
574                                m_matchedResources.add(cmsFile);
575                                if (m_replace) {
576                                    String newVal = matcher.replaceAll(m_settings.getReplacepattern());
577                                    if (!oldVal.equals(newVal)) {
578                                        value.setStringValue(getCms(), newVal);
579                                        modified = true;
580                                    }
581                                }
582                            }
583                        }
584                    } catch (Exception ex) {
585                        // log and go on
586                        LOG.error(ex.getMessage(), ex);
587                        e = ex;
588                    }
589                }
590            }
591        }
592        if (e != null) {
593            throw e;
594        }
595        if (matched) {
596            getReport().println(Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0), I_CmsReport.FORMAT_OK);
597        } else {
598            getReport().println(
599                Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
600                I_CmsReport.FORMAT_NOTE);
601        }
602        if (modified) {
603            return xmlContent.marshal();
604        }
605        return null;
606    }
607
608    /**
609     * Replace properties of given resources.<p>
610     *
611     * @param matchedResources to replace properties
612     */
613    private void replaceProperties(Set<CmsResource> matchedResources) {
614
615        for (CmsResource resource : matchedResources) {
616            try {
617                CmsProperty prop = getCms().readPropertyObject(resource, m_settings.getProperty().getName(), false);
618                Matcher matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(prop.getValue());
619                if (m_settings.getReplacepattern().isEmpty()) {
620                    prop.setValue("", "");
621                } else {
622                    prop.setValue(matcher.replaceAll(m_settings.getReplacepattern()), CmsProperty.TYPE_INDIVIDUAL);
623                }
624                getCms().lockResource(resource);
625                getCms().writePropertyObjects(resource, Collections.singletonList(prop));
626                getCms().unlockResource(resource);
627            } catch (CmsException e) {
628                LOG.error("Ubable to change property", e);
629            }
630        }
631    }
632
633    /**
634     * Reads the content as byte array of the given resource and prints a message to the report.<p>
635     *
636     * @param report the report
637     * @param counter the counter
638     * @param resCount the total resource count
639     * @param resource the file to get the content for
640     */
641    private void report(I_CmsReport report, int counter, int resCount, CmsResource resource) {
642
643        // report entries
644        report.print(
645            org.opencms.report.Messages.get().container(
646                org.opencms.report.Messages.RPT_SUCCESSION_2,
647                String.valueOf(counter),
648                String.valueOf(resCount)),
649            I_CmsReport.FORMAT_NOTE);
650        report.print(
651            org.opencms.report.Messages.get().container(
652                org.opencms.report.Messages.RPT_ARGUMENT_1,
653                report.removeSiteRoot(resource.getRootPath())));
654        report.print(
655            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0),
656            I_CmsReport.FORMAT_DEFAULT);
657    }
658
659    /**
660     * Prints the result messages into the report.<p>
661     *
662     * @param nrOfFiles the total number of files
663     */
664    private void reportResults(int nrOfFiles) {
665
666        I_CmsReport report = getReport();
667        // report entries
668        if (m_replace) {
669            // finish searching and replacing
670            report.println(
671                Messages.get().container(Messages.RPT_SOURCESEARCH_END_SEARCHING_REPLACING_0),
672                I_CmsReport.FORMAT_HEADLINE);
673        } else {
674            // finish searching
675            report.println(
676                Messages.get().container(Messages.RPT_SOURCESEARCH_END_SEARCHING_0),
677                I_CmsReport.FORMAT_HEADLINE);
678        }
679        // the results are written in the report
680        report.println(Messages.get().container(Messages.RPT_SOURCESEARCH_RESULT_0), I_CmsReport.FORMAT_HEADLINE);
681        report.println(
682            Messages.get().container(
683                Messages.RPT_SOURCESEARCH_NR_OF_FILES_TO_SEARCH_IN_1,
684                new Integer(nrOfFiles).toString()),
685            I_CmsReport.FORMAT_NOTE);
686        report.println(
687            Messages.get().container(
688                Messages.RPT_SOURCESEARCH_NR_OF_FILES_MATCHED_1,
689                new Integer(m_matchedResources.size()).toString()),
690            I_CmsReport.FORMAT_NOTE);
691        report.println(
692            Messages.get().container(
693                Messages.RPT_SOURCESEARCH_SEARCH_ERROR_COUNT_1,
694                new Integer(m_errorSearch).toString()),
695            I_CmsReport.FORMAT_NOTE);
696        if (m_replace) {
697            // replace report entries
698            report.println(
699                Messages.get().container(
700                    Messages.RPT_SOURCESEARCH_REPLACE_ERROR_COUNT_1,
701                    new Integer(m_errorUpdate).toString()),
702                I_CmsReport.FORMAT_NOTE);
703            report.println(
704                Messages.get().container(
705                    Messages.RPT_SOURCESEARCH_LOCKED_FILES_1,
706                    new Integer(m_lockedFiles).toString()),
707                I_CmsReport.FORMAT_NOTE);
708            if (m_matchedResources.size() == 0) {
709                report.println(
710                    Messages.get().container(Messages.RPT_SOURCESEARCH_NO_FILES_FOUND_0),
711                    I_CmsReport.FORMAT_OK);
712            } else {
713                report.println(
714                    Messages.get().container(Messages.RPT_SOURCESEARCH_CLICK_OK_TO_GET_LIST_0),
715                    I_CmsReport.FORMAT_OK);
716            }
717            if (m_lockedFiles > 0) {
718                report.println(
719                    Messages.get().container(Messages.RPT_SOURCESEARCH_REPLACE_FAILED_0),
720                    I_CmsReport.FORMAT_ERROR);
721            } else {
722                report.println(
723                    Messages.get().container(Messages.RPT_SOURCESEARCH_REPLACE_SUCCESS_0),
724                    I_CmsReport.FORMAT_OK);
725            }
726        } else {
727            // search report entries
728            if (m_matchedResources.size() == 0) {
729                report.println(
730                    Messages.get().container(Messages.RPT_SOURCESEARCH_NO_FILES_FOUND_0),
731                    I_CmsReport.FORMAT_OK);
732            } else {
733                report.println(
734                    Messages.get().container(Messages.RPT_SOURCESEARCH_CLICK_OK_TO_GET_LIST_0),
735                    I_CmsReport.FORMAT_OK);
736            }
737            if (m_errorSearch > 0) {
738                // only searching failed
739                report.println(
740                    Messages.get().container(Messages.RPT_SOURCESEARCH_SEARCH_FAILED_0),
741                    I_CmsReport.FORMAT_ERROR);
742            } else {
743                // only searching was successful
744                report.println(
745                    Messages.get().container(Messages.RPT_SOURCESEARCH_SEARCH_SUCCESS_0),
746                    I_CmsReport.FORMAT_OK);
747            }
748        }
749    }
750
751    /**
752     * Search and replace function for properties.<p>
753     *
754     * @param resources to be considered
755     */
756    private void searchProperties(List<CmsResource> resources) {
757
758        if (CmsSourceSearchForm.REGEX_ALL.equals(m_settings.getSearchpattern())) {
759            for (CmsResource resource : resources) {
760                m_matchedResources.add(resource);
761                getReport().println(
762                    Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0),
763                    I_CmsReport.FORMAT_OK);
764            }
765
766        } else {
767            for (CmsResource resource : resources) {
768                Matcher matcher;
769                try {
770                    CmsProperty prop = getCms().readPropertyObject(resource, m_settings.getProperty().getName(), false);
771                    matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(prop.getValue());
772                    if (matcher.find()) {
773                        m_matchedResources.add(resource);
774                        getReport().println(
775                            Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0),
776                            I_CmsReport.FORMAT_OK);
777                    } else {
778                        getReport().println(
779                            Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
780                            I_CmsReport.FORMAT_NOTE);
781                    }
782
783                } catch (CmsException e) {
784                    LOG.error("Ubable to read property", e);
785                }
786            }
787        }
788        if (m_replace) {
789            replaceProperties(m_matchedResources);
790        }
791        // report results
792        reportResults(resources.size());
793    }
794
795    /**
796     * Searches/reads all resources that are relevant.<p>
797     *
798     * @return the relevant resources
799     */
800    @SuppressWarnings("deprecation")
801    private List<CmsResource> searchResources() {
802
803        getReport().println(
804            Messages.get().container(Messages.RPT_SOURCESEARCH_START_COLLECTING_FILES_TO_SEARCH_IN_0),
805            I_CmsReport.FORMAT_HEADLINE);
806
807        List<CmsResource> resources = new ArrayList<CmsResource>();
808        if (m_settings.isSolrSearch()) {
809            CmsSolrIndex index = OpenCms.getSearchManager().getIndexSolr(m_settings.getSource());
810            if (index != null) {
811                CmsSolrQuery query = new CmsSolrQuery(
812                    null,
813                    CmsRequestUtil.createParameterMap(m_settings.getQuery() + "&fl=path,type"));
814                List<String> rootPaths = new ArrayList<>(m_settings.getPaths().size());
815                String siteRoot = getCms().getRequestContext().getSiteRoot();
816                for (String path : m_settings.getPaths()) {
817                    rootPaths.add(path.startsWith(siteRoot) ? path : getCms().addSiteRoot(path));
818                }
819                query.setSearchRoots(rootPaths);
820                if ((m_settings.getTypesArray() != null) && (m_settings.getTypesArray().length > 0)) {
821                    query.setResourceTypes(m_settings.getTypesArray());
822                }
823                query.setRows(new Integer(MAX_PROCESSED_SOLR_RESULTS));
824                query.ensureParameters();
825                try {
826                    resources.addAll(
827                        index.search(getCms(), query, true, null, false, null, MAX_PROCESSED_SOLR_RESULTS));
828                } catch (CmsSearchException e) {
829                    LOG.error(e.getMessage(), e);
830                }
831            }
832        } else {
833            CmsResourceFilter filter = CmsResourceFilter.ALL.addExcludeState(
834                CmsResource.STATE_DELETED).addRequireVisible();
835            List<CmsResourceFilter> filterList = new ArrayList<CmsResourceFilter>();
836            if ((m_settings.getTypesArray() != null) && (m_settings.getTypesArray().length > 0)) {
837                for (String resTypeName : m_settings.getTypesArray()) {
838                    try {
839                        int typeId = OpenCms.getResourceManager().getResourceType(resTypeName).getTypeId();
840                        filterList.add(((CmsResourceFilter)filter.clone()).addRequireType(typeId));
841                    } catch (CmsLoaderException e) {
842                        // noop
843                    } catch (NullPointerException e) {
844                        // noop
845                    }
846                }
847            }
848            if (filterList.size() == 1) {
849                filter = filterList.get(0);
850            }
851
852            // iterate over all selected paths
853            Iterator<String> iterPaths = m_settings.getPaths().iterator();
854
855            if (!m_settings.getType().isPropertySearch()) {
856                filter = filter.addRequireFile();
857            }
858            while (iterPaths.hasNext()) {
859                String path = iterPaths.next();
860                try {
861                    if (m_settings.getType().isPropertySearch()) {
862                        resources.addAll(
863                            getCms().readResourcesWithProperty(path, m_settings.getProperty().getName(), null, filter));
864                    } else {
865                        // only read resources which are files and not deleted, which are in the current time range window and where the current
866                        // user has the sufficient permissions to read them
867
868                        List<CmsResource> tmpResources = getCms().readResources(path, filter);
869                        List<String> subsites = null;
870                        if (m_settings.ignoreSubSites()) {
871                            subsites = OpenCms.getADEManager().getSubSitePaths(getCms(), path);
872                            subsites.remove(
873                                OpenCms.getADEManager().getSubSiteRoot(
874                                    getCms(),
875                                    getCms().readResource(path).getRootPath()));
876                        }
877                        Iterator<CmsResource> iterator = tmpResources.iterator();
878                        while (iterator.hasNext()) {
879                            CmsResource r = iterator.next();
880                            boolean remove = true;
881                            if (filterList.size() > 1) {
882                                for (CmsResourceFilter f : filterList) {
883                                    if (f.isValid(getCms().getRequestContext(), r)) {
884                                        remove = false;
885                                    }
886                                }
887                            } else {
888                                remove = false;
889                            }
890                            if ((subsites != null) & !remove) {
891                                if (subsites.contains(
892                                    OpenCms.getADEManager().getSubSiteRoot(getCms(), r.getRootPath()))) {
893                                    remove = true;
894                                }
895                            }
896                            if (remove) {
897                                iterator.remove();
898                            }
899                        }
900
901                        if ((tmpResources != null) && !tmpResources.isEmpty()) {
902                            resources.addAll(tmpResources);
903                        }
904                    }
905                } catch (CmsException e) {
906                    // an error occured
907                    LOG.error(Messages.get().container(Messages.RPT_SOURCESEARCH_ERROR_READING_RESOURCES_1, path), e);
908                    getReport().println(
909                        Messages.get().container(Messages.RPT_SOURCESEARCH_ERROR_READING_RESOURCES_1, path),
910                        I_CmsReport.FORMAT_ERROR);
911                }
912            }
913        }
914        return resources;
915    }
916
917    /**
918     * Writes the file contents.<p>
919     *
920     * @param file the file to write
921     * @param content the file content
922     *
923     * @return success flag
924     */
925    private boolean writeContent(CmsFile file, byte[] content) {
926
927        boolean success = true;
928        I_CmsReport report = getReport();
929        CmsObject cmsObject = getCms();
930        // get current lock from file
931        try {
932            // try to lock the resource
933            if (!lockResource(cmsObject, file, report)) {
934                report.println(
935                    Messages.get().container(Messages.RPT_SOURCESEARCH_LOCKED_FILE_0, cmsObject.getSitePath(file)),
936                    I_CmsReport.FORMAT_ERROR);
937                success = false;
938            }
939        } catch (CmsException e) {
940            report.println(
941                Messages.get().container(Messages.RPT_SOURCESEARCH_LOCKED_FILE_0, cmsObject.getSitePath(file)),
942                I_CmsReport.FORMAT_ERROR);
943            if (LOG.isErrorEnabled()) {
944                LOG.error(e.getMessageContainer(), e);
945            }
946            success = false;
947        }
948
949        // write the file content
950        try {
951            file.setContents(content);
952            cmsObject.writeFile(file);
953        } catch (Exception e) {
954            m_errorUpdate += 1;
955            report.println(
956                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
957                I_CmsReport.FORMAT_ERROR);
958            if (LOG.isErrorEnabled()) {
959                LOG.error(e.toString());
960            }
961            success = false;
962        }
963
964        // unlock the resource
965        try {
966            cmsObject.unlockResource(cmsObject.getSitePath(file));
967        } catch (CmsException e) {
968            m_errorUpdate += 1;
969            report.println(
970                Messages.get().container(Messages.RPT_SOURCESEARCH_UNLOCK_FILE_0),
971                I_CmsReport.FORMAT_WARNING);
972            if (LOG.isErrorEnabled()) {
973                LOG.error(e.getMessageContainer(), e);
974            }
975            success = false;
976        }
977
978        if (success) {
979            // successfully updated
980            report.println(
981                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
982                I_CmsReport.FORMAT_OK);
983        }
984
985        return success;
986    }
987}