001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * Unless otherwise indicated, all code in JBoss DNA is licensed
010 * to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.connector.jbosscache;
025
026 import java.net.URI;
027 import java.net.URISyntaxException;
028 import java.util.Collections;
029 import java.util.HashSet;
030 import java.util.Set;
031 import java.util.concurrent.ConcurrentHashMap;
032 import java.util.concurrent.locks.Lock;
033 import java.util.concurrent.locks.ReentrantLock;
034 import javax.naming.Context;
035 import net.jcip.annotations.GuardedBy;
036 import net.jcip.annotations.ThreadSafe;
037 import org.jboss.cache.Cache;
038 import org.jboss.cache.CacheFactory;
039 import org.jboss.cache.config.ConfigurationException;
040 import org.jboss.dna.common.i18n.I18n;
041 import org.jboss.dna.common.util.Logger;
042 import org.jboss.dna.graph.connector.RepositorySourceException;
043 import org.jboss.dna.graph.property.Name;
044
045 /**
046 * This class represents a set of workspaces used by the {@link JBossCacheSource JBoss Cache connector}.
047 */
048 @ThreadSafe
049 public class JBossCacheWorkspaces {
050
051 private final String sourceName;
052 private final ConcurrentHashMap<String, Cache<Name, Object>> caches = new ConcurrentHashMap<String, Cache<Name, Object>>();
053 private final Set<String> initialNames;
054 private final CacheFactory<Name, Object> cacheFactory;
055 private final String defaultCacheFactoryConfigurationName;
056 private final Context jndi;
057 private final Set<String> workspaceNamesForJndiClassCastProblems = new HashSet<String>();
058 private final Set<String> workspaceNamesForConfigurationNameProblems = new HashSet<String>();
059 private final Lock writeLock = new ReentrantLock();
060
061 /**
062 * Create a new instance of the workspace and cache manager for the JBoss Cache connector.
063 *
064 * @param sourceName the name of the source that uses this object; may not be null
065 * @param cacheFactory the factory that should be used to create new caches; may not be null
066 * @param defaultCacheFactoryConfigurationName the name of the configuration that is supplied to the {@link CacheFactory cache
067 * factory} to {@link CacheFactory#createCache(String) create the new cache} if the workspace name does not correspond
068 * to a configuration; may be null
069 * @param initialNames the initial names for the workspaces; may be null or empty
070 * @param jndiContext the JNDI context that should be used, or null if JNDI should not be used at all
071 */
072 public JBossCacheWorkspaces( String sourceName,
073 CacheFactory<Name, Object> cacheFactory,
074 String defaultCacheFactoryConfigurationName,
075 Set<String> initialNames,
076 Context jndiContext ) {
077 assert sourceName != null;
078 this.sourceName = sourceName;
079 if (initialNames == null) initialNames = Collections.emptySet();
080 this.initialNames = initialNames;
081 this.cacheFactory = cacheFactory;
082 this.defaultCacheFactoryConfigurationName = defaultCacheFactoryConfigurationName;
083 this.jndi = jndiContext;
084 }
085
086 /**
087 * Attempt to create a new workspace with the supplied name.
088 *
089 * @param workspaceName the name of the new workspace, which may be a valid URI if the cache is to be found in JNDI
090 * @return the new workspace, or null if there is already a workspace with the name
091 */
092 public Cache<Name, Object> createWorkspace( String workspaceName ) {
093 try {
094 writeLock.lock();
095 // First, see if there is already an existing cache ...
096 Cache<Name, Object> cache = caches.get(workspaceName);
097 if (cache != null) {
098 // There is already a workspace, so we can't create ...
099 return null;
100 }
101
102 // There isn't already a cache, but next check the list of initial names ...
103 if (initialNames.contains(workspaceName)) {
104 // The workspace already exists, but we just haven't accessed it yet
105 return null;
106 }
107
108 // Time to create a new cache. First see if we're supposed to use a cache already in JNDI ...
109 cache = findCacheInJndi(workspaceName);
110 if (cache == null) {
111 // Try to create one ...
112 cache = createNewCache(workspaceName);
113 }
114
115 if (cache != null) {
116 // Manage this cache ...
117 Cache<Name, Object> existing = caches.putIfAbsent(workspaceName, cache);
118 if (existing != null) cache = existing;
119 }
120 return cache; // may still be null if we couldn't create a new cache
121
122 } finally {
123 writeLock.unlock();
124 }
125 }
126
127 /**
128 * Get the cache that corresponds to the supplied workspace name, and optionally create a new cache if no such cache already
129 * exists. This method first checks for {@link Cache} instances previously found for the same workspace name. If no cache is
130 * found, this method then checks whether the supplied workspace name is a valid URI, and if so the method looks for a
131 * {@link Cache} instance in JNDI at that URI. If none is found (or the name is not a valid URI), this method then creates a
132 * new {@link Cache} instance using the {@link CacheFactory} supplied in the constructor.
133 *
134 * @param workspaceName the name of the workspace, which may be a valid URI if the cache is to be found in JNDI
135 * @param createIfMissing true if the cache should be created if no such cache already exists
136 * @return the cache that corresponds to the workspace with the supplied name, or null if there is no cache for that workspace
137 * (and one could not be or was not created)
138 */
139 public Cache<Name, Object> getWorkspace( String workspaceName,
140 boolean createIfMissing ) {
141 // First, see if there is already an existing cache ...
142 Cache<Name, Object> cache = caches.get(workspaceName);
143 if (cache != null) return cache;
144
145 try {
146 writeLock.lock();
147 // Ensure one didn't get created while we waited for the lock ...
148 cache = caches.get(workspaceName);
149 if (cache != null) return cache;
150
151 // We've not yet come across the cache for the workspace.
152
153 // Check whether the workspace name was one of the initial set of names...
154 if (this.initialNames.contains(workspaceName)) {
155 // This workspace/cache was one of those defined at startup to be available,
156 // so we really don't consider this to be "creating a new cache"; it's just the first time we've used it
157 // and we're lazily finding the instances. So, just mark 'createIfMissing' to true and continue ...
158 createIfMissing = true;
159 }
160
161 if (!createIfMissing) return null;
162
163 // First see if we can find a cache in JNDI ...
164 cache = findCacheInJndi(workspaceName);
165
166 if (cache == null) {
167 // Try to create one ...
168 cache = createNewCache(workspaceName);
169 }
170
171 if (cache != null) {
172 Cache<Name, Object> existing = caches.putIfAbsent(workspaceName, cache);
173 if (existing != null) cache = existing;
174 }
175 return cache; // may still be null if we couldn't create a new cache
176 } finally {
177 writeLock.unlock();
178 }
179 }
180
181 /**
182 * Attempt to find an existing {@link Cache} object in JNDI, using the supplied workspace name as the JNDI name.
183 *
184 * @param workspaceName the name of the workspace
185 * @return the cache found in JNDI that corresponds to the workspace name
186 */
187 @SuppressWarnings( "unchecked" )
188 @GuardedBy( "writeLock" )
189 protected Cache<Name, Object> findCacheInJndi( String workspaceName ) {
190 assert workspaceName != null;
191 if (jndi == null) return null;
192
193 // Try to look up the cache instance in JDNI ...
194 workspaceName = workspaceName.trim();
195 if (workspaceName.length() != 0) {
196 try {
197 new URI(workspaceName.trim());
198 Object object = null;
199 try {
200 object = jndi.lookup(workspaceName);
201 if (object != null && object instanceof Cache) {
202 return (Cache<Name, Object>)object;
203 }
204 } catch (ClassCastException err) {
205 // The object found in JNDI was not a JBoss Cache instance ...
206 if (this.workspaceNamesForJndiClassCastProblems.add(workspaceName)) {
207 // Log this problem only the first time ...
208 String className = object != null ? object.getClass().getName() : "null";
209 I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCache;
210 Logger.getLogger(getClass()).warn(msg, workspaceName, sourceName, className);
211 }
212 } catch (Throwable error) {
213 // try loading
214 if (error instanceof RuntimeException) throw (RuntimeException)error;
215 throw new RepositorySourceException(sourceName, error);
216 }
217
218 } catch (URISyntaxException err) {
219 // Not a valid URI, so just continue ...
220 }
221 }
222 return null;
223 }
224
225 /**
226 * Method that is responsible for attempting to create a new cache given the supplied workspace name. Note that this is
227 * probably called at most once for each workspace name (except if this method fails to create a cache for a given workspace
228 * name).
229 *
230 * @param workspaceName the name of the workspace
231 * @return the new cache that corresponds to the workspace name
232 */
233 @GuardedBy( "writeLock" )
234 protected Cache<Name, Object> createNewCache( String workspaceName ) {
235 assert workspaceName != null;
236 if (this.cacheFactory == null) return null;
237
238 // Try to create the cache using the workspace name as the configuration ...
239 try {
240 return this.cacheFactory.createCache(workspaceName);
241 } catch (ConfigurationException error) {
242 // The workspace name is probably not the name of a configuration ...
243 I18n msg = JBossCacheConnectorI18n.workspaceNameWasNotValidConfiguration;
244 Logger.getLogger(getClass()).debug(msg.text(workspaceName, error.getMessage()));
245 }
246
247 if (this.defaultCacheFactoryConfigurationName != null) {
248 // Try to create the cache using the default configuration name ...
249 try {
250 return this.cacheFactory.createCache(this.defaultCacheFactoryConfigurationName);
251 } catch (ConfigurationException error) {
252 // The default configuration name is not valid ...
253 if (this.workspaceNamesForConfigurationNameProblems.add(workspaceName)) {
254 // Log this problem only the first time ...
255 I18n msg = JBossCacheConnectorI18n.defaultCacheFactoryConfigurationNameWasNotValidConfiguration;
256 Logger.getLogger(getClass()).debug(msg.text(workspaceName));
257 }
258 }
259 }
260
261 // Just create a new cache with the default configuration ...
262 return this.cacheFactory.createCache();
263 }
264
265 /**
266 * Return an immutable set of names for the currently available workspaces.
267 *
268 * @return the immutable set of workspace names; never null
269 */
270 public Set<String> getWorkspaceNames() {
271 Set<String> names = new HashSet<String>();
272 if (!initialNames.isEmpty()) names.addAll(initialNames);
273 names.addAll(caches.keySet());
274 return Collections.unmodifiableSet(names);
275 }
276
277 /**
278 * Remove the cache that corresponds to the supplied workspace name as no longer being available. This will remove the cache
279 * even if the workspace name is one of the "initial names" provided to this object's constructor.
280 *
281 * @param workspaceName the name of the existing workspace that is to be removed
282 * @return true if there was an existing workspace that was removed by this call, or false if there was no workspace with the
283 * supplied name
284 */
285 public boolean removeWorkspace( String workspaceName ) {
286 try {
287 writeLock.lock();
288
289 // Remove this from both the cache and initialNames ...
290 boolean removed = initialNames.remove(workspaceName);
291 if (caches.remove(workspaceName) != null) removed = true;
292 return removed;
293 } finally {
294 writeLock.unlock();
295 }
296 }
297 }