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, false); 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 * Checks whether the configuration with the specified name exists in this store. 073 * 074 * @param name The config name. 075 * @return <jk>true</jk> if the configuration with the specified name exists in this store. 076 */ 077 public abstract boolean exists(String name); 078 079 /** 080 * Registers a new listener on this store. 081 * 082 * @param name The configuration name to listen for. 083 * @param l The new listener. 084 * @return This object (for method chaining). 085 */ 086 public synchronized ConfigStore register(String name, ConfigStoreListener l) { 087 name = resolveName(name); 088 Set<ConfigStoreListener> s = listeners.get(name); 089 if (s == null) { 090 s = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<ConfigStoreListener,Boolean>())); 091 listeners.put(name, s); 092 } 093 s.add(l); 094 return this; 095 } 096 097 /** 098 * Unregisters a listener from this store. 099 * 100 * @param name The configuration name to listen for. 101 * @param l The listener to unregister. 102 * @return This object (for method chaining). 103 */ 104 public synchronized ConfigStore unregister(String name, ConfigStoreListener l) { 105 name = resolveName(name); 106 Set<ConfigStoreListener> s = listeners.get(name); 107 if (s != null) 108 s.remove(l); 109 return this; 110 } 111 112 /** 113 * Returns a map file containing the parsed contents of a configuration. 114 * 115 * @param name The configuration name. 116 * @return 117 * The parsed configuration. 118 * <br>Never <jk>null</jk>. 119 * @throws IOException 120 */ 121 public synchronized ConfigMap getMap(String name) throws IOException { 122 name = resolveName(name); 123 ConfigMap cm = configMaps.get(name); 124 if (cm != null) 125 return cm; 126 cm = new ConfigMap(this, name); 127 ConfigMap cm2 = configMaps.putIfAbsent(name, cm); 128 if (cm2 != null) 129 return cm2; 130 register(name, cm); 131 return cm; 132 } 133 134 /** 135 * Called when the physical contents of a config file have changed. 136 * 137 * <p> 138 * Triggers calls to {@link ConfigStoreListener#onChange(String)} on all registered listeners. 139 * 140 * @param name The config name (e.g. the filename without the extension). 141 * @param contents The new contents. 142 * @return This object (for method chaining). 143 */ 144 public synchronized ConfigStore update(String name, String contents) { 145 name = resolveName(name); 146 Set<ConfigStoreListener> s = listeners.get(name); 147 if (s != null) 148 for (ConfigStoreListener l : listeners.get(name)) 149 l.onChange(contents); 150 return this; 151 } 152 153 /** 154 * Convenience method for updating the contents of a file with lines. 155 * 156 * @param name The config name (e.g. the filename without the extension). 157 * @param contentLines The new contents. 158 * @return This object (for method chaining). 159 */ 160 public synchronized ConfigStore update(String name, String...contentLines) { 161 name = resolveName(name); 162 StringBuilder sb = new StringBuilder(); 163 for (String l : contentLines) 164 sb.append(l).append('\n'); 165 return update(name, sb.toString()); 166 } 167 168 /** 169 * Subclasses can override this method to convert config names to internal forms. 170 * 171 * <p> 172 * For example, the {@link ConfigFileStore} class can take in both <js>"MyConfig"</js> and <js>"MyConfig.cfg"</js> 173 * as names that both resolve to <js>"MyConfig.cfg"</js>. 174 * 175 * @param name The name to resolve. 176 * @return The resolved name. 177 */ 178 protected String resolveName(String name) { 179 return name; 180 } 181 182 /** 183 * Unused. 184 */ 185 @Override /* Context */ 186 public final Session createSession(SessionArgs args) { 187 throw new NoSuchMethodError(); 188 } 189 190 /** 191 * Unused. 192 */ 193 @Override /* Context */ 194 public final SessionArgs createDefaultSessionArgs() { 195 throw new NoSuchMethodError(); 196 } 197}