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}