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.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 javax.servlet.http.HttpSession;
076
077import org.apache.commons.logging.Log;
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
443            if ((lock != null) && lock.isUnlocked()) {
444                // lock resource from current user in current project
445                cms.lockResource(cms.getSitePath(cmsResource));
446            }
447        lock = cms.getLock(cms.getSitePath(cmsResource));
448        if ((lock != null)
449            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
450            && !lock.isOwnedInProjectBy(
451                cms.getRequestContext().getCurrentUser(),
452                cms.getRequestContext().getCurrentProject())) {
453            // resource could not be locked
454            m_lockedFiles += 1;
455
456            return false;
457        }
458        // resource is locked successfully
459        return true;
460    }
461
462    /**
463     * Renames a nested container within a container page XML.<p>
464     *
465     * @param targetContainerPage the target container page
466     * @param layoutResource the container element resource generating the nested container
467     * @param oldName the old container name
468     * @param newName the new container name
469     *
470     * @return the changed content bytes
471     *
472     * @throws Exception in case unmarshalling of the container page fails
473     */
474    private byte[] renameNestedContainers(
475        CmsFile targetContainerPage,
476        CmsResource layoutResource,
477        String oldName,
478        String newName)
479    throws Exception {
480
481        byte[] contents = targetContainerPage.getContents();
482        Set<String> replaceElementIds = new HashSet<String>();
483        try {
484            CmsXmlContainerPage page = CmsXmlContainerPageFactory.unmarshal(getCms(), targetContainerPage);
485            for (CmsContainerElementBean element : page.getContainerPage(getCms()).getElements()) {
486                if (element.getId().equals(layoutResource.getStructureId()) && (element.getInstanceId() != null)) {
487                    replaceElementIds.add(element.getInstanceId());
488                }
489            }
490            if (replaceElementIds.size() > 0) {
491                String encoding = CmsLocaleManager.getResourceEncoding(getCms(), targetContainerPage);
492                String content = new String(contents, encoding);
493                for (String instanceId : replaceElementIds) {
494                    Pattern patt = Pattern.compile(
495                        CmsJspTagContainer.getNestedContainerName(oldName, instanceId, null));
496                    Matcher m = patt.matcher(content);
497                    StringBuffer sb = new StringBuffer(content.length());
498                    while (m.find()) {
499                        m.appendReplacement(
500                            sb,
501                            Matcher.quoteReplacement(
502                                CmsJspTagContainer.getNestedContainerName(newName, instanceId, null)));
503                    }
504                    m.appendTail(sb);
505                    content = sb.toString();
506                }
507                contents = content.getBytes(encoding);
508            }
509        } catch (Exception e) {
510            LOG.error(e.getLocalizedMessage(), e);
511            throw e;
512        }
513        return contents;
514    }
515
516    /**
517     * Performs the replacement in content.<p>
518     *
519     * @param file the file object
520     * @param contents the byte content
521     *
522     * @return the new content if a replacement has been performed
523     *
524     * @throws Exception if something goes wrong
525     */
526    private byte[] replaceInContent(CmsFile file, byte[] contents) throws Exception {
527
528        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_settings.getLocale())) {
529            Locale contentLocale = CmsLocaleManager.getMainLocale(getCms(), file);
530            if (!contentLocale.toString().equalsIgnoreCase(m_settings.getLocale())) {
531                // content does not match the requested locale, skip it
532                getReport().println(
533                    Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
534                    I_CmsReport.FORMAT_NOTE);
535                return null;
536            }
537        }
538
539        String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
540        String content = new String(contents, encoding);
541
542        if (CmsSourceSearchForm.REGEX_ALL.equals(m_settings.getSearchpattern()) & !m_replace) {
543            m_matchedResources.add(file);
544            getReport().print(Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0), I_CmsReport.FORMAT_OK);
545            return null;
546        }
547
548        Matcher matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(content);
549
550        if (matcher.find()) {
551            // search pattern did match here, so take this file in the list with matches resources
552            m_matchedResources.add(file);
553            getReport().print(Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0), I_CmsReport.FORMAT_OK);
554            if (m_replace) {
555                if (m_settings.getType().equals(SearchType.renameContainer)) {
556
557                    return renameNestedContainers(
558                        file,
559                        m_settings.getElementResource(),
560                        m_settings.getReplacepattern().split(";")[0],
561                        m_settings.getReplacepattern().split(";")[1]);
562                }
563                return matcher.replaceAll(m_settings.getReplacepattern()).getBytes(encoding);
564            }
565        } else {
566            // search pattern did not match
567            getReport().print(
568                Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
569                I_CmsReport.FORMAT_NOTE);
570        }
571        return null;
572    }
573
574    /**
575     * Performs a replacement for XML contents.<p>
576     *
577     * @param cmsFile the file to operate on
578     *
579     * @return the marshaled content
580     * @throws Exception if something goes wrong
581     */
582    private byte[] replaceInXml(CmsFile cmsFile) throws Exception {
583
584        Exception e = null;
585        CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), cmsFile);
586        Pattern pattern = Pattern.compile(m_settings.getSearchpattern());
587        // loop over the locales of the content
588        boolean modified = false;
589        boolean matched = false;
590        String requestedLocale = m_settings.getLocale();
591        for (Locale locale : xmlContent.getLocales()) {
592            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(requestedLocale)
593                && !locale.toString().equalsIgnoreCase(requestedLocale)) {
594                // does not match the requested locale, skip it
595                continue;
596            }
597            // loop over the available element paths of the current content locale
598            List<String> paths = xmlContent.getNames(locale);
599            for (String xpath : paths) {
600                // try to get the value extraction for the current element path
601                I_CmsXmlContentValue value = xmlContent.getValue(xpath, locale);
602                if (value.isSimpleType()) {
603                    try {
604                        String currPath = value.getPath();
605                        if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_settings.getXpath())
606                            || currPath.equals(m_settings.getXpath())
607                            || (CmsXmlUtils.removeXpath(currPath).equals(m_settings.getXpath()))) {
608                            // xpath match
609                            String oldVal = value.getStringValue(getCms());
610                            Matcher matcher = pattern.matcher(oldVal);
611                            matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(oldVal);
612                            if (matcher.find()) {
613                                matched = true;
614                                m_matchedResources.add(cmsFile);
615                                if (m_replace) {
616                                    String newVal = matcher.replaceAll(m_settings.getReplacepattern());
617                                    if (!oldVal.equals(newVal)) {
618                                        value.setStringValue(getCms(), newVal);
619                                        modified = true;
620                                    }
621                                }
622                            }
623                        }
624                    } catch (Exception ex) {
625                        // log and go on
626                        LOG.error(ex.getMessage(), ex);
627                        e = ex;
628                    }
629                }
630            }
631        }
632        if (e != null) {
633            throw e;
634        }
635        if (matched) {
636            getReport().println(Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0), I_CmsReport.FORMAT_OK);
637        } else {
638            getReport().println(
639                Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
640                I_CmsReport.FORMAT_NOTE);
641        }
642        if (modified) {
643            return xmlContent.marshal();
644        }
645        return null;
646    }
647
648    /**
649     * Replace properties of given resources.<p>
650     *
651     * @param matchedResources to replace properties
652     */
653    private void replaceProperties(Set<CmsResource> matchedResources) {
654
655        for (CmsResource resource : matchedResources) {
656            try {
657                CmsProperty prop = getCms().readPropertyObject(resource, m_settings.getProperty().getName(), false);
658                Matcher matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(prop.getValue());
659                if (m_settings.getReplacepattern().isEmpty()) {
660                    prop.setValue("", "");
661                } else {
662                    prop.setValue(matcher.replaceAll(m_settings.getReplacepattern()), CmsProperty.TYPE_INDIVIDUAL);
663                }
664                getCms().lockResource(resource);
665                getCms().writePropertyObjects(resource, Collections.singletonList(prop));
666                getCms().unlockResource(resource);
667            } catch (CmsException e) {
668                LOG.error("Ubable to change property", e);
669            }
670        }
671    }
672
673    /**
674     * Reads the content as byte array of the given resource and prints a message to the report.<p>
675     *
676     * @param report the report
677     * @param counter the counter
678     * @param resCount the total resource count
679     * @param resource the file to get the content for
680     */
681    private void report(I_CmsReport report, int counter, int resCount, CmsResource resource) {
682
683        // report entries
684        report.print(
685            org.opencms.report.Messages.get().container(
686                org.opencms.report.Messages.RPT_SUCCESSION_2,
687                String.valueOf(counter),
688                String.valueOf(resCount)),
689            I_CmsReport.FORMAT_NOTE);
690        report.print(
691            org.opencms.report.Messages.get().container(
692                org.opencms.report.Messages.RPT_ARGUMENT_1,
693                report.removeSiteRoot(resource.getRootPath())));
694        report.print(
695            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0),
696            I_CmsReport.FORMAT_DEFAULT);
697    }
698
699    /**
700     * Prints the result messages into the report.<p>
701     *
702     * @param nrOfFiles the total number of files
703     */
704    private void reportResults(int nrOfFiles) {
705
706        I_CmsReport report = getReport();
707        // report entries
708        if (m_replace) {
709            // finish searching and replacing
710            report.println(
711                Messages.get().container(Messages.RPT_SOURCESEARCH_END_SEARCHING_REPLACING_0),
712                I_CmsReport.FORMAT_HEADLINE);
713        } else {
714            // finish searching
715            report.println(
716                Messages.get().container(Messages.RPT_SOURCESEARCH_END_SEARCHING_0),
717                I_CmsReport.FORMAT_HEADLINE);
718        }
719        // the results are written in the report
720        report.println(Messages.get().container(Messages.RPT_SOURCESEARCH_RESULT_0), I_CmsReport.FORMAT_HEADLINE);
721        report.println(
722            Messages.get().container(
723                Messages.RPT_SOURCESEARCH_NR_OF_FILES_TO_SEARCH_IN_1,
724                Integer.valueOf(nrOfFiles).toString()),
725            I_CmsReport.FORMAT_NOTE);
726        report.println(
727            Messages.get().container(
728                Messages.RPT_SOURCESEARCH_NR_OF_FILES_MATCHED_1,
729                Integer.valueOf(m_matchedResources.size()).toString()),
730            I_CmsReport.FORMAT_NOTE);
731        report.println(
732            Messages.get().container(
733                Messages.RPT_SOURCESEARCH_SEARCH_ERROR_COUNT_1,
734                Integer.valueOf(m_errorSearch).toString()),
735            I_CmsReport.FORMAT_NOTE);
736        if (m_replace) {
737            // replace report entries
738            report.println(
739                Messages.get().container(
740                    Messages.RPT_SOURCESEARCH_REPLACE_ERROR_COUNT_1,
741                    Integer.valueOf(m_errorUpdate).toString()),
742                I_CmsReport.FORMAT_NOTE);
743            report.println(
744                Messages.get().container(
745                    Messages.RPT_SOURCESEARCH_LOCKED_FILES_1,
746                    Integer.valueOf(m_lockedFiles).toString()),
747                I_CmsReport.FORMAT_NOTE);
748            if (m_matchedResources.size() == 0) {
749                report.println(
750                    Messages.get().container(Messages.RPT_SOURCESEARCH_NO_FILES_FOUND_0),
751                    I_CmsReport.FORMAT_OK);
752            } else {
753                report.println(
754                    Messages.get().container(Messages.RPT_SOURCESEARCH_CLICK_OK_TO_GET_LIST_0),
755                    I_CmsReport.FORMAT_OK);
756            }
757            if (m_lockedFiles > 0) {
758                report.println(
759                    Messages.get().container(Messages.RPT_SOURCESEARCH_REPLACE_FAILED_0),
760                    I_CmsReport.FORMAT_ERROR);
761            } else {
762                report.println(
763                    Messages.get().container(Messages.RPT_SOURCESEARCH_REPLACE_SUCCESS_0),
764                    I_CmsReport.FORMAT_OK);
765            }
766        } else {
767            // search report entries
768            if (m_matchedResources.size() == 0) {
769                report.println(
770                    Messages.get().container(Messages.RPT_SOURCESEARCH_NO_FILES_FOUND_0),
771                    I_CmsReport.FORMAT_OK);
772            } else {
773                report.println(
774                    Messages.get().container(Messages.RPT_SOURCESEARCH_CLICK_OK_TO_GET_LIST_0),
775                    I_CmsReport.FORMAT_OK);
776            }
777            if (m_errorSearch > 0) {
778                // only searching failed
779                report.println(
780                    Messages.get().container(Messages.RPT_SOURCESEARCH_SEARCH_FAILED_0),
781                    I_CmsReport.FORMAT_ERROR);
782            } else {
783                // only searching was successful
784                report.println(
785                    Messages.get().container(Messages.RPT_SOURCESEARCH_SEARCH_SUCCESS_0),
786                    I_CmsReport.FORMAT_OK);
787            }
788        }
789    }
790
791    /**
792     * Search and replace function for properties.<p>
793     *
794     * @param resources to be considered
795     */
796    private void searchProperties(List<CmsResource> resources) {
797
798        if (CmsSourceSearchForm.REGEX_ALL.equals(m_settings.getSearchpattern())) {
799            for (CmsResource resource : resources) {
800                m_matchedResources.add(resource);
801                getReport().println(
802                    Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0),
803                    I_CmsReport.FORMAT_OK);
804            }
805
806        } else {
807            for (CmsResource resource : resources) {
808                if (isAbandoned() && !m_replace) { // if it's not a replace operation, it's safe to cancel
809                    return;
810                }
811                Matcher matcher;
812                try {
813                    CmsProperty prop = getCms().readPropertyObject(resource, m_settings.getProperty().getName(), false);
814                    matcher = Pattern.compile(m_settings.getSearchpattern()).matcher(prop.getValue());
815                    if (matcher.find()) {
816                        m_matchedResources.add(resource);
817                        getReport().println(
818                            Messages.get().container(Messages.RPT_SOURCESEARCH_MATCHED_0),
819                            I_CmsReport.FORMAT_OK);
820                    } else {
821                        getReport().println(
822                            Messages.get().container(Messages.RPT_SOURCESEARCH_NOT_MATCHED_0),
823                            I_CmsReport.FORMAT_NOTE);
824                    }
825
826                } catch (CmsException e) {
827                    LOG.error("Ubable to read property", e);
828                }
829            }
830        }
831        if (m_replace) {
832            replaceProperties(m_matchedResources);
833        }
834        // report results
835        reportResults(resources.size());
836    }
837
838    /**
839     * Searches/reads all resources that are relevant.<p>
840     *
841     * @return the relevant resources
842     */
843    @SuppressWarnings("deprecation")
844    private List<CmsResource> searchResources() {
845
846        getReport().println(
847            Messages.get().container(Messages.RPT_SOURCESEARCH_START_COLLECTING_FILES_TO_SEARCH_IN_0),
848            I_CmsReport.FORMAT_HEADLINE);
849
850        List<CmsResource> resources = new ArrayList<CmsResource>();
851        if (m_settings.isSolrSearch()) {
852            CmsSolrIndex index = OpenCms.getSearchManager().getIndexSolr(m_settings.getSource());
853            if (index != null) {
854                CmsSolrQuery query = new CmsSolrQuery(
855                    null,
856                    CmsRequestUtil.createParameterMap(m_settings.getQuery() + "&fl=path,type"));
857                List<String> rootPaths = new ArrayList<>(m_settings.getPaths().size());
858                String siteRoot = getCms().getRequestContext().getSiteRoot();
859                for (String path : m_settings.getPaths()) {
860                    rootPaths.add(path.startsWith(siteRoot) ? path : getCms().addSiteRoot(path));
861                }
862                query.setSearchRoots(rootPaths);
863                if (CmsSourceSearchForm.RESOURCE_TYPES_ALL_NON_BINARY.equals(m_settings.getTypes())) {
864                    query.addFilterQuery("type:-(\"image\" OR \"binary\")");
865                } else if ((m_settings.getTypesArray() != null) && (m_settings.getTypesArray().length > 0)) {
866                    query.setResourceTypes(m_settings.getTypesArray());
867                }
868                query.setRows(Integer.valueOf(MAX_PROCESSED_SOLR_RESULTS));
869                query.ensureParameters();
870                try {
871                    resources.addAll(
872                        index.search(getCms(), query, true, null, false, null, MAX_PROCESSED_SOLR_RESULTS));
873                } catch (CmsSearchException e) {
874                    LOG.error(e.getMessage(), e);
875                }
876            }
877        } else {
878            CmsResourceFilter filter = CmsResourceFilter.ALL.addExcludeState(
879                CmsResource.STATE_DELETED).addRequireVisible();
880            List<CmsResourceFilter> filterList = new ArrayList<CmsResourceFilter>();
881            List<Integer> filterByExcludeType = null;
882            if (CmsSourceSearchForm.RESOURCE_TYPES_ALL_NON_BINARY.equals(m_settings.getTypes())) {
883                try {
884                    int typeBinary = OpenCms.getResourceManager().getResourceType("binary").getTypeId();
885                    int typeImage = OpenCms.getResourceManager().getResourceType("image").getTypeId();
886                    filterByExcludeType = Arrays.asList(Integer.valueOf(typeBinary), Integer.valueOf(typeImage));
887                } catch (CmsLoaderException e) {
888                    // noop
889                } catch (NullPointerException e) {
890                    // noop
891                }
892            } else if ((m_settings.getTypesArray() != null) && (m_settings.getTypesArray().length > 0)) {
893                for (String resTypeName : m_settings.getTypesArray()) {
894                    try {
895                        int typeId = OpenCms.getResourceManager().getResourceType(resTypeName).getTypeId();
896                        filterList.add(((CmsResourceFilter)filter.clone()).addRequireType(typeId));
897                    } catch (CmsLoaderException e) {
898                        // noop
899                    } catch (NullPointerException e) {
900                        // noop
901                    }
902                }
903            }
904            if (filterList.size() == 1) {
905                filter = filterList.get(0);
906            }
907
908            // iterate over all selected paths
909            Iterator<String> iterPaths = m_settings.getPaths().iterator();
910
911            if (!m_settings.getType().isPropertySearch()) {
912                filter = filter.addRequireFile();
913            }
914            while (iterPaths.hasNext()) {
915                String path = iterPaths.next();
916                try {
917                    if (m_settings.getType().isPropertySearch()) {
918                        resources.addAll(
919                            getCms().readResourcesWithProperty(path, m_settings.getProperty().getName(), null, filter));
920                    } else {
921                        // only read resources which are files and not deleted, which are in the current time range window and where the current
922                        // user has the sufficient permissions to read them
923
924                        List<CmsResource> tmpResources = getCms().readResources(path, filter);
925                        List<String> subsites = null;
926                        if (m_settings.ignoreSubSites()) {
927                            subsites = OpenCms.getADEManager().getSubSitePaths(getCms(), path);
928                            subsites.remove(
929                                OpenCms.getADEManager().getSubSiteRoot(
930                                    getCms(),
931                                    getCms().readResource(path).getRootPath()));
932                        }
933                        Iterator<CmsResource> iterator = tmpResources.iterator();
934                        while (iterator.hasNext()) {
935                            CmsResource r = iterator.next();
936                            boolean remove = true;
937                            if (null != filterByExcludeType) {
938                                remove = filterByExcludeType.contains(Integer.valueOf(r.getTypeId()));
939                            } else if (filterList.size() > 1) {
940                                for (CmsResourceFilter f : filterList) {
941                                    if (f.isValid(getCms().getRequestContext(), r)) {
942                                        remove = false;
943                                    }
944                                }
945                            } else {
946                                remove = false;
947                            }
948                            if ((subsites != null) & !remove) {
949                                if (subsites.contains(
950                                    OpenCms.getADEManager().getSubSiteRoot(getCms(), r.getRootPath()))) {
951                                    remove = true;
952                                }
953                            }
954                            if (!remove && m_settings.getType().isContentValuesOnly()) {
955                                remove = !(OpenCms.getResourceManager().getResourceType(
956                                    r) instanceof CmsResourceTypeXmlContent);
957                            }
958                            if (remove) {
959                                iterator.remove();
960                            }
961                        }
962
963                        if ((tmpResources != null) && !tmpResources.isEmpty()) {
964                            resources.addAll(tmpResources);
965                        }
966                    }
967                } catch (CmsException e) {
968                    // an error occured
969                    LOG.error(Messages.get().container(Messages.RPT_SOURCESEARCH_ERROR_READING_RESOURCES_1, path), e);
970                    getReport().println(
971                        Messages.get().container(Messages.RPT_SOURCESEARCH_ERROR_READING_RESOURCES_1, path),
972                        I_CmsReport.FORMAT_ERROR);
973                }
974            }
975        }
976        return resources;
977    }
978
979    /**
980     * Writes the file contents.<p>
981     *
982     * @param file the file to write
983     * @param content the file content
984     *
985     * @return success flag
986     */
987    private boolean writeContent(CmsFile file, byte[] content) {
988
989        boolean success = true;
990        I_CmsReport report = getReport();
991        CmsObject cmsObject = getCms();
992        // get current lock from file
993        try {
994            // try to lock the resource
995            if (!lockResource(cmsObject, file, report)) {
996                report.println(
997                    Messages.get().container(Messages.RPT_SOURCESEARCH_LOCKED_FILE_0, cmsObject.getSitePath(file)),
998                    I_CmsReport.FORMAT_ERROR);
999                success = false;
1000            }
1001        } catch (CmsException e) {
1002            report.println(
1003                Messages.get().container(Messages.RPT_SOURCESEARCH_LOCKED_FILE_0, cmsObject.getSitePath(file)),
1004                I_CmsReport.FORMAT_ERROR);
1005            if (LOG.isErrorEnabled()) {
1006                LOG.error(e.getMessageContainer(), e);
1007            }
1008            success = false;
1009        }
1010
1011        // write the file content
1012        try {
1013            file.setContents(content);
1014            cmsObject.writeFile(file);
1015        } catch (Exception e) {
1016            m_errorUpdate += 1;
1017            report.println(
1018                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
1019                I_CmsReport.FORMAT_ERROR);
1020            if (LOG.isErrorEnabled()) {
1021                LOG.error(e.toString());
1022            }
1023            success = false;
1024        }
1025
1026        // unlock the resource
1027        try {
1028            cmsObject.unlockResource(cmsObject.getSitePath(file));
1029        } catch (CmsException e) {
1030            m_errorUpdate += 1;
1031            report.println(
1032                Messages.get().container(Messages.RPT_SOURCESEARCH_UNLOCK_FILE_0),
1033                I_CmsReport.FORMAT_WARNING);
1034            if (LOG.isErrorEnabled()) {
1035                LOG.error(e.getMessageContainer(), e);
1036            }
1037            success = false;
1038        }
1039
1040        if (success) {
1041            // successfully updated
1042            report.println(
1043                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
1044                I_CmsReport.FORMAT_OK);
1045        }
1046
1047        return success;
1048    }
1049}