/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.integration.metadata; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Properties; import java.util.concurrent.locks.Lock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.support.locks.DefaultLockRegistry; import org.springframework.integration.support.locks.LockRegistry; import org.springframework.util.Assert; import org.springframework.util.DefaultPropertiesPersister; /** * Properties file-based implementation of {@link MetadataStore}. To avoid conflicts * each instance should be constructed with the unique key from which unique file name * will be generated. * By default, the properties file will be * {@code 'java.io.tmpdir' + "/spring-integration/metadata-store.properties"}, * but the directory and filename are settable. * * @author Oleg Zhurakousky * @author Mark Fisher * @author Gary Russell * @since 2.0 */ public class PropertiesPersistingMetadataStore implements ConcurrentMetadataStore, InitializingBean, DisposableBean, Closeable, Flushable { private final Log logger = LogFactory.getLog(getClass()); private final Properties metadata = new Properties(); private final DefaultPropertiesPersister persister = new DefaultPropertiesPersister(); private final LockRegistry lockRegistry = new DefaultLockRegistry(); private String baseDirectory = System.getProperty("java.io.tmpdir") + "/spring-integration/"; private String fileName = "metadata-store.properties"; private File file; private volatile boolean dirty; /** * Set the location for the properties file. Defaults to * {@code 'java.io.tmpdir' + "/spring-integration/"}. * @param baseDirectory the directory. */ public void setBaseDirectory(String baseDirectory) { Assert.hasText(baseDirectory, "'baseDirectory' must be non-empty"); this.baseDirectory = baseDirectory; } /** * Set the name of the properties file in {@link #setBaseDirectory(String)}. * Defaults to {@code metadata-store.properties}, * @param fileName the properties file name. */ public void setFileName(String fileName) { this.fileName = fileName; } @Override public void afterPropertiesSet() throws Exception { File baseDir = new File(this.baseDirectory); baseDir.mkdirs(); this.file = new File(baseDir, this.fileName); try { if (!this.file.exists()) { this.file.createNewFile(); } } catch (Exception e) { throw new IllegalArgumentException("Failed to create metadata-store file '" + this.file.getAbsolutePath() + "'", e); } this.loadMetadata(); } @Override public void put(String key, String value) { Assert.notNull(key, "'key' cannot be null"); Assert.notNull(value, "'value' cannot be null"); Lock lock = this.lockRegistry.obtain(key); lock.lock(); try { this.metadata.setProperty(key, value); } finally { this.dirty = true; lock.unlock(); } } @Override public String get(String key) { Assert.notNull(key, "'key' cannot be null"); Lock lock = this.lockRegistry.obtain(key); lock.lock(); try { return this.metadata.getProperty(key); } finally { lock.unlock(); } } @Override public String remove(String key) { Assert.notNull(key, "'key' cannot be null"); Lock lock = this.lockRegistry.obtain(key); lock.lock(); try { return (String) this.metadata.remove(key); } finally { this.dirty = true; lock.unlock(); } } @Override public String putIfAbsent(String key, String value) { Assert.notNull(key, "'key' cannot be null"); Assert.notNull(value, "'value' cannot be null"); Lock lock = this.lockRegistry.obtain(key); lock.lock(); try { String property = this.metadata.getProperty(key); if (property == null) { this.metadata.setProperty(key, value); this.dirty = true; return null; } else { return property; } } finally { lock.unlock(); } } @Override public boolean replace(String key, String oldValue, String newValue) { Assert.notNull(key, "'key' cannot be null"); Assert.notNull(oldValue, "'oldValue' cannot be null"); Assert.notNull(newValue, "'newValue' cannot be null"); Lock lock = this.lockRegistry.obtain(key); lock.lock(); try { String property = this.metadata.getProperty(key); if (oldValue.equals(property)) { this.metadata.setProperty(key, newValue); this.dirty = true; return true; } else { return false; } } finally { lock.unlock(); } } @Override public void close() throws IOException { flush(); } @Override public void flush() { saveMetadata(); } @Override public void destroy() throws Exception { flush(); } private void saveMetadata() { if (this.file == null || !this.dirty) { return; } this.dirty = false; OutputStream outputStream = null; try { outputStream = new BufferedOutputStream(new FileOutputStream(this.file)); this.persister.store(this.metadata, outputStream, "Last entry"); } catch (IOException e) { // not fatal for the functionality of the component this.logger.warn("Failed to persist entry. This may result in a duplicate " + "entry after this component is restarted.", e); } finally { try { if (outputStream != null) { outputStream.close(); } } catch (IOException e) { // not fatal for the functionality of the component this.logger.warn("Failed to close OutputStream to " + this.file.getAbsolutePath(), e); } } } private void loadMetadata() { InputStream inputStream = null; try { inputStream = new BufferedInputStream(new FileInputStream(this.file)); this.persister.load(this.metadata, inputStream); } catch (Exception e) { // not fatal for the functionality of the component this.logger.warn("Failed to load entry from the persistent store. This may result in a duplicate " + "entry after this component is restarted", e); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (Exception e2) { // non fatal this.logger.warn("Failed to close InputStream for: " + this.file.getAbsolutePath()); } } } }