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 static org.apache.juneau.common.internal.StringUtils.*;
016
017import java.io.*;
018import java.lang.annotation.*;
019import java.lang.reflect.*;
020import java.util.concurrent.*;
021
022import org.apache.juneau.*;
023import org.apache.juneau.common.internal.*;
024import org.apache.juneau.internal.*;
025import org.apache.juneau.utils.*;
026
027/**
028 * Classpath-based storage location for configuration files.
029 *
030 * <p>
031 * Looks inside the JVM classpath for configuration files.
032 *
033 * <p>
034 * Configuration files retrieved from the classpath can be modified but not persisted.
035 *
036 * <h5 class='section'>Notes:</h5><ul>
037 *    <li class='note'>This class is thread safe and reusable.
038 * </ul>
039 */
040public class ClasspathStore extends ConfigStore {
041
042   //-------------------------------------------------------------------------------------------------------------------
043   // Static
044   //-------------------------------------------------------------------------------------------------------------------
045
046   /** Default memory store, all default values.*/
047   public static final ClasspathStore DEFAULT = ClasspathStore.create().build();
048
049   /**
050    * Creates a new builder for this object.
051    *
052    * @return A new builder.
053    */
054   public static Builder create() {
055      return new Builder();
056   }
057
058   //-------------------------------------------------------------------------------------------------------------------
059   // Builder
060   //-------------------------------------------------------------------------------------------------------------------
061
062   /**
063    * Builder class.
064    */
065   @FluentSetters
066   public static class Builder extends ConfigStore.Builder {
067
068      /**
069       * Constructor, default settings.
070       */
071      protected Builder() {
072      }
073
074      /**
075       * Copy constructor.
076       *
077       * @param copyFrom The bean to copy from.
078       */
079      protected Builder(ClasspathStore copyFrom) {
080         super(copyFrom);
081         type(copyFrom.getClass());
082      }
083
084      /**
085       * Copy constructor.
086       *
087       * @param copyFrom The builder to copy from.
088       */
089      protected Builder(Builder copyFrom) {
090         super(copyFrom);
091      }
092
093      @Override /* Context.Builder */
094      public Builder copy() {
095         return new Builder(this);
096      }
097
098      @Override /* Context.Builder */
099      public ClasspathStore build() {
100         return build(ClasspathStore.class);
101      }
102
103      //-----------------------------------------------------------------------------------------------------------------
104      // Properties
105      //-----------------------------------------------------------------------------------------------------------------
106
107      // <FluentSetters>
108
109      @Override /* GENERATED - org.apache.juneau.Context.Builder */
110      public Builder annotations(Annotation...values) {
111         super.annotations(values);
112         return this;
113      }
114
115      @Override /* GENERATED - org.apache.juneau.Context.Builder */
116      public Builder apply(AnnotationWorkList work) {
117         super.apply(work);
118         return this;
119      }
120
121      @Override /* GENERATED - org.apache.juneau.Context.Builder */
122      public Builder applyAnnotations(java.lang.Class<?>...fromClasses) {
123         super.applyAnnotations(fromClasses);
124         return this;
125      }
126
127      @Override /* GENERATED - org.apache.juneau.Context.Builder */
128      public Builder applyAnnotations(Method...fromMethods) {
129         super.applyAnnotations(fromMethods);
130         return this;
131      }
132
133      @Override /* GENERATED - org.apache.juneau.Context.Builder */
134      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
135         super.cache(value);
136         return this;
137      }
138
139      @Override /* GENERATED - org.apache.juneau.Context.Builder */
140      public Builder debug() {
141         super.debug();
142         return this;
143      }
144
145      @Override /* GENERATED - org.apache.juneau.Context.Builder */
146      public Builder debug(boolean value) {
147         super.debug(value);
148         return this;
149      }
150
151      @Override /* GENERATED - org.apache.juneau.Context.Builder */
152      public Builder impl(Context value) {
153         super.impl(value);
154         return this;
155      }
156
157      @Override /* GENERATED - org.apache.juneau.Context.Builder */
158      public Builder type(Class<? extends org.apache.juneau.Context> value) {
159         super.type(value);
160         return this;
161      }
162
163      // </FluentSetters>
164   }
165
166   //-------------------------------------------------------------------------------------------------------------------
167   // Instance
168   //-------------------------------------------------------------------------------------------------------------------
169
170   @Override /* Context */
171   public Builder copy() {
172      return new Builder(this);
173   }
174
175   private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
176
177   /**
178    * Constructor.
179    *
180    * @param builder The builder for this object.
181    */
182   public ClasspathStore(Builder builder) {
183      super(builder);
184   }
185
186   @Override /* ConfigStore */
187   public synchronized String read(String name) throws IOException {
188      String s = cache.get(name);
189      if (s != null)
190         return s;
191
192      ClassLoader cl = Thread.currentThread().getContextClassLoader();
193      try (InputStream in = cl.getResourceAsStream(name)) {
194         if (in != null)
195            cache.put(name, IOUtils.read(in));
196      }
197      return emptyIfNull(cache.get(name));
198   }
199
200   @Override /* ConfigStore */
201   public synchronized String write(String name, String expectedContents, String newContents) throws IOException {
202
203      // This is a no-op.
204      if (eq(expectedContents, newContents))
205         return null;
206
207      String currentContents = read(name);
208
209      if (expectedContents != null && ! eq(currentContents, expectedContents))
210         return currentContents;
211
212      update(name, newContents);
213
214      return null;
215   }
216
217   @Override /* ConfigStore */
218   public synchronized boolean exists(String name) {
219      try {
220         return ! read(name).isEmpty();
221      } catch (IOException e) {
222         return false;
223      }
224   }
225
226   @Override /* ConfigStore */
227   public synchronized ClasspathStore update(String name, String newContents) {
228      if (newContents == null)
229         cache.remove(name);
230      else
231         cache.put(name, newContents);
232      super.update(name, newContents);
233      return this;
234   }
235
236   /**
237    * No-op.
238    */
239   @Override /* Closeable */
240   public void close() throws IOException {
241      // No-op
242   }
243}