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.annotation.*; 021import org.apache.juneau.collections.*; 022import org.apache.juneau.config.internal.*; 023 024/** 025 * Represents a storage location for configuration files. 026 * 027 * <p> 028 * Content stores require two methods to be implemented: 029 * <ul class='javatree'> 030 * <li class='jm'>{@link #read(String)} - Retrieve a config file. 031 * <li class='jm'>{@link #write(String,String,String)} - ConfigStore a config file. 032 * </ul> 033 */ 034@ConfigurableContext 035public abstract class ConfigStore extends Context implements Closeable { 036 037 //------------------------------------------------------------------------------------------------------------------- 038 // Configurable properties 039 //------------------------------------------------------------------------------------------------------------------- 040 041 static final String PREFIX = "ConfigStore"; 042 043 //------------------------------------------------------------------------------------------------------------------- 044 // Instance 045 //------------------------------------------------------------------------------------------------------------------- 046 047 private final ConcurrentHashMap<String,Set<ConfigStoreListener>> listeners = new ConcurrentHashMap<>(); 048 private final ConcurrentHashMap<String,ConfigMap> configMaps = new ConcurrentHashMap<>(); 049 050 /** 051 * Constructor. 052 * 053 * @param ps The settings for this content store. 054 */ 055 protected ConfigStore(PropertyStore ps) { 056 super(ps, false); 057 } 058 059 /** 060 * Returns the contents of the configuration file. 061 * 062 * @param name The config file name. 063 * @return 064 * The contents of the configuration file. 065 * <br>A blank string if the config does not exist. 066 * <br>Never <jk>null</jk>. 067 * @throws IOException Thrown by underlying stream. 068 */ 069 public abstract String read(String name) throws IOException; 070 071 /** 072 * Saves the contents of the configuration file if the underlying storage hasn't been modified. 073 * 074 * @param name The config file name. 075 * @param expectedContents The expected contents of the file. 076 * @param newContents The new contents. 077 * @return 078 * If <jk>null</jk>, then we successfully stored the contents of the file. 079 * <br>Otherwise the contents of the file have changed and we return the new contents of the file. 080 * @throws IOException Thrown by underlying stream. 081 */ 082 public abstract String write(String name, String expectedContents, String newContents) throws IOException; 083 084 /** 085 * Checks whether the configuration with the specified name exists in this store. 086 * 087 * @param name The config name. 088 * @return <jk>true</jk> if the configuration with the specified name exists in this store. 089 */ 090 public abstract boolean exists(String name); 091 092 /** 093 * Registers a new listener on this store. 094 * 095 * @param name The configuration name to listen for. 096 * @param l The new listener. 097 * @return This object (for method chaining). 098 */ 099 public synchronized ConfigStore register(String name, ConfigStoreListener l) { 100 name = resolveName(name); 101 Set<ConfigStoreListener> s = listeners.get(name); 102 if (s == null) { 103 s = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<ConfigStoreListener,Boolean>())); 104 listeners.put(name, s); 105 } 106 s.add(l); 107 return this; 108 } 109 110 /** 111 * Unregisters a listener from this store. 112 * 113 * @param name The configuration name to listen for. 114 * @param l The listener to unregister. 115 * @return This object (for method chaining). 116 */ 117 public synchronized ConfigStore unregister(String name, ConfigStoreListener l) { 118 name = resolveName(name); 119 Set<ConfigStoreListener> s = listeners.get(name); 120 if (s != null) 121 s.remove(l); 122 return this; 123 } 124 125 /** 126 * Returns a map file containing the parsed contents of a configuration. 127 * 128 * @param name The configuration name. 129 * @return 130 * The parsed configuration. 131 * <br>Never <jk>null</jk>. 132 * @throws IOException Thrown by underlying stream. 133 */ 134 public synchronized ConfigMap getMap(String name) throws IOException { 135 name = resolveName(name); 136 ConfigMap cm = configMaps.get(name); 137 if (cm != null) 138 return cm; 139 cm = new ConfigMap(this, name); 140 ConfigMap cm2 = configMaps.putIfAbsent(name, cm); 141 if (cm2 != null) 142 return cm2; 143 register(name, cm); 144 return cm; 145 } 146 147 /** 148 * Called when the physical contents of a config file have changed. 149 * 150 * <p> 151 * Triggers calls to {@link ConfigStoreListener#onChange(String)} on all registered listeners. 152 * 153 * @param name The config name (e.g. the filename without the extension). 154 * @param contents The new contents. 155 * @return This object (for method chaining). 156 */ 157 public synchronized ConfigStore update(String name, String contents) { 158 name = resolveName(name); 159 Set<ConfigStoreListener> s = listeners.get(name); 160 if (s != null) 161 for (ConfigStoreListener l : listeners.get(name)) 162 l.onChange(contents); 163 return this; 164 } 165 166 /** 167 * Convenience method for updating the contents of a file with lines. 168 * 169 * @param name The config name (e.g. the filename without the extension). 170 * @param contentLines The new contents. 171 * @return This object (for method chaining). 172 */ 173 public synchronized ConfigStore update(String name, String...contentLines) { 174 name = resolveName(name); 175 StringBuilder sb = new StringBuilder(); 176 for (String l : contentLines) 177 sb.append(l).append('\n'); 178 return update(name, sb.toString()); 179 } 180 181 /** 182 * Subclasses can override this method to convert config names to internal forms. 183 * 184 * <p> 185 * For example, the {@link ConfigFileStore} class can take in both <js>"MyConfig"</js> and <js>"MyConfig.cfg"</js> 186 * as names that both resolve to <js>"MyConfig.cfg"</js>. 187 * 188 * @param name The name to resolve. 189 * @return The resolved name. 190 */ 191 protected String resolveName(String name) { 192 return name; 193 } 194 195 /** 196 * Unused. 197 */ 198 @Override /* Context */ 199 public final Session createSession(SessionArgs args) { 200 throw new NoSuchMethodError(); 201 } 202 203 /** 204 * Unused. 205 */ 206 @Override /* Context */ 207 public final SessionArgs createDefaultSessionArgs() { 208 throw new NoSuchMethodError(); 209 } 210 211 //----------------------------------------------------------------------------------------------------------------- 212 // Other methods. 213 //----------------------------------------------------------------------------------------------------------------- 214 215 @Override /* Context */ 216 public OMap toMap() { 217 return super.toMap() 218 .a("ConfigStore", new DefaultFilteringOMap() 219 ); 220 } 221}