/* * Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com). * * 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 och.service.props.impl; import static java.util.Collections.*; import static och.util.FileUtil.*; import static och.util.MapUtil.*; import static och.util.Util.*; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import och.util.concurrent.ExecutorsUtil; import org.apache.commons.logging.Log; public class FileProps extends BaseProps { private static Log log = getLog(FileProps.class); private static ScheduledExecutorService syncService; private static synchronized ScheduledExecutorService getExecutorService(){ if(syncService == null){ syncService = ExecutorsUtil.newScheduledThreadPool("FileProps-sync", 1); } return syncService; } public static interface PropsModifier { Properties modify(Properties props); } public static FileProps createPropsWithoutUpdate(File propsFile){ return new FileProps(propsFile, 0, null); } public static List<FileProps> createFileProps(Collection<String> paths){ LinkedList<FileProps> list = new LinkedList<>(); for (String path : paths) { File file = new File(path); if( ! file.exists()){ log.error("can't find file by path: "+path); continue; } list.addFirst(new FileProps(file)); } return list; } private final File file; private final PropsModifier propsModifier; private volatile long curModified; private volatile Properties state = new Properties(); public FileProps(String propsPath){ this(new File(propsPath)); } public FileProps(File propsFile){ this(propsFile, null); } public FileProps(File propsFile, PropsModifier propsModifier){ this(propsFile, 1000*60, propsModifier); } public FileProps(File propsFile, long updateTimeMs, PropsModifier propsModifier){ this.file = propsFile; this.propsModifier = propsModifier; updateFromFileIfNeed(); updateTimeMs = updateTimeMs > 0 && updateTimeMs < 50? 50 : updateTimeMs; if(updateTimeMs > 0){ getExecutorService().scheduleWithFixedDelay(() -> updateFromFileIfNeed(), updateTimeMs, updateTimeMs, TimeUnit.MILLISECONDS); } } public File getFile(){ return file; } @Override public String getVal(Object oKey, String defaultVal) { String key = String.valueOf(oKey); Properties props = state; return props.containsKey(key)? (String)props.get(key) : defaultVal; } @Override public void putVal(Object oKey, String val) { String key = String.valueOf(oKey); putObjVal(key, val); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Map<String, String> toMap() { HashMap<String, String> out = new HashMap<>((Map)state); return out; } private synchronized void putObjVal(String key, String val){ Properties props = state; Properties newProps = new Properties(); newProps.putAll(props); if(val == null) newProps.remove(key); else newProps.put(key, val); saveToFile(newProps); fireChangedEvent(singleton(key)); } @SuppressWarnings("unchecked") public synchronized void updateFromFileIfNeed() { long lastModified = file.lastModified(); if(curModified == lastModified) return; log.info("load props from "+file.getAbsolutePath()); Properties props = null; try { FileInputStream is = new FileInputStream(file); Properties fromFile = new Properties(); fromFile.load(new InputStreamReader(is, "UTF-8")); is.close(); props = fromFile; } //empty props catch(FileNotFoundException e){ props = new Properties(); } //invalid read catch (Exception e) { props = null; log.error("can't read props", e); } if(props == null) return; Properties newState = modifyProps(props); Properties oldState = state; this.curModified = lastModified; this.state = newState; Set<String> keys = getUpdatedKeys(oldState, newState); if( ! isEmpty(keys)) { fireChangedEvent(keys); } } private void saveToFile(Properties newProps) { log.info("save props to "+file.getAbsolutePath()); Properties newState = modifyProps(newProps); StringBuilder sb = new StringBuilder(); sb.append("#Props from app"); for (Entry<Object, Object> entry : newProps.entrySet()) { sb.append("\n"+entry.getKey()+"="+entry.getValue()); } try { replaceFileUTF8(file, sb.toString()); }catch (Exception e) { log.error("can't saveToFile", e); return; } this.curModified = file.lastModified(); this.state = newState; } public Properties modifyProps(Properties props){ PropsModifier curModifier = this.propsModifier; if(curModifier == null) return props; return curModifier.modify(props); } @Override public String toString() { return "FileProps [file=" + file + "]"; } }