/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.store.s3; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.S3Object; import com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.util.DateUtil; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.LocaleUtil; import com.liferay.portal.kernel.util.StreamUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.SystemProperties; import com.liferay.portal.kernel.util.Time; import com.liferay.portal.store.s3.configuration.S3StoreConfiguration; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; /** * @author Edward C. Han */ @Component( configurationPid = "com.liferay.portal.store.s3.configuration.S3StoreConfiguration", configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true, service = S3FileCache.class ) public class S3FileCacheImpl implements S3FileCache { @Override public void cleanUpCacheFiles() { _calledCleanUpCacheFilesCount++; if (_calledCleanUpCacheFilesCount < _cacheDirCleanUpFrequency.intValue()) { return; } synchronized (this) { if (_calledCleanUpCacheFilesCount == 0) { return; } _calledCleanUpCacheFilesCount = 0; String cacheDirName = getCacheDirName(); File cacheDir = new File(cacheDirName); long lastModified = System.currentTimeMillis(); lastModified -= _cacheDirCleanUpExpunge.intValue() * Time.DAY; cleanUpCacheFiles(cacheDir, lastModified); } } @Override public File getCacheFile(S3Object s3Object, String fileName) throws IOException { StringBundler sb = new StringBundler(4); sb.append(getCacheDirName()); sb.append( DateUtil.getCurrentDate( _CACHE_DIR_PATTERN, LocaleUtil.getDefault())); sb.append(_s3KeyTransformer.getNormalizedFileName(fileName)); ObjectMetadata objectMetadata = s3Object.getObjectMetadata(); Date lastModifiedDate = objectMetadata.getLastModified(); sb.append(lastModifiedDate.getTime()); String cacheFileName = sb.toString(); File cacheFile = new File(cacheFileName); InputStream inputStream = s3Object.getObjectContent(); if (cacheFile.exists() && (cacheFile.lastModified() >= lastModifiedDate.getTime())) { StreamUtil.cleanUp(inputStream); return cacheFile; } if (inputStream == null) { throw new IOException("S3 object input stream is null"); } OutputStream outputStream = null; try { File parentFile = cacheFile.getParentFile(); FileUtil.mkdirs(parentFile); outputStream = new FileOutputStream(cacheFile); StreamUtil.transfer(inputStream, outputStream); } finally { StreamUtil.cleanUp(inputStream, outputStream); } return cacheFile; } @Activate @Modified protected void activate(Map<String, Object> properties) { _s3StoreConfiguration = ConfigurableUtil.createConfigurable( S3StoreConfiguration.class, properties); _cacheDirCleanUpExpunge = new AtomicInteger( _s3StoreConfiguration.cacheDirCleanUpExpunge()); _cacheDirCleanUpFrequency = new AtomicInteger( _s3StoreConfiguration.cacheDirCleanUpFrequency()); } protected void cleanUpCacheFiles(File file, long lastModified) { if (!file.isDirectory()) { return; } String[] fileNames = FileUtil.listDirs(file); if (fileNames.length == 0) { if (file.lastModified() < lastModified) { FileUtil.deltree(file); return; } } else { for (String fileName : fileNames) { cleanUpCacheFiles(new File(file, fileName), lastModified); } String[] subfileNames = file.list(); if (subfileNames.length == 0) { FileUtil.deltree(file); return; } } } @Deactivate protected void deactivate() { File cacheDir = new File(getCacheDirName()); boolean deleted = cacheDir.delete(); if (!deleted) { if (_log.isWarnEnabled()) { _log.warn("Unable to delete " + getCacheDirName()); } } } protected String getCacheDirName() { return SystemProperties.get(SystemProperties.TMP_DIR) + _CACHE_DIR_NAME; } @Reference(unbind = "-") protected void setS3KeyTransformer(S3KeyTransformer s3KeyTransformer) { _s3KeyTransformer = s3KeyTransformer; } private static final String _CACHE_DIR_NAME = "/liferay/s3"; private static final String _CACHE_DIR_PATTERN = "/yyyy/MM/dd/HH/"; private static final Log _log = LogFactoryUtil.getLog( S3FileCacheImpl.class); private AtomicInteger _cacheDirCleanUpExpunge; private AtomicInteger _cacheDirCleanUpFrequency; private int _calledCleanUpCacheFilesCount; private S3KeyTransformer _s3KeyTransformer; private volatile S3StoreConfiguration _s3StoreConfiguration; }