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