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.synchronize; 029 030import org.opencms.db.CmsDbIoException; 031import org.opencms.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProperty; 034import org.opencms.file.CmsPropertyDefinition; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.types.CmsResourceTypeFolder; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.main.OpenCms; 041import org.opencms.report.I_CmsReport; 042import org.opencms.util.CmsFileUtil; 043 044import java.io.DataOutputStream; 045import java.io.File; 046import java.io.FileOutputStream; 047import java.io.FileReader; 048import java.io.IOException; 049import java.io.LineNumberReader; 050import java.io.PrintWriter; 051import java.util.ArrayList; 052import java.util.HashMap; 053import java.util.Iterator; 054import java.util.List; 055import java.util.StringTokenizer; 056import java.util.regex.Pattern; 057 058import org.apache.commons.logging.Log; 059 060/** 061 * Contains all methods to synchronize the VFS with the "real" FS.<p> 062 * 063 * @since 6.0.0 064 */ 065public class CmsSynchronize { 066 067 /** Flag to import a deleted resource in the VFS. */ 068 static final int DELETE_VFS = 3; 069 070 /** Flag to export a resource from the VFS to the FS. */ 071 static final int EXPORT_VFS = 1; 072 073 /** File name of the synclist file on the server FS. */ 074 static final String SYNCLIST_FILENAME = "#synclist.txt"; 075 076 /** Flag to import a resource from the FS to the VFS. */ 077 static final int UPDATE_VFS = 2; 078 079 /** The log object for this class. */ 080 private static final Log LOG = CmsLog.getLog(CmsSynchronize.class); 081 082 /** List to store all file modification interface implementations. */ 083 private static List<I_CmsSynchronizeModification> m_synchronizeModifications = new ArrayList<I_CmsSynchronizeModification>(); 084 085 /** The CmsObject. */ 086 private CmsObject m_cms; 087 088 /** Counter for logging. */ 089 private int m_count; 090 091 /** The path in the "real" file system where the resources have to be synchronized to. */ 092 private String m_destinationPathInRfs; 093 094 /** Hash map for the new synchronization list of the current sync process. */ 095 private HashMap<String, CmsSynchronizeList> m_newSyncList; 096 097 /** The report to write the output to. */ 098 private I_CmsReport m_report; 099 100 /** Hash map for the synchronization list of the last sync process. */ 101 private HashMap<String, CmsSynchronizeList> m_syncList; 102 103 /** 104 * Creates a new CmsSynchronize object which automatically start the 105 * synchronization process.<p> 106 * 107 * @param cms the current CmsObject 108 * @param settings the synchronization settings to use 109 * @param report the report to write the output to 110 * 111 * @throws CmsException if something goes wrong 112 * @throws CmsSynchronizeException if the synchronization process cannot be started 113 */ 114 public CmsSynchronize(CmsObject cms, CmsSynchronizeSettings settings, I_CmsReport report) 115 throws CmsSynchronizeException, CmsException { 116 117 // TODO: initialize the list of file modification interface implementations 118 119 // do the synchronization only if the synchronization folders in the VFS 120 // and the FS are valid 121 if ((settings != null) && (settings.isSyncEnabled())) { 122 123 // create an independent copy of the provided user context 124 m_cms = OpenCms.initCmsObject(cms); 125 // set site to root site 126 m_cms.getRequestContext().setSiteRoot("/"); 127 128 m_report = report; 129 m_count = 1; 130 131 // get the destination folder 132 m_destinationPathInRfs = settings.getDestinationPathInRfs(); 133 134 // check if target folder exists and is writable 135 File destinationFolder = new File(m_destinationPathInRfs); 136 if (!destinationFolder.exists() || !destinationFolder.isDirectory()) { 137 // destination folder does not exist 138 throw new CmsSynchronizeException( 139 Messages.get().container(Messages.ERR_RFS_DESTINATION_NOT_THERE_1, m_destinationPathInRfs)); 140 } 141 if (!destinationFolder.canWrite()) { 142 // destination folder can't be written to 143 throw new CmsSynchronizeException( 144 Messages.get().container(Messages.ERR_RFS_DESTINATION_NO_WRITE_1, m_destinationPathInRfs)); 145 } 146 147 // create the sync list for this run 148 m_syncList = readSyncList(); 149 m_newSyncList = new HashMap<String, CmsSynchronizeList>(); 150 151 Iterator<String> i = settings.getSourceListInVfs().iterator(); 152 while (i.hasNext()) { 153 // iterate all source folders 154 String sourcePathInVfs = i.next(); 155 String destPath = m_destinationPathInRfs + sourcePathInVfs.replace('/', File.separatorChar); 156 157 report.println( 158 org.opencms.workplace.threads.Messages.get().container( 159 org.opencms.workplace.threads.Messages.RPT_SYNCHRONIZE_FOLDERS_2, 160 sourcePathInVfs, 161 destPath), 162 I_CmsReport.FORMAT_HEADLINE); 163 // synchronize the VFS and the RFS 164 syncVfsToRfs(sourcePathInVfs); 165 } 166 167 // remove files from the RFS 168 removeFromRfs(m_destinationPathInRfs); 169 i = settings.getSourceListInVfs().iterator(); 170 171 while (i.hasNext()) { 172 // add new files from the RFS 173 copyFromRfs(i.next()); 174 } 175 176 // write the sync list 177 writeSyncList(); 178 179 // free memory 180 m_syncList = null; 181 m_newSyncList = null; 182 m_cms = null; 183 } else { 184 throw new CmsSynchronizeException(Messages.get().container(Messages.ERR_INIT_SYNC_0)); 185 } 186 } 187 188 /** 189 * Returns the count.<p> 190 * 191 * @return the count 192 */ 193 public int getCount() { 194 195 return m_count; 196 } 197 198 /** 199 * Copys all resources from the FS which are not existing in the VFS yet. <p> 200 * 201 * @param folder the folder in the VFS to be synchronized with the FS 202 * @throws CmsException if something goes wrong 203 */ 204 private void copyFromRfs(String folder) throws CmsException { 205 206 // get the corresponding folder in the FS 207 File[] res; 208 File fsFile = getFileInRfs(folder); 209 // first of all, test if this folder existis in the VFS. If not, create it 210 try { 211 m_cms.readFolder(translate(folder), CmsResourceFilter.IGNORE_EXPIRATION); 212 } catch (CmsException e) { 213 // the folder could not be read, so create it 214 String foldername = translate(folder); 215 m_report.print( 216 org.opencms.report.Messages.get().container( 217 org.opencms.report.Messages.RPT_SUCCESSION_1, 218 String.valueOf(m_count++)), 219 I_CmsReport.FORMAT_NOTE); 220 m_report.print(Messages.get().container(Messages.RPT_IMPORT_FOLDER_0), I_CmsReport.FORMAT_NOTE); 221 m_report.print( 222 org.opencms.report.Messages.get().container( 223 org.opencms.report.Messages.RPT_ARGUMENT_1, 224 fsFile.getAbsolutePath().replace('\\', '/'))); 225 m_report.print(Messages.get().container(Messages.RPT_FROM_FS_TO_0), I_CmsReport.FORMAT_NOTE); 226 m_report.print( 227 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1, foldername)); 228 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 229 230 CmsResource newFolder = m_cms.createResource(foldername, CmsResourceTypeFolder.RESOURCE_TYPE_ID); 231 // now check if there is some external method to be called which 232 // should modify the imported resource in the VFS 233 Iterator<I_CmsSynchronizeModification> i = m_synchronizeModifications.iterator(); 234 while (i.hasNext()) { 235 try { 236 i.next().modifyVfs(m_cms, newFolder, fsFile); 237 } catch (CmsSynchronizeException e1) { 238 break; 239 } 240 } 241 // we have to read the new resource again, to get the correct timestamp 242 newFolder = m_cms.readFolder(foldername, CmsResourceFilter.IGNORE_EXPIRATION); 243 String resourcename = m_cms.getSitePath(newFolder); 244 // add the folder to the sync list 245 CmsSynchronizeList sync = new CmsSynchronizeList( 246 folder, 247 resourcename, 248 newFolder.getDateLastModified(), 249 fsFile.lastModified()); 250 m_newSyncList.put(resourcename, sync); 251 252 m_report.println( 253 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 254 I_CmsReport.FORMAT_OK); 255 } 256 // since the check has been done on folder basis, this must be a folder 257 if (fsFile.isDirectory()) { 258 // get all resources in this folder 259 res = fsFile.listFiles(); 260 261 // now loop through all resources 262 for (int i = 0; i < res.length; i++) { 263 if (isExcluded(res[i])) { 264 continue; 265 } 266 // get the relative filename 267 String resname = res[i].getAbsolutePath(); 268 resname = resname.substring(m_destinationPathInRfs.length()); 269 // translate the folder separator if necessary 270 resname = resname.replace(File.separatorChar, '/'); 271 // now check if this resource was already processed, by looking 272 // up the new sync list 273 if (res[i].isFile()) { 274 if (!m_newSyncList.containsKey(translate(resname))) { 275 // this file does not exist in the VFS, so import it 276 importToVfs(res[i], resname, folder); 277 } 278 } else { 279 // do a recursion if the current resource is a folder 280 copyFromRfs(resname + "/"); 281 } 282 } 283 } 284 } 285 286 /** 287 * Creates a new file on the server FS.<p> 288 * 289 * @param newFile the file that has to be created 290 * @throws CmsException if something goes wrong 291 */ 292 private void createNewLocalFile(File newFile) throws CmsException { 293 294 if (newFile.exists()) { 295 throw new CmsSynchronizeException( 296 Messages.get().container(Messages.ERR_EXISTENT_FILE_1, newFile.getPath())); 297 } 298 FileOutputStream fOut = null; 299 try { 300 File parentFolder = new File( 301 newFile.getPath().replace('/', File.separatorChar).substring( 302 0, 303 newFile.getPath().lastIndexOf(File.separator))); 304 parentFolder.mkdirs(); 305 if (parentFolder.exists()) { 306 fOut = new FileOutputStream(newFile); 307 } else { 308 throw new CmsSynchronizeException( 309 Messages.get().container(Messages.ERR_CREATE_DIR_1, newFile.getPath())); 310 } 311 } catch (IOException e) { 312 throw new CmsSynchronizeException( 313 Messages.get().container(Messages.ERR_CREATE_FILE_1, this.getClass().getName(), newFile.getPath()), 314 e); 315 } finally { 316 if (fOut != null) { 317 try { 318 fOut.close(); 319 } catch (IOException e) { 320 // ignore 321 } 322 } 323 } 324 } 325 326 /** 327 * Deletes a resource in the VFS and updates the synchronisation lists.<p> 328 * 329 * @param res The resource to be deleted 330 * 331 * @throws CmsException if something goes wrong 332 */ 333 private void deleteFromVfs(CmsResource res) throws CmsException { 334 335 String resourcename = m_cms.getSitePath(res); 336 337 m_report.print( 338 org.opencms.report.Messages.get().container( 339 org.opencms.report.Messages.RPT_SUCCESSION_1, 340 String.valueOf(m_count++)), 341 I_CmsReport.FORMAT_NOTE); 342 if (res.isFile()) { 343 m_report.print(Messages.get().container(Messages.RPT_DEL_FILE_0), I_CmsReport.FORMAT_NOTE); 344 } else { 345 m_report.print(Messages.get().container(Messages.RPT_DEL_FOLDER_0), I_CmsReport.FORMAT_NOTE); 346 } 347 m_report.print( 348 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1, resourcename)); 349 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 350 351 // lock the file in the VFS, so that it can be updated 352 m_cms.lockResource(resourcename); 353 m_cms.deleteResource(resourcename, CmsResource.DELETE_PRESERVE_SIBLINGS); 354 // Remove it from the sync list 355 m_syncList.remove(translate(resourcename)); 356 357 m_report.println( 358 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 359 I_CmsReport.FORMAT_OK); 360 } 361 362 /** 363 * Exports a resource from the VFS to the FS and updates the 364 * synchronization lists.<p> 365 * 366 * @param res the resource to be exported 367 * 368 * @throws CmsException if something goes wrong 369 */ 370 private void exportToRfs(CmsResource res) throws CmsException { 371 372 CmsFile vfsFile; 373 File fsFile; 374 String resourcename; 375 // to get the name of the file in the FS, we must look it up in the 376 // sync list. This is necessary, since the VFS could use a translated 377 // filename. 378 CmsSynchronizeList sync = m_syncList.get(translate(m_cms.getSitePath(res))); 379 // if no entry in the sync list was found, its a new resource and we 380 // can use the name of the VFS resource. 381 if (sync != null) { 382 resourcename = sync.getResName(); 383 } else { 384 // otherwise use the original non-translated name 385 resourcename = m_cms.getSitePath(res); 386 387 // the parent folder could contain a translated names as well, so 388 // make a lookup in the sync list to get its original 389 // non-translated name 390 String parent = CmsResource.getParentFolder(resourcename); 391 CmsSynchronizeList parentSync = m_newSyncList.get(parent); 392 // use the non-translated pathname 393 if (parentSync != null) { 394 resourcename = parentSync.getResName() + res.getName(); 395 } 396 } 397 if ((res.isFolder()) && (!resourcename.endsWith("/"))) { 398 resourcename += "/"; 399 } 400 fsFile = getFileInRfs(resourcename); 401 402 try { 403 // if the resource is marked for deletion, do not export it! 404 if (!res.getState().isDeleted()) { 405 // if its a file, create export the file to the FS 406 m_report.print( 407 org.opencms.report.Messages.get().container( 408 org.opencms.report.Messages.RPT_SUCCESSION_1, 409 String.valueOf(m_count++)), 410 I_CmsReport.FORMAT_NOTE); 411 if (res.isFile()) { 412 413 m_report.print(Messages.get().container(Messages.RPT_EXPORT_FILE_0), I_CmsReport.FORMAT_NOTE); 414 m_report.print( 415 org.opencms.report.Messages.get().container( 416 org.opencms.report.Messages.RPT_ARGUMENT_1, 417 m_cms.getSitePath(res))); 418 m_report.print(Messages.get().container(Messages.RPT_TO_FS_AS_0), I_CmsReport.FORMAT_NOTE); 419 m_report.print( 420 org.opencms.report.Messages.get().container( 421 org.opencms.report.Messages.RPT_ARGUMENT_1, 422 fsFile.getAbsolutePath().replace('\\', '/'))); 423 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 424 425 // create the resource if nescessary 426 if (!fsFile.exists()) { 427 createNewLocalFile(fsFile); 428 } 429 // write the file content to the FS 430 vfsFile = m_cms.readFile(m_cms.getSitePath(res), CmsResourceFilter.IGNORE_EXPIRATION); 431 try { 432 writeFileByte(vfsFile.getContents(), fsFile); 433 } catch (IOException e) { 434 throw new CmsSynchronizeException(Messages.get().container(Messages.ERR_WRITE_FILE_0)); 435 } 436 // now check if there is some external method to be called 437 // which should modify the exported resource in the FS 438 Iterator<I_CmsSynchronizeModification> i = m_synchronizeModifications.iterator(); 439 while (i.hasNext()) { 440 try { 441 i.next().modifyFs(m_cms, vfsFile, fsFile); 442 } catch (CmsSynchronizeException e) { 443 if (LOG.isWarnEnabled()) { 444 LOG.warn( 445 Messages.get().getBundle().key( 446 Messages.LOG_SYNCHRONIZE_EXPORT_FAILED_1, 447 res.getRootPath()), 448 e); 449 } 450 break; 451 } 452 } 453 fsFile.setLastModified(res.getDateLastModified()); 454 } else { 455 456 m_report.print(Messages.get().container(Messages.RPT_EXPORT_FOLDER_0), I_CmsReport.FORMAT_NOTE); 457 m_report.print( 458 org.opencms.report.Messages.get().container( 459 org.opencms.report.Messages.RPT_ARGUMENT_1, 460 m_cms.getSitePath(res))); 461 m_report.print(Messages.get().container(Messages.RPT_TO_FS_AS_0), I_CmsReport.FORMAT_NOTE); 462 m_report.print( 463 org.opencms.report.Messages.get().container( 464 org.opencms.report.Messages.RPT_ARGUMENT_1, 465 fsFile.getAbsolutePath().replace('\\', '/'))); 466 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 467 468 // its a folder, so create a folder in the FS 469 fsFile.mkdirs(); 470 } 471 // add resource to synchronization list 472 CmsSynchronizeList syncList = new CmsSynchronizeList( 473 resourcename, 474 translate(resourcename), 475 res.getDateLastModified(), 476 fsFile.lastModified()); 477 m_newSyncList.put(translate(resourcename), syncList); 478 // and remove it fomr the old one 479 m_syncList.remove(translate(resourcename)); 480 m_report.println( 481 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 482 I_CmsReport.FORMAT_OK); 483 } 484 // free mem 485 vfsFile = null; 486 487 } catch (CmsException e) { 488 throw new CmsSynchronizeException(e.getMessageContainer(), e); 489 } 490 } 491 492 /** 493 * Gets the corresponding file to a resource in the VFS. <p> 494 * 495 * @param res path to the resource inside the VFS 496 * @return the corresponding file in the FS 497 */ 498 private File getFileInRfs(String res) { 499 500 String path = m_destinationPathInRfs + res.substring(0, res.lastIndexOf("/")); 501 String fileName = res.substring(res.lastIndexOf("/") + 1); 502 return new File(path, fileName); 503 } 504 505 /** 506 * Gets the corresponding filename of the VFS to a resource in the FS. <p> 507 * 508 * @param res the resource in the FS 509 * @return the corresponding filename in the VFS 510 */ 511 private String getFilenameInVfs(File res) { 512 513 String resname = res.getAbsolutePath(); 514 if (res.isDirectory()) { 515 resname += "/"; 516 } 517 // translate the folder separator if necessary 518 resname = resname.replace(File.separatorChar, '/'); 519 return resname.substring(m_destinationPathInRfs.length()); 520 } 521 522 /** 523 * Imports a new resource from the FS into the VFS and updates the 524 * synchronization lists.<p> 525 * 526 * @param fsFile the file in the FS 527 * @param resName the name of the resource in the VFS 528 * @param folder the folder to import the file into 529 * @throws CmsException if something goes wrong 530 */ 531 private void importToVfs(File fsFile, String resName, String folder) throws CmsException { 532 533 try { 534 // get the content of the FS file 535 byte[] content = CmsFileUtil.readFile(fsFile); 536 537 // create the file 538 String filename = translate(fsFile.getName()); 539 540 m_report.print( 541 org.opencms.report.Messages.get().container( 542 org.opencms.report.Messages.RPT_SUCCESSION_1, 543 String.valueOf(m_count++)), 544 I_CmsReport.FORMAT_NOTE); 545 if (fsFile.isFile()) { 546 m_report.print(Messages.get().container(Messages.RPT_IMPORT_FILE_0), I_CmsReport.FORMAT_NOTE); 547 } else { 548 m_report.print(Messages.get().container(Messages.RPT_IMPORT_FOLDER_0), I_CmsReport.FORMAT_NOTE); 549 } 550 551 m_report.print( 552 org.opencms.report.Messages.get().container( 553 org.opencms.report.Messages.RPT_ARGUMENT_1, 554 fsFile.getAbsolutePath().replace('\\', '/'))); 555 m_report.print(Messages.get().container(Messages.RPT_FROM_FS_TO_0), I_CmsReport.FORMAT_NOTE); 556 557 // get the file type of the FS file 558 int resType = OpenCms.getResourceManager().getDefaultTypeForName(resName).getTypeId(); 559 560 // set property 'Title' for the new resource 561 CmsProperty titleProp = new CmsProperty(); 562 titleProp.setName(CmsPropertyDefinition.PROPERTY_TITLE); 563 564 // in the unlikely case that the filename contains a full path, we only take the last part 565 String fsFileName = fsFile.getName(); 566 String title = fsFileName.replace("\\", "/"); 567 568 int lastSlashIndex = title.lastIndexOf('/'); 569 if (lastSlashIndex != -1) { 570 title = title.substring(lastSlashIndex + 1); 571 } 572 573 // remove the file extension from the title 574 int lastDotIndex = title.lastIndexOf('.'); 575 if (lastDotIndex != -1) { 576 title = title.substring(0, lastDotIndex); 577 } 578 579 if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) { 580 titleProp.setStructureValue(title); 581 } else { 582 titleProp.setResourceValue(title); 583 } 584 585 // list of properties to be set for the new resource 586 List<CmsProperty> properties = new ArrayList<>(); 587 properties.add(titleProp); 588 589 CmsResource newFile = m_cms.createResource(translate(folder) + filename, resType, content, properties); 590 591 m_report.print( 592 org.opencms.report.Messages.get().container( 593 org.opencms.report.Messages.RPT_ARGUMENT_1, 594 m_cms.getSitePath(newFile))); 595 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 596 597 // now check if there is some external method to be called which 598 // should modify the imported resource in the VFS 599 Iterator<I_CmsSynchronizeModification> i = m_synchronizeModifications.iterator(); 600 while (i.hasNext()) { 601 try { 602 i.next().modifyVfs(m_cms, newFile, fsFile); 603 } catch (CmsSynchronizeException e) { 604 break; 605 } 606 } 607 // we have to read the new resource again, to get the correct timestamp 608 m_cms.setDateLastModified(m_cms.getSitePath(newFile), fsFile.lastModified(), false); 609 CmsResource newRes = m_cms.readResource(m_cms.getSitePath(newFile)); 610 // add resource to synchronization list 611 CmsSynchronizeList syncList = new CmsSynchronizeList( 612 resName, 613 translate(resName), 614 newRes.getDateLastModified(), 615 fsFile.lastModified()); 616 m_newSyncList.put(translate(resName), syncList); 617 618 m_report.println( 619 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 620 I_CmsReport.FORMAT_OK); 621 } catch (IOException e) { 622 throw new CmsSynchronizeException( 623 Messages.get().container(Messages.ERR_READING_FILE_1, fsFile.getName()), 624 e); 625 } 626 } 627 628 /** 629 * Determine if this file is to be excluded.<p> 630 * 631 * @param file the file to check 632 * 633 * @return <code>true</code> if the file should be excluded from synchronization 634 */ 635 private boolean isExcluded(File file) { 636 637 ArrayList<Pattern> excludes = OpenCms.getWorkplaceManager().getSynchronizeExcludePatterns(); 638 for (Pattern pattern : excludes) { 639 if (pattern.matcher(file.getName()).find()) { 640 m_report.print( 641 org.opencms.report.Messages.get().container( 642 org.opencms.report.Messages.RPT_SUCCESSION_1, 643 String.valueOf(m_count++)), 644 I_CmsReport.FORMAT_NOTE); 645 m_report.print(Messages.get().container(Messages.RPT_EXCLUDING_0), I_CmsReport.FORMAT_NOTE); 646 m_report.println( 647 org.opencms.report.Messages.get().container( 648 org.opencms.report.Messages.RPT_ARGUMENT_1, 649 file.getAbsolutePath().replace("\\", "/"))); 650 651 return true; 652 } 653 } 654 return false; 655 } 656 657 /** 658 * Reads the synchronization list from the last sync process form the file 659 * system and stores the information in a HashMap. <p> 660 * 661 * Filenames are stored as keys, CmsSynchronizeList objects as values. 662 * @return HashMap with synchronization information of the last sync process 663 * @throws CmsException if something goes wrong 664 */ 665 private HashMap<String, CmsSynchronizeList> readSyncList() throws CmsException { 666 667 HashMap<String, CmsSynchronizeList> syncList = new HashMap<String, CmsSynchronizeList>(); 668 669 // the sync list file in the server fs 670 File syncListFile; 671 syncListFile = new File(m_destinationPathInRfs, SYNCLIST_FILENAME); 672 // try to read the sync list file if it is there 673 if (syncListFile.exists()) { 674 // prepare the streams to write the data 675 FileReader fIn = null; 676 LineNumberReader lIn = null; 677 try { 678 fIn = new FileReader(syncListFile); 679 lIn = new LineNumberReader(fIn); 680 // read one line from the file 681 String line = lIn.readLine(); 682 while (line != null) { 683 line = lIn.readLine(); 684 // extract the data and create a CmsSychroizedList object 685 // from it 686 if (line != null) { 687 StringTokenizer tok = new StringTokenizer(line, ":"); 688 String resName = tok.nextToken(); 689 String tranResName = tok.nextToken(); 690 long modifiedVfs = Long.valueOf(tok.nextToken()).longValue(); 691 long modifiedFs = Long.valueOf(tok.nextToken()).longValue(); 692 CmsSynchronizeList sync = new CmsSynchronizeList(resName, tranResName, modifiedVfs, modifiedFs); 693 syncList.put(translate(resName), sync); 694 } 695 } 696 } catch (IOException e) { 697 throw new CmsSynchronizeException(Messages.get().container(Messages.ERR_READ_SYNC_LIST_0), e); 698 } finally { 699 // close all streams that were used 700 try { 701 if (lIn != null) { 702 lIn.close(); 703 } 704 if (fIn != null) { 705 fIn.close(); 706 } 707 } catch (IOException e) { 708 // ignore 709 } 710 } 711 } 712 713 return syncList; 714 } 715 716 /** 717 * Removes all resources in the RFS which are deleted in the VFS.<p> 718 * 719 * @param folder the folder in the FS to check 720 * @throws CmsException if something goes wrong 721 */ 722 private void removeFromRfs(String folder) throws CmsException { 723 724 // get the corresponding folder in the FS 725 File[] res; 726 File rfsFile = new File(folder); 727 // get all resources in this folder 728 res = rfsFile.listFiles(); 729 // now loop through all resources 730 for (int i = 0; i < res.length; i++) { 731 if (isExcluded(res[i])) { 732 continue; 733 } 734 // get the corresponding name in the VFS 735 String vfsFile = getFilenameInVfs(res[i]); 736 // recurse if it is an directory, we must go depth first to delete 737 // files 738 if (res[i].isDirectory()) { 739 removeFromRfs(res[i].getAbsolutePath()); 740 } 741 // now check if this resource is still in the old sync list. 742 // if so, then it does not exist in the FS anymore andm ust be 743 // deleted 744 CmsSynchronizeList sync = m_syncList.get(translate(vfsFile)); 745 746 // there is an entry, so delete the resource 747 if (sync != null) { 748 749 m_report.print( 750 org.opencms.report.Messages.get().container( 751 org.opencms.report.Messages.RPT_SUCCESSION_1, 752 String.valueOf(m_count++)), 753 I_CmsReport.FORMAT_NOTE); 754 if (res[i].isFile()) { 755 m_report.print(Messages.get().container(Messages.RPT_DEL_FS_FILE_0), I_CmsReport.FORMAT_NOTE); 756 } else { 757 m_report.print(Messages.get().container(Messages.RPT_DEL_FS_FOLDER_0), I_CmsReport.FORMAT_NOTE); 758 } 759 m_report.print( 760 org.opencms.report.Messages.get().container( 761 org.opencms.report.Messages.RPT_ARGUMENT_1, 762 res[i].getAbsolutePath().replace('\\', '/'))); 763 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 764 765 res[i].delete(); 766 m_syncList.remove(translate(vfsFile)); 767 768 m_report.println( 769 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 770 I_CmsReport.FORMAT_OK); 771 } 772 } 773 } 774 775 /** 776 * Updates the synchronization lists if a resource is not used during the 777 * synchronization process.<p> 778 * 779 * @param res the resource whose entry must be updated 780 */ 781 private void skipResource(CmsResource res) { 782 783 // add the file to the new sync list... 784 String resname = m_cms.getSitePath(res); 785 CmsSynchronizeList syncList = m_syncList.get(translate(resname)); 786 m_newSyncList.put(translate(resname), syncList); 787 // .. and remove it from the old one 788 m_syncList.remove(translate(resname)); 789 // update the report 790 m_report.print( 791 org.opencms.report.Messages.get().container( 792 org.opencms.report.Messages.RPT_SUCCESSION_1, 793 String.valueOf(m_count++)), 794 I_CmsReport.FORMAT_NOTE); 795 m_report.print(Messages.get().container(Messages.RPT_SKIPPING_0), I_CmsReport.FORMAT_NOTE); 796 m_report.println( 797 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1, resname)); 798 } 799 800 /** 801 * Synchronizes resources from the VFS to the RFS. <p> 802 * 803 * During the synchronization process, the following actions will be done:<p> 804 * 805 * <ul> 806 * <li>Export modified resources from the VFS to the FS</li> 807 * <li>Update resources in the VFS if the corresponding resource in the FS 808 * has changed</li> 809 * <li>Delete resources in the VFS if the corresponding resource in the FS 810 * has been deleted</li> 811 * </ul> 812 * 813 * @param folder The folder in the VFS to be synchronized with the FS 814 * @throws CmsException if something goes wrong 815 */ 816 private void syncVfsToRfs(String folder) throws CmsException { 817 818 int action = 0; 819 //get all resources in the given folder 820 List<CmsResource> resources = m_cms.getResourcesInFolder(folder, CmsResourceFilter.IGNORE_EXPIRATION); 821 // now look through all resources in the folder 822 for (int i = 0; i < resources.size(); i++) { 823 CmsResource res = resources.get(i); 824 // test if the resource is marked as deleted. if so, 825 // do nothing, the corresponding file in the FS will be removed later 826 if (!res.getState().isDeleted()) { 827 // do a recursion if the current resource is a folder 828 if (res.isFolder()) { 829 // first check if this folder must be synchronized 830 action = testSyncVfs(res); 831 // do the correct action according to the test result 832 if (action == EXPORT_VFS) { 833 exportToRfs(res); 834 } else if (action != DELETE_VFS) { 835 skipResource(res); 836 } 837 // recurse into the sub folders. This must be done before 838 // the folder might be deleted! 839 syncVfsToRfs(m_cms.getSitePath(res)); 840 if (action == DELETE_VFS) { 841 deleteFromVfs(res); 842 } 843 } else { 844 // if the current resource is a file, check if it has to 845 // be synchronized 846 action = testSyncVfs(res); 847 // do the correct action according to the test result 848 switch (action) { 849 case EXPORT_VFS: 850 exportToRfs(res); 851 break; 852 853 case UPDATE_VFS: 854 updateFromRfs(res); 855 break; 856 857 case DELETE_VFS: 858 deleteFromVfs(res); 859 break; 860 861 default: 862 skipResource(res); 863 864 } 865 } 866 // free memory 867 res = null; 868 } 869 } 870 // free memory 871 resources = null; 872 } 873 874 /** 875 * Determines the synchronization status of a VFS resource. <p> 876 * 877 * @param res the VFS resource to check 878 * @return integer value for the action to be done for this VFS resource 879 */ 880 private int testSyncVfs(CmsResource res) { 881 882 int action = 0; 883 File fsFile; 884 //data from sync list 885 String resourcename = m_cms.getSitePath(res); 886 887 if (m_syncList.containsKey(translate(resourcename))) { 888 // this resource was already used in a previous synchronization process 889 CmsSynchronizeList sync = m_syncList.get(translate(resourcename)); 890 // get the corresponding resource from the FS 891 fsFile = getFileInRfs(sync.getResName()); 892 // now check what to do with this resource. 893 // if the modification date is newer than the logged modification 894 // date in the sync list, this resource must be exported too 895 if (res.getDateLastModified() > sync.getModifiedVfs()) { 896 // now check if the resource in the FS is newer, then the 897 // resource from the FS must be imported 898 899 // check if it has been modified since the last sync process 900 // and its newer than the resource in the VFS, only then this 901 // resource must be imported form the FS 902 if ((fsFile.lastModified() > sync.getModifiedFs()) 903 && (fsFile.lastModified() > res.getDateLastModified())) { 904 action = UPDATE_VFS; 905 } else { 906 907 action = EXPORT_VFS; 908 } 909 } else { 910 // test if the resource in the FS does not exist anymore. 911 // if so, remove the resource in the VFS 912 if (!fsFile.exists()) { 913 action = DELETE_VFS; 914 } else { 915 // now check if the resource in the FS might have changed 916 if (fsFile.lastModified() > sync.getModifiedFs()) { 917 action = UPDATE_VFS; 918 } 919 } 920 } 921 } else { 922 // the resource name was not found in the sync list 923 // this is a new resource 924 action = EXPORT_VFS; 925 } 926 //free memory 927 fsFile = null; 928 return action; 929 } 930 931 /** 932 * Translates the resource name. <p> 933 * 934 * This is necessary since the server RFS does allow different naming 935 * conventions than the VFS. 936 * 937 * @param name the resource name to be translated 938 * @return the translated resource name 939 */ 940 private String translate(String name) { 941 942 String translation = null; 943 // test if an external translation should be used 944 Iterator<I_CmsSynchronizeModification> i = m_synchronizeModifications.iterator(); 945 while (i.hasNext()) { 946 try { 947 translation = i.next().translate(m_cms, name); 948 } catch (CmsSynchronizeException e) { 949 if (LOG.isInfoEnabled()) { 950 LOG.info(Messages.get().getBundle().key(Messages.LOG_EXTERNAL_TRANSLATION_1, name), e); 951 } 952 break; 953 } 954 } 955 // if there was no external method called, do the default OpenCms 956 // RFS-VFS translation 957 if (translation == null) { 958 translation = m_cms.getRequestContext().getFileTranslator().translateResource(name); 959 } 960 return translation; 961 } 962 963 /** 964 * Imports a resource from the FS to the VFS and updates the 965 * synchronization lists.<p> 966 * 967 * @param res the resource to be exported 968 * 969 * @throws CmsSynchronizeException if the resource could not be synchronized 970 * @throws CmsException if something goes wrong 971 */ 972 private void updateFromRfs(CmsResource res) throws CmsSynchronizeException, CmsException { 973 974 CmsFile vfsFile; 975 // to get the name of the file in the FS, we must look it up in the 976 // sync list. This is necessary, since the VFS could use a translated 977 // filename. 978 String resourcename = m_cms.getSitePath(res); 979 CmsSynchronizeList sync = m_syncList.get(translate(resourcename)); 980 File fsFile = getFileInRfs(sync.getResName()); 981 982 m_report.print( 983 org.opencms.report.Messages.get().container( 984 org.opencms.report.Messages.RPT_SUCCESSION_1, 985 String.valueOf(m_count++)), 986 I_CmsReport.FORMAT_NOTE); 987 m_report.print(Messages.get().container(Messages.RPT_UPDATE_FILE_0), I_CmsReport.FORMAT_NOTE); 988 m_report.print( 989 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1, resourcename)); 990 m_report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0)); 991 992 // lock the file in the VFS, so that it can be updated 993 m_cms.lockResource(resourcename); 994 // read the file in the VFS 995 vfsFile = m_cms.readFile(resourcename, CmsResourceFilter.IGNORE_EXPIRATION); 996 // import the content from the FS 997 try { 998 vfsFile.setContents(CmsFileUtil.readFile(fsFile)); 999 } catch (IOException e) { 1000 throw new CmsSynchronizeException(Messages.get().container(Messages.ERR_IMPORT_1, fsFile.getName())); 1001 } 1002 m_cms.writeFile(vfsFile); 1003 // now check if there is some external method to be called which 1004 // should modify 1005 // the updated resource in the VFS 1006 Iterator<I_CmsSynchronizeModification> i = m_synchronizeModifications.iterator(); 1007 while (i.hasNext()) { 1008 try { 1009 i.next().modifyVfs(m_cms, vfsFile, fsFile); 1010 } catch (CmsSynchronizeException e) { 1011 if (LOG.isInfoEnabled()) { 1012 LOG.info( 1013 Messages.get().getBundle().key(Messages.LOG_SYNCHRONIZE_UPDATE_FAILED_1, res.getRootPath()), 1014 e); 1015 } 1016 break; 1017 } 1018 } 1019 // everything is done now, so unlock the resource 1020 // read the resource again, necessary to get the actual timestamp 1021 m_cms.setDateLastModified(resourcename, fsFile.lastModified(), false); 1022 res = m_cms.readResource(resourcename, CmsResourceFilter.IGNORE_EXPIRATION); 1023 1024 //add resource to synchronization list 1025 CmsSynchronizeList syncList = new CmsSynchronizeList( 1026 sync.getResName(), 1027 translate(resourcename), 1028 res.getDateLastModified(), 1029 fsFile.lastModified()); 1030 m_newSyncList.put(translate(resourcename), syncList); 1031 // and remove it from the old one 1032 m_syncList.remove(translate(resourcename)); 1033 vfsFile = null; 1034 1035 m_report.println( 1036 org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0), 1037 I_CmsReport.FORMAT_OK); 1038 } 1039 1040 /** 1041 * This writes the byte content of a resource to the file on the server 1042 * file system.<p> 1043 * 1044 * @param content the content of the file in the VFS 1045 * @param file the file in SFS that has to be updated with content 1046 * 1047 * @throws IOException if something goes wrong 1048 */ 1049 private void writeFileByte(byte[] content, File file) throws IOException { 1050 1051 FileOutputStream fOut = null; 1052 DataOutputStream dOut = null; 1053 try { 1054 // write the content to the file in server filesystem 1055 fOut = new FileOutputStream(file); 1056 dOut = new DataOutputStream(fOut); 1057 dOut.write(content); 1058 dOut.flush(); 1059 } catch (IOException e) { 1060 throw e; 1061 } finally { 1062 try { 1063 if (fOut != null) { 1064 fOut.close(); 1065 } 1066 } catch (IOException e) { 1067 // ignore 1068 } 1069 try { 1070 if (dOut != null) { 1071 dOut.close(); 1072 } 1073 } catch (IOException e) { 1074 // ignore 1075 } 1076 } 1077 } 1078 1079 /** 1080 * Writes the synchronization list of the current sync process to the 1081 * server file system. <p> 1082 * 1083 * The file can be found in the synchronization folder 1084 * @throws CmsException if something goes wrong 1085 */ 1086 private void writeSyncList() throws CmsException { 1087 1088 // the sync list file in the server file system 1089 File syncListFile; 1090 syncListFile = new File(m_destinationPathInRfs, SYNCLIST_FILENAME); 1091 1092 // prepare the streams to write the data 1093 FileOutputStream fOut = null; 1094 PrintWriter pOut = null; 1095 try { 1096 fOut = new FileOutputStream(syncListFile); 1097 pOut = new PrintWriter(fOut); 1098 pOut.println(CmsSynchronizeList.getFormatDescription()); 1099 1100 // get all keys from the hash map and make an iterator on it 1101 Iterator<CmsSynchronizeList> values = m_newSyncList.values().iterator(); 1102 // loop through all values and write them to the sync list file in 1103 // a human readable format 1104 while (values.hasNext()) { 1105 CmsSynchronizeList sync = values.next(); 1106 //fOut.write(sync.toString().getBytes()); 1107 pOut.println(sync.toString()); 1108 } 1109 } catch (IOException e) { 1110 throw new CmsDbIoException(Messages.get().container(Messages.ERR_IO_WRITE_SYNCLIST_0), e); 1111 } finally { 1112 // close all streams that were used 1113 try { 1114 if (pOut != null) { 1115 pOut.flush(); 1116 pOut.close(); 1117 } 1118 if (fOut != null) { 1119 fOut.flush(); 1120 fOut.close(); 1121 } 1122 } catch (IOException e) { 1123 // ignore 1124 } 1125 } 1126 } 1127}