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, 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.cache;
029
030import org.opencms.util.CmsFileUtil;
031import org.opencms.util.CmsStringUtil;
032
033import java.io.File;
034import java.io.FileOutputStream;
035import java.io.IOException;
036import java.io.InputStream;
037
038/**
039 * Implements a RFS file based disk cache, that handles parameter based versions of VFS files,
040 * providing a cache for the "online" and another for the "offline" project.<p>
041 *
042 * @since 6.2.0
043 */
044public class CmsVfsDiskCache {
045
046    /** The name of the cache base repository folder in the RFS. */
047    private String m_rfsRepository;
048
049    /**
050     * Creates a new disk cache.<p>
051     *
052     * @param basepath the base path for the cache in the RFS
053     * @param foldername the folder name for this cache, to be used a subfolder for the base folder
054     */
055    public CmsVfsDiskCache(String basepath, String foldername) {
056
057        // normalize the given folder name
058        m_rfsRepository = CmsFileUtil.normalizePath(basepath + foldername + File.separatorChar);
059    }
060
061    /**
062     * Saves the given file content to a RFS file of the given name (full path).<p>
063     *
064     * If the required parent folders do not exists, they are also created.<p>
065     *
066     * @param rfsName the RFS name of the file to save the content in
067     * @param content the content of the file to save
068     *
069     * @return a reference to the File that was saved
070     *
071     * @throws IOException in case of disk access errors
072     */
073    public static File saveFile(String rfsName, byte[] content) throws IOException {
074
075        File f = createFile(rfsName);
076        // write file contents
077        FileOutputStream fs = new FileOutputStream(f);
078        fs.write(content);
079        fs.close();
080        return f;
081    }
082
083    /**
084     * Saves the given input stream content to a RFS file of the given name (full path).<p>
085     *
086     * If the required parent folders do not exists, they are also created.<p>
087     *
088     * @param rfsName the RFS name of the file to save the content in
089     * @param content the input stream to save
090     *
091     * @return a reference to the File that was saved
092     *
093     * @throws IOException in case of disk access errors
094     */
095    public static File saveFile(String rfsName, InputStream content) throws IOException {
096
097        File f = createFile(rfsName);
098        FileOutputStream fs = new FileOutputStream(f);
099        try {
100            byte[] buffer = new byte[8192];
101            int read;
102            while ((read = content.read(buffer)) >= 0) {
103                if (read > 0) {
104                    fs.write(buffer, 0, read);
105                }
106            }
107        } finally {
108            fs.close();
109        }
110        return f;
111    }
112
113    /**
114     * Creates a RFS file reference and its parent folders.<p>
115     *
116     * @param rfsName the RFS name of the file
117     *
118     * @return the file
119     */
120    static File createFile(String rfsName) {
121
122        File f = new File(rfsName);
123        ensureParentFolder(f);
124        return f;
125    }
126
127    /**
128     * Ensures that the parent folder of the given file exists.<p>
129     *
130     * @param file the file
131     */
132    private static void ensureParentFolder(File file) {
133
134        File p = file.getParentFile();
135        if (!p.exists()) {
136            // create parent folders
137            p.mkdirs();
138        }
139    }
140
141    /**
142     * Returns the content of the requested file in the disk cache, or <code>null</code> if the
143     * file is not found in the cache, or is found but outdated.<p>
144     *
145     * @param rfsName the file RFS name to look up in the cache
146     * @param dateLastModified the date of last modification for the cache
147     *
148     * @return the content of the requested file in the VFS disk cache, or <code>null</code>
149     */
150    public byte[] getCacheContent(String rfsName, long dateLastModified) {
151
152        dateLastModified = simplifyDateLastModified(dateLastModified);
153        try {
154            File f = new File(rfsName);
155            if (f.exists()) {
156                if (f.lastModified() != dateLastModified) {
157                    // last modification time different, remove cached file in RFS
158                    f.delete();
159                } else {
160                    return CmsFileUtil.readFile(f);
161                }
162            }
163        } catch (IOException e) {
164            // unable to read content
165        }
166        return null;
167    }
168
169    /**
170     * Returns the RFS name to use for caching the given VFS resource with parameters in the disk cache.<p>
171     *
172     * @param online if true, the online disk cache is used, the offline disk cache otherwise
173     * @param rootPath the VFS resource root path to get the RFS cache name for
174     * @param parameters the parameters of the request to the VFS resource
175     *
176     * @return the RFS name to use for caching the given VFS resource with parameters
177     */
178    public String getCacheName(boolean online, String rootPath, String parameters) {
179
180        String rfsName = CmsFileUtil.getRepositoryName(m_rfsRepository, rootPath, online);
181        if (CmsStringUtil.isNotEmpty(parameters)) {
182            String extension = CmsFileUtil.getExtension(rfsName);
183            // build the RFS name for the VFS name with parameters
184            rfsName = CmsFileUtil.getRfsPath(rfsName, extension, parameters);
185        }
186
187        return rfsName;
188    }
189
190    /**
191     * Returns the absolute path of the cache repository in the RFS.<p>
192     *
193     * @return the absolute path of the cache repository in the RFS
194     */
195    public String getRepositoryPath() {
196
197        return m_rfsRepository;
198    }
199
200    /**
201     * Saves the given file content in the disk cache.<p>
202     *
203     * @param rfsName the RFS name of the file to save the content in
204     * @param content the content of the file to save
205     * @param dateLastModified the date of last modification to set for the save file
206     *
207     * @throws IOException in case of disk access errors
208     */
209    public void saveCacheFile(String rfsName, byte[] content, long dateLastModified) throws IOException {
210
211        dateLastModified = simplifyDateLastModified(dateLastModified);
212        File f = saveFile(rfsName, content);
213        // set last modification date
214        f.setLastModified(dateLastModified);
215    }
216
217    /**
218     * Saves the given input stream content in the disk cache.<p>
219     *
220     * @param rfsName the RFS name of the file to save the content in
221     * @param content the input stream to save
222     * @param dateLastModified the date of last modification to set for the save file
223     *
224     * @throws IOException in case of disk access errors
225     */
226    public void saveCacheFile(String rfsName, InputStream content, long dateLastModified) throws IOException {
227
228        dateLastModified = simplifyDateLastModified(dateLastModified);
229        File f = saveFile(rfsName, content);
230        // set last modification date
231        f.setLastModified(dateLastModified);
232    }
233
234    /**
235     * Simplifies the "date last modified" from the OpenCms VFS based milliseconds
236     * to just seconds, to support file systems that don't use milliseconds.<p>
237     *
238     * @param dateLastModified the date to simplify
239     *
240     * @return the simplified date last modified
241     */
242    private long simplifyDateLastModified(long dateLastModified) {
243
244        return dateLastModified / 1000L;
245    }
246}