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}