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 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed 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 025 package org.jboss.dna.connector.filesystem; 026 027 import java.io.File; 028 import java.io.FilenameFilter; 029 import java.util.Enumeration; 030 import java.util.HashMap; 031 import java.util.Hashtable; 032 import java.util.List; 033 import java.util.Map; 034 import java.util.concurrent.CopyOnWriteArraySet; 035 import javax.naming.Context; 036 import javax.naming.RefAddr; 037 import javax.naming.Reference; 038 import javax.naming.StringRefAddr; 039 import javax.naming.spi.ObjectFactory; 040 import net.jcip.annotations.Immutable; 041 import net.jcip.annotations.ThreadSafe; 042 import org.jboss.dna.common.i18n.I18n; 043 import org.jboss.dna.common.util.Logger; 044 import org.jboss.dna.common.util.StringUtil; 045 import org.jboss.dna.graph.cache.CachePolicy; 046 import org.jboss.dna.graph.connector.RepositoryConnection; 047 import org.jboss.dna.graph.connector.RepositoryContext; 048 import org.jboss.dna.graph.connector.RepositorySource; 049 import org.jboss.dna.graph.connector.RepositorySourceCapabilities; 050 import org.jboss.dna.graph.connector.RepositorySourceException; 051 052 /** 053 * The {@link RepositorySource} for the connector that exposes an area of the local file system as content in a repository. This 054 * source considers a workspace name to be the path to the directory on the file system that represents the root of that 055 * workspace. New workspaces can be created, as long as the names represent valid paths to existing directories. 056 * 057 * @author Randall Hauch 058 */ 059 @ThreadSafe 060 public class FileSystemSource implements RepositorySource, ObjectFactory { 061 062 /** 063 * The first serialized version of this source. Version {@value} . 064 */ 065 private static final long serialVersionUID = 1L; 066 067 protected static final String SOURCE_NAME = "sourceName"; 068 protected static final String CACHE_TIME_TO_LIVE_IN_MILLISECONDS = "cacheTimeToLiveInMilliseconds"; 069 protected static final String RETRY_LIMIT = "retryLimit"; 070 protected static final String DEFAULT_WORKSPACE = "defaultWorkspace"; 071 protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames"; 072 protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces"; 073 074 /** 075 * This source supports events. 076 */ 077 protected static final boolean SUPPORTS_EVENTS = true; 078 /** 079 * This source supports same-name-siblings. 080 */ 081 protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = true; 082 /** 083 * This source does support creating workspaces. 084 */ 085 protected static final boolean DEFAULT_SUPPORTS_CREATING_WORKSPACES = true; 086 /** 087 * This source does not support udpates by default, but each instance may be configured to be read-only or updateable}. 088 */ 089 public static final boolean DEFAULT_SUPPORTS_UPDATES = false; 090 091 /** 092 * This source supports creating references. 093 */ 094 protected static final boolean SUPPORTS_REFERENCES = false; 095 096 public static final int DEFAULT_RETRY_LIMIT = 0; 097 public static final int DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS = 60 * 5; // 5 minutes 098 099 private volatile String name; 100 private volatile int retryLimit = DEFAULT_RETRY_LIMIT; 101 private volatile int cacheTimeToLiveInMilliseconds = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS * 1000; 102 private volatile String defaultWorkspace; 103 private volatile String[] predefinedWorkspaces = new String[] {}; 104 private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities( 105 SUPPORTS_SAME_NAME_SIBLINGS, 106 DEFAULT_SUPPORTS_UPDATES, 107 SUPPORTS_EVENTS, 108 DEFAULT_SUPPORTS_CREATING_WORKSPACES, 109 SUPPORTS_REFERENCES); 110 private transient CachePolicy cachePolicy; 111 private transient CopyOnWriteArraySet<String> availableWorkspaceNames; 112 113 /** 114 * 115 */ 116 public FileSystemSource() { 117 } 118 119 /** 120 * {@inheritDoc} 121 * 122 * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities() 123 */ 124 public RepositorySourceCapabilities getCapabilities() { 125 return capabilities; 126 } 127 128 /** 129 * {@inheritDoc} 130 * 131 * @see org.jboss.dna.graph.connector.RepositorySource#getName() 132 */ 133 public String getName() { 134 return name; 135 } 136 137 /** 138 * Set the name for the source 139 * 140 * @param name the new name for the source 141 */ 142 public synchronized void setName( String name ) { 143 if (name != null) { 144 name = name.trim(); 145 if (name.length() == 0) name = null; 146 } 147 this.name = name; 148 } 149 150 /** 151 * Get whether this source supports updates. 152 * 153 * @return true if this source supports updates, or false if this source only supports reading content. 154 */ 155 public boolean getSupportsUpdates() { 156 return capabilities.supportsUpdates(); 157 } 158 159 // /** 160 // * Set whether this source supports updates. 161 // * 162 // * @param supportsUpdates true if this source supports updating content, or false if this source only supports reading 163 // * content. 164 // */ 165 // public synchronized void setSupportsUpdates( boolean supportsUpdates ) { 166 // capabilities = new RepositorySourceCapabilities(SUPPORTS_SAME_NAME_SIBLINGS, 167 // supportsUpdates, 168 // SUPPORTS_EVENTS, 169 // capabilities.supportsCreatingWorkspaces()); 170 // } 171 172 /** 173 * Get the file system path to the existing directory that should be used for the default workspace. If the default is 174 * specified as a null String or is not a valid and resolvable path, this source will consider the default to be the current 175 * working directory of this virtual machine, as defined by the <code>new File(".")</code>. 176 * 177 * @return the file system path to the directory representing the default workspace, or null if the default should be the 178 * current working directory 179 */ 180 public String getDirectoryForDefaultWorkspace() { 181 return defaultWorkspace; 182 } 183 184 /** 185 * Set the file system path to the existing directory that should be used for the default workspace. If the default is 186 * specified as a null String or is not a valid and resolvable path, this source will consider the default to be the current 187 * working directory of this virtual machine, as defined by the <code>new File(".")</code>. 188 * 189 * @param pathToDirectoryForDefaultWorkspace the valid and resolvable file system path to the directory representing the 190 * default workspace, or null if the current working directory should be used as the default workspace 191 */ 192 public synchronized void setDirectoryForDefaultWorkspace( String pathToDirectoryForDefaultWorkspace ) { 193 this.defaultWorkspace = pathToDirectoryForDefaultWorkspace; 194 } 195 196 /** 197 * Gets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path 198 * to a directory on the file system. 199 * 200 * @return the names of the workspaces that this source starts with, or null if there are no such workspaces 201 * @see #setPredefinedWorkspaceNames(String[]) 202 * @see #setCreatingWorkspacesAllowed(boolean) 203 */ 204 public synchronized String[] getPredefinedWorkspaceNames() { 205 String[] copy = new String[predefinedWorkspaces.length]; 206 System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length); 207 return copy; 208 } 209 210 /** 211 * Sets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path 212 * to a directory on the file system. 213 * 214 * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no 215 * such workspaces 216 * @see #setCreatingWorkspacesAllowed(boolean) 217 * @see #getPredefinedWorkspaceNames() 218 */ 219 public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) { 220 this.predefinedWorkspaces = predefinedWorkspaceNames; 221 } 222 223 /** 224 * Get whether this source allows workspaces to be created dynamically. 225 * 226 * @return true if this source allows workspaces to be created by clients, or false if the set of workspaces is fixed 227 * @see #setPredefinedWorkspaceNames(String[]) 228 * @see #getPredefinedWorkspaceNames() 229 * @see #setCreatingWorkspacesAllowed(boolean) 230 */ 231 public boolean isCreatingWorkspacesAllowed() { 232 return capabilities.supportsCreatingWorkspaces(); 233 } 234 235 /** 236 * Set whether this source allows workspaces to be created dynamically. 237 * 238 * @param allowWorkspaceCreation true if this source allows workspaces to be created by clients, or false if the set of 239 * workspaces is fixed 240 * @see #setPredefinedWorkspaceNames(String[]) 241 * @see #getPredefinedWorkspaceNames() 242 * @see #isCreatingWorkspacesAllowed() 243 */ 244 public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) { 245 capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), capabilities.supportsUpdates(), 246 capabilities.supportsEvents(), allowWorkspaceCreation, 247 capabilities.supportsReferences()); 248 } 249 250 /** 251 * {@inheritDoc} 252 * 253 * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit() 254 */ 255 public int getRetryLimit() { 256 return retryLimit; 257 } 258 259 /** 260 * {@inheritDoc} 261 * 262 * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int) 263 */ 264 public synchronized void setRetryLimit( int limit ) { 265 this.retryLimit = limit < 0 ? 0 : limit; 266 } 267 268 /** 269 * Get the time in milliseconds that content returned from this source may used while in the cache. 270 * 271 * @return the time to live, in milliseconds, or 0 if the time to live is not specified by this source 272 */ 273 public int getCacheTimeToLiveInMilliseconds() { 274 return cacheTimeToLiveInMilliseconds; 275 } 276 277 /** 278 * Set the time in milliseconds that content returned from this source may used while in the cache. 279 * 280 * @param cacheTimeToLive the time to live, in milliseconds; 0 if the time to live is not specified by this source; or a 281 * negative number for the default value 282 */ 283 public synchronized void setCacheTimeToLiveInMilliseconds( int cacheTimeToLive ) { 284 if (cacheTimeToLive < 0) cacheTimeToLive = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS; 285 this.cacheTimeToLiveInMilliseconds = cacheTimeToLive; 286 this.cachePolicy = cacheTimeToLiveInMilliseconds > 0 ? new FileSystemCachePolicy(cacheTimeToLiveInMilliseconds) : null; 287 } 288 289 /** 290 * {@inheritDoc} 291 * 292 * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext) 293 */ 294 public synchronized void initialize( RepositoryContext context ) throws RepositorySourceException { 295 // No need to do anything 296 } 297 298 /** 299 * {@inheritDoc} 300 * 301 * @see javax.naming.Referenceable#getReference() 302 */ 303 public synchronized Reference getReference() { 304 String className = getClass().getName(); 305 String factoryClassName = this.getClass().getName(); 306 Reference ref = new Reference(className, factoryClassName, null); 307 308 if (getName() != null) { 309 ref.add(new StringRefAddr(SOURCE_NAME, getName())); 310 } 311 ref.add(new StringRefAddr(CACHE_TIME_TO_LIVE_IN_MILLISECONDS, Integer.toString(getCacheTimeToLiveInMilliseconds()))); 312 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit()))); 313 ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDirectoryForDefaultWorkspace())); 314 ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed()))); 315 String[] workspaceNames = getPredefinedWorkspaceNames(); 316 if (workspaceNames != null && workspaceNames.length != 0) { 317 ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames))); 318 } 319 return ref; 320 } 321 322 /** 323 * {@inheritDoc} 324 */ 325 public Object getObjectInstance( Object obj, 326 javax.naming.Name name, 327 Context nameCtx, 328 Hashtable<?, ?> environment ) throws Exception { 329 if (obj instanceof Reference) { 330 Map<String, String> values = new HashMap<String, String>(); 331 Reference ref = (Reference)obj; 332 Enumeration<?> en = ref.getAll(); 333 while (en.hasMoreElements()) { 334 RefAddr subref = (RefAddr)en.nextElement(); 335 if (subref instanceof StringRefAddr) { 336 String key = subref.getType(); 337 Object value = subref.getContent(); 338 if (value != null) values.put(key, value.toString()); 339 } 340 } 341 String sourceName = values.get(SOURCE_NAME); 342 String cacheTtlInMillis = values.get(CACHE_TIME_TO_LIVE_IN_MILLISECONDS); 343 String retryLimit = values.get(RETRY_LIMIT); 344 String defaultWorkspace = values.get(DEFAULT_WORKSPACE); 345 String createWorkspaces = values.get(ALLOW_CREATING_WORKSPACES); 346 347 String combinedWorkspaceNames = values.get(PREDEFINED_WORKSPACE_NAMES); 348 String[] workspaceNames = null; 349 if (combinedWorkspaceNames != null) { 350 List<String> paths = StringUtil.splitLines(combinedWorkspaceNames); 351 workspaceNames = paths.toArray(new String[paths.size()]); 352 } 353 354 // Create the source instance ... 355 FileSystemSource source = new FileSystemSource(); 356 if (sourceName != null) source.setName(sourceName); 357 if (cacheTtlInMillis != null) source.setCacheTimeToLiveInMilliseconds(Integer.parseInt(cacheTtlInMillis)); 358 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); 359 if (defaultWorkspace != null) source.setDirectoryForDefaultWorkspace(defaultWorkspace); 360 if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces)); 361 if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames); 362 return source; 363 } 364 return null; 365 } 366 367 /** 368 * {@inheritDoc} 369 * 370 * @see org.jboss.dna.graph.connector.RepositorySource#getConnection() 371 */ 372 public synchronized RepositoryConnection getConnection() throws RepositorySourceException { 373 String sourceName = getName(); 374 if (sourceName == null || sourceName.trim().length() == 0) { 375 I18n msg = FileSystemI18n.propertyIsRequired; 376 throw new RepositorySourceException(getName(), msg.text("name")); 377 } 378 379 boolean reportWarnings = false; 380 if (this.availableWorkspaceNames == null) { 381 // Set up the predefined workspace names ... 382 this.availableWorkspaceNames = new CopyOnWriteArraySet<String>(); 383 for (String predefined : this.predefinedWorkspaces) { 384 this.availableWorkspaceNames.add(predefined); 385 } 386 387 // Report the warnings for non-existant predefined workspaces 388 reportWarnings = true; 389 for (String path : this.availableWorkspaceNames) { 390 // Look for the file at this path ... 391 File file = new File(path); 392 if (!file.exists()) { 393 Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceDoesNotExist, path, name); 394 } else if (!file.isDirectory()) { 395 Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceIsNotDirectory, path, name); 396 } else if (!file.canRead()) { 397 Logger.getLogger(getClass()).warn(FileSystemI18n.pathForPredefinedWorkspaceCannotBeRead, path, name); 398 } 399 } 400 } 401 402 FilenameFilter filenameFilter = null; 403 boolean supportsUpdates = getSupportsUpdates(); 404 File defaultWorkspace = new File("."); 405 String path = getDirectoryForDefaultWorkspace(); 406 if (path != null) { 407 // Look for the file at this path ... 408 File file = new File(path); 409 I18n warning = null; 410 if (!file.exists()) { 411 warning = FileSystemI18n.pathForDefaultWorkspaceDoesNotExist; 412 } else if (!file.isDirectory()) { 413 warning = FileSystemI18n.pathForDefaultWorkspaceIsNotDirectory; 414 } else if (!file.canRead()) { 415 warning = FileSystemI18n.pathForDefaultWorkspaceCannotBeRead; 416 } else { 417 // good to use! 418 defaultWorkspace = file; 419 } 420 if (reportWarnings && warning != null) { 421 Logger.getLogger(getClass()).warn(warning, path, name); 422 } 423 } 424 this.availableWorkspaceNames.add(defaultWorkspace.getPath()); 425 return new FileSystemConnection(name, defaultWorkspace, availableWorkspaceNames, isCreatingWorkspacesAllowed(), 426 cachePolicy, filenameFilter, supportsUpdates); 427 } 428 429 @Immutable 430 /*package*/class FileSystemCachePolicy implements CachePolicy { 431 private static final long serialVersionUID = 1L; 432 private final int ttl; 433 434 /*package*/FileSystemCachePolicy( int ttl ) { 435 this.ttl = ttl; 436 } 437 438 public long getTimeToLive() { 439 return ttl; 440 } 441 442 } 443 444 }