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}