001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.config.store;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.io.*;
023import java.lang.annotation.*;
024import java.util.concurrent.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.commons.collections.*;
028import org.apache.juneau.commons.utils.*;
029
030/**
031 * Classpath-based storage location for configuration files.
032 *
033 * <p>
034 * Looks inside the JVM classpath for configuration files.
035 *
036 * <p>
037 * Configuration files retrieved from the classpath can be modified but not persisted.
038 *
039 * <h5 class='section'>Notes:</h5><ul>
040 *    <li class='note'>This class is thread safe and reusable.
041 * </ul>
042 */
043@SuppressWarnings("resource")
044public class ClasspathStore extends ConfigStore {
045   /**
046    * Builder class.
047    */
048   public static class Builder extends ConfigStore.Builder {
049
050      /**
051       * Constructor, default settings.
052       */
053      protected Builder() {}
054
055      /**
056       * Copy constructor.
057       *
058       * @param copyFrom The builder to copy from.
059       *    <br>Cannot be <jk>null</jk>.
060       */
061      protected Builder(Builder copyFrom) {
062         super(assertArgNotNull("copyFrom", copyFrom));
063      }
064
065      /**
066       * Copy constructor.
067       *
068       * @param copyFrom The bean to copy from.
069       *    <br>Cannot be <jk>null</jk>.
070       */
071      protected Builder(ClasspathStore copyFrom) {
072         super(assertArgNotNull("copyFrom", copyFrom));
073         type(copyFrom.getClass());
074      }
075
076      @Override /* Overridden from Builder */
077      public Builder annotations(Annotation...values) {
078         super.annotations(values);
079         return this;
080      }
081
082      @Override /* Overridden from Builder */
083      public Builder apply(AnnotationWorkList work) {
084         super.apply(work);
085         return this;
086      }
087
088      @Override /* Overridden from Builder */
089      public Builder applyAnnotations(Class<?>...from) {
090         super.applyAnnotations(from);
091         return this;
092      }
093
094      @Override /* Overridden from Builder */
095      public Builder applyAnnotations(Object...from) {
096         super.applyAnnotations(from);
097         return this;
098      }
099
100      @Override /* Overridden from Context.Builder */
101      public ClasspathStore build() {
102         return build(ClasspathStore.class);
103      }
104
105      @Override /* Overridden from Builder */
106      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
107         super.cache(value);
108         return this;
109      }
110
111      @Override /* Overridden from Context.Builder */
112      public Builder copy() {
113         return new Builder(this);
114      }
115
116      @Override /* Overridden from Builder */
117      public Builder debug() {
118         super.debug();
119         return this;
120      }
121
122      @Override /* Overridden from Builder */
123      public Builder debug(boolean value) {
124         super.debug(value);
125         return this;
126      }
127
128      @Override /* Overridden from Builder */
129      public Builder impl(Context value) {
130         super.impl(value);
131         return this;
132      }
133
134      @Override /* Overridden from Builder */
135      public Builder type(Class<? extends org.apache.juneau.Context> value) {
136         super.type(value);
137         return this;
138      }
139   }
140
141   /** Default memory store, all default values.*/
142   public static final ClasspathStore DEFAULT = ClasspathStore.create().build();
143
144   /**
145    * Creates a new builder for this object.
146    *
147    * @return A new builder.
148    */
149   public static Builder create() {
150      return new Builder();
151   }
152
153   private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
154
155   /**
156    * Constructor.
157    *
158    * @param builder The builder for this object.
159    */
160   public ClasspathStore(Builder builder) {
161      super(builder);
162   }
163
164   /**
165    * No-op.
166    */
167   @Override /* Overridden from Closeable */
168   public void close() throws IOException {
169      // No-op
170   }
171
172   @Override /* Overridden from Context */
173   public Builder copy() {
174      return new Builder(this);
175   }
176
177   @Override /* Overridden from ConfigStore */
178   public synchronized boolean exists(String name) {
179      try {
180         return ! read(name).isEmpty();
181      } catch (@SuppressWarnings("unused") IOException e) {
182         return false;
183      }
184   }
185
186   @Override /* Overridden from ConfigStore */
187   public synchronized String read(String name) throws IOException {
188      var s = cache.get(name);
189      if (nn(s))
190         return s;
191
192      var cl = Thread.currentThread().getContextClassLoader();
193      try (var in = cl.getResourceAsStream(name)) {
194         if (nn(in))
195            cache.put(name, IoUtils.read(in));
196      }
197      return emptyIfNull(cache.get(name));
198   }
199
200   @Override /* Overridden from ConfigStore */
201   public synchronized ClasspathStore update(String name, String newContents) {
202      if (newContents == null)
203         cache.remove(name);
204      else
205         cache.put(name, newContents);
206      super.update(name, newContents);
207      return this;
208   }
209
210   @Override /* Overridden from ConfigStore */
211   public synchronized String write(String name, String expectedContents, String newContents) throws IOException {
212
213      // This is a no-op.
214      if (eq(expectedContents, newContents))
215         return null;
216
217      var currentContents = read(name);
218
219      if (nn(expectedContents) && neq(currentContents, expectedContents))
220         return currentContents;
221
222      update(name, newContents);
223
224      return null;
225   }
226}