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