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}