001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.config.store; 014 015import java.io.*; 016import java.util.*; 017import java.util.concurrent.*; 018 019import org.apache.juneau.*; 020import org.apache.juneau.config.internal.*; 021 022/** 023 * Represents a storage location for configuration files. 024 * 025 * <p> 026 * Content stores require two methods to be implemented: 027 * <ul> 028 * <li class='jm'>{@link #read(String)} - Retrieve a config file. 029 * <li class='jm'>{@link #write(String,String,String)} - ConfigStore a config file. 030 * </ul> 031 */ 032public abstract class ConfigStore extends Context implements Closeable { 033 034 private final ConcurrentHashMap<String,Set<ConfigStoreListener>> listeners = new ConcurrentHashMap<>(); 035 private final ConcurrentHashMap<String,ConfigMap> configMaps = new ConcurrentHashMap<>(); 036 037 /** 038 * Constructor. 039 * 040 * @param ps The settings for this content store. 041 */ 042 protected ConfigStore(PropertyStore ps) { 043 super(ps); 044 } 045 046 /** 047 * Returns the contents of the configuration file. 048 * 049 * @param name The config file name. 050 * @return 051 * The contents of the configuration file. 052 * <br>A blank string if the config does not exist. 053 * <br>Never <jk>null</jk>. 054 * @throws IOException 055 */ 056 public abstract String read(String name) throws IOException; 057 058 /** 059 * Saves the contents of the configuration file if the underlying storage hasn't been modified. 060 * 061 * @param name The config file name. 062 * @param expectedContents The expected contents of the file. 063 * @param newContents The new contents. 064 * @return 065 * If <jk>null</jk>, then we successfully stored the contents of the file. 066 * <br>Otherwise the contents of the file have changed and we return the new contents of the file. 067 * @throws IOException 068 */ 069 public abstract String write(String name, String expectedContents, String newContents) throws IOException; 070 071 /** 072 * Registers a new listener on this store. 073 * 074 * @param name The configuration name to listen for. 075 * @param l The new listener. 076 * @return This object (for method chaining). 077 */ 078 public synchronized ConfigStore register(String name, ConfigStoreListener l) { 079 Set<ConfigStoreListener> s = listeners.get(name); 080 if (s == null) { 081 s = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<ConfigStoreListener,Boolean>())); 082 listeners.put(name, s); 083 } 084 s.add(l); 085 return this; 086 } 087 088 /** 089 * Unregisters a listener from this store. 090 * 091 * @param name The configuration name to listen for. 092 * @param l The listener to unregister. 093 * @return This object (for method chaining). 094 */ 095 public synchronized ConfigStore unregister(String name, ConfigStoreListener l) { 096 Set<ConfigStoreListener> s = listeners.get(name); 097 if (s != null) 098 s.remove(l); 099 return this; 100 } 101 102 /** 103 * Returns a map file containing the parsed contents of a configuration. 104 * 105 * @param name The configuration name. 106 * @return 107 * The parsed configuration. 108 * <br>Never <jk>null</jk>. 109 * @throws IOException 110 */ 111 public synchronized ConfigMap getMap(String name) throws IOException { 112 ConfigMap cm = configMaps.get(name); 113 if (cm != null) 114 return cm; 115 cm = new ConfigMap(this, name); 116 ConfigMap cm2 = configMaps.putIfAbsent(name, cm); 117 if (cm2 != null) 118 return cm2; 119 register(name, cm); 120 return cm; 121 } 122 123 /** 124 * Called when the physical contents of a config file have changed. 125 * 126 * <p> 127 * Triggers calls to {@link ConfigStoreListener#onChange(String)} on all registered listeners. 128 * 129 * @param name The config name (e.g. the filename without the extension). 130 * @param contents The new contents. 131 * @return This object (for method chaining). 132 */ 133 public synchronized ConfigStore update(String name, String contents) { 134 Set<ConfigStoreListener> s = listeners.get(name); 135 if (s != null) 136 for (ConfigStoreListener l : listeners.get(name)) 137 l.onChange(contents); 138 return this; 139 } 140 141 /** 142 * Convenience method for updating the contents of a file with lines. 143 * 144 * @param name The config name (e.g. the filename without the extension). 145 * @param contentLines The new contents. 146 * @return This object (for method chaining). 147 */ 148 public synchronized ConfigStore update(String name, String...contentLines) { 149 StringBuilder sb = new StringBuilder(); 150 for (String l : contentLines) 151 sb.append(l).append('\n'); 152 return update(name, sb.toString()); 153 } 154 155 /** 156 * Unused. 157 */ 158 @Override /* Context */ 159 public final Session createSession(SessionArgs args) { 160 throw new NoSuchMethodError(); 161 } 162 163 /** 164 * Unused. 165 */ 166 @Override /* Context */ 167 public final SessionArgs createDefaultSessionArgs() { 168 throw new NoSuchMethodError(); 169 } 170}