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.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.io.*;
023import java.util.concurrent.*;
024
025import org.apache.juneau.commons.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 */
040@SuppressWarnings({
041   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
042   "resource" // Resource management handled externally
043})
044public class ClasspathStore extends ConfigStore {
045
046   /**
047    * Builder class.
048    */
049   public static class Builder extends ConfigStore.Builder<Builder> {
050
051      /**
052       * Constructor, default settings.
053       */
054      protected Builder() {}
055
056      /**
057       * Copy constructor.
058       *
059       * @param copyFrom The builder to copy from.
060       *    <br>Cannot be <jk>null</jk>.
061       */
062      protected Builder(Builder copyFrom) {
063         super(reqnn("copyFrom", copyFrom));
064      }
065
066      /**
067       * Copy constructor.
068       *
069       * @param copyFrom The bean to copy from.
070       *    <br>Cannot be <jk>null</jk>.
071       */
072      protected Builder(ClasspathStore copyFrom) {
073         super(reqnn("copyFrom", copyFrom));
074         type(copyFrom.getClass());
075      }
076
077      @Override /* Overridden from Context.Builder<?> */
078      public ClasspathStore build() {
079         return build(ClasspathStore.class);
080      }
081
082      @Override /* Overridden from Context.Builder<?> */
083      public Builder copy() {
084         return new Builder(this);
085      }
086
087   }
088
089   /** Default memory store, all default values.*/
090   public static final ClasspathStore DEFAULT = ClasspathStore.create().build();
091
092   /**
093    * Creates a new builder for this object.
094    *
095    * @return A new builder.
096    */
097   public static Builder create() {
098      return new Builder();
099   }
100
101   private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
102
103   /**
104    * Constructor.
105    *
106    * @param builder The builder for this object.
107    *    <br>Cannot be <jk>null</jk>.
108    */
109   public ClasspathStore(Builder builder) {
110      super(builder);
111   }
112
113   /**
114    * No-op.
115    */
116   @Override /* Overridden from Closeable */
117   public void close() throws IOException {
118      // No-op
119   }
120
121   @Override /* Overridden from Context */
122   public Builder copy() {
123      return new Builder(this);
124   }
125
126   @Override /* Overridden from ConfigStore */
127   public synchronized boolean exists(String name) {
128      try {
129         return ! read(name).isEmpty();
130      } catch (@SuppressWarnings("unused") IOException e) {
131         // A read failure (missing/unreadable classpath resource) means the config is not available, so report it as not existing.
132         return false;
133      }
134   }
135
136   @Override /* Overridden from ConfigStore */
137   public synchronized String read(String name) throws IOException {
138      var s = cache.get(name);
139      if (nn(s))
140         return s;
141
142      var cl = Thread.currentThread().getContextClassLoader();
143      try (var in = cl.getResourceAsStream(name)) {
144         if (nn(in))
145            cache.put(name, IoUtils.read(in));
146      }
147      return emptyIfNull(cache.get(name));
148   }
149
150   @Override /* Overridden from ConfigStore */
151   public synchronized ClasspathStore update(String name, String newContents) {
152      if (newContents == null)
153         cache.remove(name);
154      else
155         cache.put(name, newContents);
156      super.update(name, newContents);
157      return this;
158   }
159
160   @Override /* Overridden from ConfigStore */
161   public synchronized String write(String name, String expectedContents, String newContents) throws IOException {
162
163      // This is a no-op.
164      if (eq(expectedContents, newContents))
165         return null;
166
167      var currentContents = read(name);
168
169      if (nn(expectedContents) && neq(currentContents, expectedContents))
170         return currentContents;
171
172      update(name, newContents);
173
174      return null;
175   }
176}