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