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         super();
073      }
074
075      /**
076       * Copy constructor.
077       *
078       * @param copyFrom The bean to copy from.
079       */
080      protected Builder(ClasspathStore copyFrom) {
081         super(copyFrom);
082         type(copyFrom.getClass());
083      }
084
085      /**
086       * Copy constructor.
087       *
088       * @param copyFrom The builder to copy from.
089       */
090      protected Builder(Builder copyFrom) {
091         super(copyFrom);
092      }
093
094      @Override /* Context.Builder */
095      public Builder copy() {
096         return new Builder(this);
097      }
098
099      @Override /* Context.Builder */
100      public ClasspathStore build() {
101         return build(ClasspathStore.class);
102      }
103
104      //-----------------------------------------------------------------------------------------------------------------
105      // Properties
106      //-----------------------------------------------------------------------------------------------------------------
107
108      // <FluentSetters>
109
110      @Override /* GENERATED - org.apache.juneau.Context.Builder */
111      public Builder annotations(Annotation...values) {
112         super.annotations(values);
113         return this;
114      }
115
116      @Override /* GENERATED - org.apache.juneau.Context.Builder */
117      public Builder apply(AnnotationWorkList work) {
118         super.apply(work);
119         return this;
120      }
121
122      @Override /* GENERATED - org.apache.juneau.Context.Builder */
123      public Builder applyAnnotations(java.lang.Class<?>...fromClasses) {
124         super.applyAnnotations(fromClasses);
125         return this;
126      }
127
128      @Override /* GENERATED - org.apache.juneau.Context.Builder */
129      public Builder applyAnnotations(Method...fromMethods) {
130         super.applyAnnotations(fromMethods);
131         return this;
132      }
133
134      @Override /* GENERATED - org.apache.juneau.Context.Builder */
135      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
136         super.cache(value);
137         return this;
138      }
139
140      @Override /* GENERATED - org.apache.juneau.Context.Builder */
141      public Builder debug() {
142         super.debug();
143         return this;
144      }
145
146      @Override /* GENERATED - org.apache.juneau.Context.Builder */
147      public Builder debug(boolean value) {
148         super.debug(value);
149         return this;
150      }
151
152      @Override /* GENERATED - org.apache.juneau.Context.Builder */
153      public Builder impl(Context value) {
154         super.impl(value);
155         return this;
156      }
157
158      @Override /* GENERATED - org.apache.juneau.Context.Builder */
159      public Builder type(Class<? extends org.apache.juneau.Context> value) {
160         super.type(value);
161         return this;
162      }
163
164      // </FluentSetters>
165   }
166
167   //-------------------------------------------------------------------------------------------------------------------
168   // Instance
169   //-------------------------------------------------------------------------------------------------------------------
170
171   @Override /* Context */
172   public Builder copy() {
173      return new Builder(this);
174   }
175
176   private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
177
178   /**
179    * Constructor.
180    *
181    * @param builder The builder for this object.
182    */
183   public ClasspathStore(Builder builder) {
184      super(builder);
185   }
186
187   @Override /* ConfigStore */
188   public synchronized String read(String name) throws IOException {
189      String s = cache.get(name);
190      if (s != null)
191         return s;
192
193      ClassLoader cl = Thread.currentThread().getContextClassLoader();
194      try (InputStream in = cl.getResourceAsStream(name)) {
195         if (in != null)
196            cache.put(name, IOUtils.read(in));
197      }
198      return emptyIfNull(cache.get(name));
199   }
200
201   @Override /* ConfigStore */
202   public synchronized String write(String name, String expectedContents, String newContents) throws IOException {
203
204      // This is a no-op.
205      if (eq(expectedContents, newContents))
206         return null;
207
208      String currentContents = read(name);
209
210      if (expectedContents != null && ! eq(currentContents, expectedContents))
211         return currentContents;
212
213      update(name, newContents);
214
215      return null;
216   }
217
218   @Override /* ConfigStore */
219   public synchronized boolean exists(String name) {
220      try {
221         return ! read(name).isEmpty();
222      } catch (IOException e) {
223         return false;
224      }
225   }
226
227   @Override /* ConfigStore */
228   public synchronized ClasspathStore update(String name, String newContents) {
229      if (newContents == null)
230         cache.remove(name);
231      else
232         cache.put(name, newContents);
233      super.update(name, newContents);
234      return this;
235   }
236
237   /**
238    * No-op.
239    */
240   @Override /* Closeable */
241   public void close() throws IOException {
242      // No-op
243   }
244}