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}