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