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}