001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.Arrays;
064import java.util.Collections;
065import java.util.HashSet;
066import java.util.Iterator;
067import java.util.LinkedHashSet;
068import java.util.List;
069import java.util.Locale;
070import java.util.Set;
071import java.util.concurrent.TimeUnit;
072import java.util.regex.Matcher;
073import java.util.regex.Pattern;
074
075import org.apache.commons.logging.Log;
076
077import jakarta.servlet.http.HttpSession;
078
079/**
080 * Searches in sources.
081 * <p>
082 *
083 * @since 7.5.3
084 */
085public class CmsSearchReplaceThread extends A_CmsReportThread {
086
087    /** The log object for this class. */
088    private static final Log LOG = CmsLog.getLog(CmsSearchReplaceThread.class);
089
090    /** The number of Solr search results to be processed at maximum. */
091    private static final int MAX_PROCESSED_SOLR_RESULTS = 10000;
092
093    /** Time after which the search operation is cancelled if no report update is requested. */
094    public static final long ABANDON_TIMEOUT = TimeUnit.MINUTES.toMillis(5);
095
096    /** Number of errors while searching. */
097    private int m_errorSearch;
098
099    /** Number of errors while updating. */
100    private int m_errorUpdate;
101
102    /** Number of locked files during updating. */
103    private int m_lockedFiles;
104
105    /** The found resources. */
106    private Set<CmsResource> m_matchedResources = new LinkedHashSet<CmsResource>();
107
108    /** The replace flag. */
109    private boolean m_replace;
110
111    /** Settings. */
112    private CmsSearchReplaceSettings m_settings;
113
114    /** True if this thread was started with a non-null session argument. */
115    private boolean m_hasSession;
116
117    /** Timestamp of last report update. */
118    private volatile long m_lastTimestamp = -1;
119
120    /**
121     * Creates a replace html tag Thread.<p>
122     *
123     * @param session the current session
124     * @param cms the current cms object
125     * @param settings the settings needed to perform the operation.
126     */
127    public CmsSearchReplaceThread(HttpSession session, CmsObject cms, CmsSearchReplaceSettings settings) {
128
129        super(cms, "searchAndReplace");
130        m_hasSession = session != null;
131        initHtmlReport(cms.getRequestContext().getLocale());
132        m_settings = settings;
133    }
134
135    /**
136     * Creates a replace html tag Thread.<p>
137     *
138     * @param session the current session
139     * @param cms the current cms object
140     * @param settings the settings needed to perform the operation.
141     */
142    public CmsSearchReplaceThread(
143        HttpSession session,
144        CmsObject cms,
145        CmsSearchReplaceSettings settings,
146        I_CmsReport report) {
147
148        super(cms, "searchAndReplace");
149        m_report = report;
150        m_settings = settings;
151    }
152
153    /**
154     * Returns the matched resources.<p>
155     *
156     * @return the matched resources
157     */
158    public List<CmsResource> getMatchedResources() {
159
160        if (m_replace) {
161            // re-read the resources to include changes
162            List<CmsResource> result = new ArrayList<CmsResource>();
163            for (CmsResource resource : m_matchedResources) {
164                try {
165                    result.add(getCms().readResource(resource.getStructureId()));
166                } catch (CmsException e) {
167                    LOG.error(e.getLocalizedMessage(), e);
168                }
169            }
170            return result;
171        } else {
172            return new ArrayList<CmsResource>(m_matchedResources);
173        }
174    }
175
176    /**
177     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
178     */
179    @Override
180    public String getReportUpdate() {
181
182        m_lastTimestamp = System.currentTimeMillis();
183        return getReport().getReportUpdate();
184    }
185
186    /**
187     * @see org.opencms.report.A_CmsReportThread#getReportUpdate(org.opencms.report.I_CmsReportUpdateFormatter)
188     */
189    @Override
190    public String getReportUpdate(I_CmsReportUpdateFormatter formatter) {
191
192        m_lastTimestamp = System.currentTimeMillis();
193        return super.getReportUpdate(formatter);
194    }
195
196    /**
197     * Returns true if the last report update is too far back in time, so the user has probably closed the window/tab.
198     *
199     * @return true if the last report update is too far back
200     */
201    public boolean isAbandoned() {
202
203        boolean result = m_hasSession
204            && (m_lastTimestamp != -1)
205            && ((System.currentTimeMillis() - m_lastTimestamp) > ABANDON_TIMEOUT);
206        return result;
207    }
208
209    /**
210     * @see java.lang.Runnable#run()
211     */
212    @Override
213    public void run() {
214
215        // get the report
216        I_CmsReport report = getReport();
217        boolean isError = false;
218        report.println(
219            Messages.get().container(Messages.RPT_SOURCESEARCH_BEGIN_SEARCH_THREAD_0),
220            I_CmsReport.FORMAT_HEADLINE);
221        // write parameters to report
222        report.println(Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_0), I_CmsReport.FORMAT_HEADLINE);
223        // the paths
224        if (!m_settings.getPaths().isEmpty()) {
225            // iterate over the paths
226            Iterator<String> iter = m_settings.getPaths().iterator();
227            while (iter.hasNext()) {
228                String path = iter.next();
229                report.println(
230                    Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_RESOURCE_PATH_1, path),
231                    I_CmsReport.FORMAT_NOTE);
232            }
233        } else {
234            // no paths selected
235            isError = true;
236            report.println(
237                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_EMPTY_RESOURCE_PATHS_0),
238                I_CmsReport.FORMAT_ERROR);
239        }
240        // the search pattern
241        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_settings.getSearchpattern())) {
242            // there is a search pattern
243            report.println(
244                Messages.get().container(
245                    Messages.RPT_SOURCESEARCH_PARAMETERS_SEARCHPATTERN_1,
246                    CmsStringUtil.escapeHtml(m_settings.getSearchpattern())),
247                I_CmsReport.FORMAT_NOTE);
248        } else {
249            // empty search pattern
250            isError = true;
251            report.println(
252                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_EMPTY_SEARCHPATTERN_0),
253                I_CmsReport.FORMAT_ERROR);
254        }
255        // the replace pattern
256        report.println(
257            Messages.get().container(
258                Messages.RPT_SOURCESEARCH_PARAMETERS_REPLACEPATTERN_1,
259                CmsStringUtil.escapeHtml(m_settings.getReplacepattern())),
260            I_CmsReport.FORMAT_NOTE);
261        // the project
262        report.println(
263            Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_PROJECT_1, m_settings.getProject()),
264            I_CmsReport.FORMAT_NOTE);
265        // remarks for search/replace dependent od the replace pattern and the selected project
266        // in the online project search is possible only
267        // in other projects there is replaced, if the replace pattern is not empty
268        if (CmsStringUtil.isEmpty(m_settings.getReplacepattern()) && !m_settings.isForceReplace()) {
269            report.println(
270                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_EMPTY_REPLACEPATTERN_0),
271                I_CmsReport.FORMAT_NOTE);
272        } else {
273            // not empty replace pattern, search and replace
274            m_replace = true;
275            report.println(
276                Messages.get().container(Messages.RPT_SOURCESEARCH_PARAMETERS_NOTEMPTY_REPLACEPATTERN_0),
277                I_CmsReport.FORMAT_NOTE);
278        }
279
280        // make an OpenCms object copy if replace is active
281        CmsObject cmsObject = getCms();
282        if (m_replace && !m_settings.getProject().equals(cmsObject.getRequestContext().getCurrentProject().getName())) {
283            try {
284                cmsObject = OpenCms.initCmsObject(getCms());
285                CmsProject cmsProject = getCms().readProject(m_settings.getProject());
286                cmsObject.getRequestContext().setCurrentProject(cmsProject);
287            } catch (CmsException e) {
288                report.println(
289                    Messages.get().container(Messages.RPT_SOURCESEARCH_INIT_CMS_OBJECT_FAILED_0),
290                    I_CmsReport.FORMAT_NOTE);
291                m_replace = false;
292            }
293        }
294
295        // search the resources and replace the patterns
296        if (!isError) {
297            List<CmsResource> resources = searchResources();
298
299            if (resources.isEmpty()) {
300                // no resources found, so search is not possible
301                report.println(
302                    Messages.get().container(Messages.RPT_SOURCESEARCH_NO_FILES_TO_SEARCH_IN_0),
303                    I_CmsReport.FORMAT_NOTE);
304            } else {
305
306                report.println(
307                    Messages.get().container(
308                        Messages.RPT_SOURCESEARCH_NR_OF_FILES_TO_SEARCH_IN_1,
309                        Integer.valueOf(resources.size())),
310                    I_CmsReport.FORMAT_NOTE);
311                if (m_replace) {
312                    // start searching and replacing
313                    report.println(
314                        Messages.get().container(Messages.RPT_SOURCESEARCH_START_SEARCHING_REPLACING_0),
315                        I_CmsReport.FORMAT_HEADLINE);
316                } else {
317                    // start searching
318                    report.println(
319                        Messages.get().container(Messages.RPT_SOURCESEARCH_START_SEARCHING_0),
320                        I_CmsReport.FORMAT_HEADLINE);
321                }
322
323                if (m_settings.getType().isPropertySearch()) {
324                    searchProperties(resources);
325                } else {
326                    searchAndReplace(resources);
327                }
328            }
329
330        } else {
331            // do not show the resources, because there were errors while searching
332        }
333
334        report.println(
335            Messages.get().container(Messages.RPT_SOURCESEARCH_END_SEARCH_THREAD_0),
336            I_CmsReport.FORMAT_HEADLINE);
337    }
338
339    /**
340     * Search the resources.<p>
341     *
342     * @param resources the relevant resources
343     */
344    protected void searchAndReplace(List<CmsResource> resources) {
345
346        // the file counter
347        int counter = 0;
348        int resCount = resources.size();
349        I_CmsReport report = getReport();
350        // iterate over the files in the selected path
351        for (CmsResource resource : resources) {
352            if (isAbandoned() && !m_replace) { // if it's not a replace operation, it's safe to cancel
353                return;
354            }
355
356            try {
357
358                // get the content
359                CmsFile file = getCms().readFile(resource);
360                byte[] contents = file.getContents();
361
362                // report the current resource
363                ++counter;
364                report(report, counter, resCount, resource);
365
366                // search and replace
367                byte[] result = null;
368                boolean xpath = false;
369                if ((CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_settings.getXpath())
370                    || m_settings.isOnlyContentValues()) && CmsResourceTypeXmlContent.isXmlContent(resource)) {
371                    xpath = true;
372                }
373                if (!xpath) {
374                    result = replaceInContent(file, contents);
375                } else {
376                    result = replaceInXml(file);
377                }
378
379                if ((result != null) && (contents != null) && !contents.equals(result)) {
380                    // rewrite the content
381                    writeContent(file, result);
382                } else {
383                    getReport().println();
384                }
385
386            } catch (Exception e) {
387                report.print(
388                    org.opencms.report.Messages.get().container(Messages.RPT_SOURCESEARCH_COULD_NOT_READ_FILE_0),
389                    I_CmsReport.FORMAT_ERROR);
390                report.addError(e);
391                report.println(
392                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
393                    I_CmsReport.FORMAT_ERROR);
394                m_errorSearch += 1;
395                LOG.error(
396                    org.opencms.report.Messages.get().container(Messages.RPT_SOURCESEARCH_COULD_NOT_READ_FILE_0),
397                    e);
398                continue;
399            }
400        }
401
402        // report results
403        reportResults(resources.size());
404    }
405
406    /**
407     * Locks the current resource.<p>
408     *
409     * @param cms the current CmsObject
410     * @param cmsResource the resource to lock
411     * @param report the report
412     *
413     * @return <code>true</code> if the given resource was locked was successfully
414     *
415     * @throws CmsException if some goes wrong
416     */
417    private boolean lockResource(CmsObject cms, CmsResource cmsResource, I_CmsReport report) throws CmsException {
418
419        CmsLock lock = cms.getLock(cms.getSitePath(cmsResource));
420        // check the lock
421        if ((lock != null)
422            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
423            && lock.isOwnedInProjectBy(
424                cms.getRequestContext().getCurrentUser(),
425                cms.getRequestContext().getCurrentProject())) {
426            // prove is current lock from current user in current project
427            return true;
428        } else if ((lock != null) && !lock.isUnlocked() && !lock.isOwnedBy(cms.getRequestContext().getCurrentUser())) {
429            // the resource is not locked by the current user, so can not lock it
430            m_lockedFiles += 1;
431            return false;
432        } else if ((lock != null)
433            && !lock.isUnlocked()
434            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
435            && !lock.isOwnedInProjectBy(
436                cms.getRequestContext().getCurrentUser(),
437                cms.getRequestContext().getCurrentProject())) {
438            // prove is current lock from current user but not in current project
439            // file is locked by current user but not in current project
440            // change the lock
441            cms.changeLock(cms.getSitePath(cmsResource));
442        } else 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                Integer.valueOf(nrOfFiles).toString()),
724            I_CmsReport.FORMAT_NOTE);
725        report.println(
726            Messages.get().container(
727                Messages.RPT_SOURCESEARCH_NR_OF_FILES_MATCHED_1,
728                Integer.valueOf(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                Integer.valueOf(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                    Integer.valueOf(m_errorUpdate).toString()),
741                I_CmsReport.FORMAT_NOTE);
742            report.println(
743                Messages.get().container(
744                    Messages.RPT_SOURCESEARCH_LOCKED_FILES_1,
745                    Integer.valueOf(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 (CmsSourceSearchForm.RESOURCE_TYPES_ALL_NON_BINARY.equals(m_settings.getTypes())) {
863                    query.addFilterQuery("type:-(\"image\" OR \"binary\")");
864                } else if ((m_settings.getTypesArray() != null) && (m_settings.getTypesArray().length > 0)) {
865                    query.setResourceTypes(m_settings.getTypesArray());
866                }
867                query.setRows(Integer.valueOf(MAX_PROCESSED_SOLR_RESULTS));
868                query.ensureParameters();
869                try {
870                    resources.addAll(
871                        index.search(getCms(), query, true, null, false, null, MAX_PROCESSED_SOLR_RESULTS));
872                } catch (CmsSearchException e) {
873                    LOG.error(e.getMessage(), e);
874                }
875            }
876        } else {
877            CmsResourceFilter filter = CmsResourceFilter.ALL.addExcludeState(
878                CmsResource.STATE_DELETED).addRequireVisible();
879            List<CmsResourceFilter> filterList = new ArrayList<CmsResourceFilter>();
880            List<Integer> filterByExcludeType = null;
881            if (CmsSourceSearchForm.RESOURCE_TYPES_ALL_NON_BINARY.equals(m_settings.getTypes())) {
882                try {
883                    int typeBinary = OpenCms.getResourceManager().getResourceType("binary").getTypeId();
884                    int typeImage = OpenCms.getResourceManager().getResourceType("image").getTypeId();
885                    filterByExcludeType = Arrays.asList(Integer.valueOf(typeBinary), Integer.valueOf(typeImage));
886                } catch (CmsLoaderException e) {
887                    // noop
888                } catch (NullPointerException e) {
889                    // noop
890                }
891            } else if ((m_settings.getTypesArray() != null) && (m_settings.getTypesArray().length > 0)) {
892                for (String resTypeName : m_settings.getTypesArray()) {
893                    try {
894                        int typeId = OpenCms.getResourceManager().getResourceType(resTypeName).getTypeId();
895                        filterList.add(((CmsResourceFilter)filter.clone()).addRequireType(typeId));
896                    } catch (CmsLoaderException e) {
897                        // noop
898                    } catch (NullPointerException e) {
899                        // noop
900                    }
901                }
902            }
903            if (filterList.size() == 1) {
904                filter = filterList.get(0);
905            }
906
907            // iterate over all selected paths
908            Iterator<String> iterPaths = m_settings.getPaths().iterator();
909
910            if (!m_settings.getType().isPropertySearch()) {
911                filter = filter.addRequireFile();
912            }
913            while (iterPaths.hasNext()) {
914                String path = iterPaths.next();
915                try {
916                    if (m_settings.getType().isPropertySearch()) {
917                        resources.addAll(
918                            getCms().readResourcesWithProperty(path, m_settings.getProperty().getName(), null, filter));
919                    } else {
920                        // only read resources which are files and not deleted, which are in the current time range window and where the current
921                        // user has the sufficient permissions to read them
922
923                        List<CmsResource> tmpResources = getCms().readResources(path, filter);
924                        List<String> subsites = null;
925                        if (m_settings.ignoreSubSites()) {
926                            subsites = OpenCms.getADEManager().getSubSitePaths(getCms(), path);
927                            subsites.remove(
928                                OpenCms.getADEManager().getSubSiteRoot(
929                                    getCms(),
930                                    getCms().readResource(path).getRootPath()));
931                        }
932                        Iterator<CmsResource> iterator = tmpResources.iterator();
933                        while (iterator.hasNext()) {
934                            CmsResource r = iterator.next();
935                            boolean remove = true;
936                            if (null != filterByExcludeType) {
937                                remove = filterByExcludeType.contains(Integer.valueOf(r.getTypeId()));
938                            } else if (filterList.size() > 1) {
939                                for (CmsResourceFilter f : filterList) {
940                                    if (f.isValid(getCms().getRequestContext(), r)) {
941                                        remove = false;
942                                    }
943                                }
944                            } else {
945                                remove = false;
946                            }
947                            if ((subsites != null) & !remove) {
948                                if (subsites.contains(
949                                    OpenCms.getADEManager().getSubSiteRoot(getCms(), r.getRootPath()))) {
950                                    remove = true;
951                                }
952                            }
953                            if (!remove && m_settings.getType().isContentValuesOnly()) {
954                                remove = !(OpenCms.getResourceManager().getResourceType(
955                                    r) instanceof CmsResourceTypeXmlContent);
956                            }
957                            if (remove) {
958                                iterator.remove();
959                            }
960                        }
961
962                        if ((tmpResources != null) && !tmpResources.isEmpty()) {
963                            resources.addAll(tmpResources);
964                        }
965                    }
966                } catch (CmsException e) {
967                    // an error occured
968                    LOG.error(Messages.get().container(Messages.RPT_SOURCESEARCH_ERROR_READING_RESOURCES_1, path), e);
969                    getReport().println(
970                        Messages.get().container(Messages.RPT_SOURCESEARCH_ERROR_READING_RESOURCES_1, path),
971                        I_CmsReport.FORMAT_ERROR);
972                }
973            }
974        }
975        return resources;
976    }
977
978    /**
979     * Writes the file contents.<p>
980     *
981     * @param file the file to write
982     * @param content the file content
983     *
984     * @return success flag
985     */
986    private boolean writeContent(CmsFile file, byte[] content) {
987
988        boolean success = true;
989        I_CmsReport report = getReport();
990        CmsObject cmsObject = getCms();
991        // get current lock from file
992        try {
993            // try to lock the resource
994            if (!lockResource(cmsObject, file, report)) {
995                report.println(
996                    Messages.get().container(Messages.RPT_SOURCESEARCH_LOCKED_FILE_0, cmsObject.getSitePath(file)),
997                    I_CmsReport.FORMAT_ERROR);
998                success = false;
999            }
1000        } catch (CmsException e) {
1001            report.println(
1002                Messages.get().container(Messages.RPT_SOURCESEARCH_LOCKED_FILE_0, cmsObject.getSitePath(file)),
1003                I_CmsReport.FORMAT_ERROR);
1004            if (LOG.isErrorEnabled()) {
1005                LOG.error(e.getMessageContainer(), e);
1006            }
1007            success = false;
1008        }
1009
1010        // write the file content
1011        try {
1012            file.setContents(content);
1013            cmsObject.writeFile(file);
1014        } catch (Exception e) {
1015            m_errorUpdate += 1;
1016            report.println(
1017                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
1018                I_CmsReport.FORMAT_ERROR);
1019            if (LOG.isErrorEnabled()) {
1020                LOG.error(e.toString());
1021            }
1022            success = false;
1023        }
1024
1025        // unlock the resource
1026        try {
1027            cmsObject.unlockResource(cmsObject.getSitePath(file));
1028        } catch (CmsException e) {
1029            m_errorUpdate += 1;
1030            report.println(
1031                Messages.get().container(Messages.RPT_SOURCESEARCH_UNLOCK_FILE_0),
1032                I_CmsReport.FORMAT_WARNING);
1033            if (LOG.isErrorEnabled()) {
1034                LOG.error(e.getMessageContainer(), e);
1035            }
1036            success = false;
1037        }
1038
1039        if (success) {
1040            // successfully updated
1041            report.println(
1042                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
1043                I_CmsReport.FORMAT_OK);
1044        }
1045
1046        return success;
1047    }
1048}