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 }