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 package org.jboss.dna.connector.svn; 025 026 import java.io.ByteArrayInputStream; 027 import java.io.ByteArrayOutputStream; 028 import java.io.IOException; 029 import java.io.ObjectInputStream; 030 import java.io.ObjectOutputStream; 031 import java.util.Enumeration; 032 import java.util.HashMap; 033 import java.util.Hashtable; 034 import java.util.Map; 035 import java.util.concurrent.atomic.AtomicInteger; 036 import javax.naming.BinaryRefAddr; 037 import javax.naming.Context; 038 import javax.naming.Name; 039 import javax.naming.RefAddr; 040 import javax.naming.Reference; 041 import javax.naming.Referenceable; 042 import javax.naming.StringRefAddr; 043 import javax.naming.spi.ObjectFactory; 044 import org.jboss.dna.common.i18n.I18n; 045 import org.jboss.dna.common.util.CheckArg; 046 import org.jboss.dna.graph.cache.CachePolicy; 047 import org.jboss.dna.graph.connector.RepositoryConnection; 048 import org.jboss.dna.graph.connector.RepositoryContext; 049 import org.jboss.dna.graph.connector.RepositorySource; 050 import org.jboss.dna.graph.connector.RepositorySourceCapabilities; 051 import org.jboss.dna.graph.connector.RepositorySourceException; 052 import org.tmatesoft.svn.core.SVNException; 053 import org.tmatesoft.svn.core.SVNURL; 054 import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; 055 import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; 056 import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; 057 import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; 058 import org.tmatesoft.svn.core.io.SVNRepository; 059 import org.tmatesoft.svn.core.io.SVNRepositoryFactory; 060 import org.tmatesoft.svn.core.wc.SVNWCUtil; 061 062 /** 063 * A repository source that uses a SVN repository instance to manage the content. This source is capable of using an existing 064 * {@link SVNRepository} instance or creating a new instance. This process is controlled entirely by the JavaBean properties of 065 * the SVNRepositorySource instance. Like other {@link RepositorySource} classes, instances of SVNRepositorySource can be placed 066 * into JNDI and do support the creation of {@link Referenceable JNDI referenceable} objects and resolution of references into 067 * SVNRepositorySource. </p> 068 * 069 * @author Serge Pagop 070 */ 071 public class SVNRepositorySource implements RepositorySource, ObjectFactory { 072 073 private static final long serialVersionUID = 1L; 074 /** 075 * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source. 076 */ 077 public static final int DEFAULT_RETRY_LIMIT = 0; 078 079 /** 080 * This source supports events. 081 */ 082 protected static final boolean SUPPORTS_EVENTS = true; 083 /** 084 * This source supports same-name-siblings. 085 */ 086 protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = false; 087 /** 088 * This source supports creating workspaces. 089 */ 090 protected static final boolean SUPPORTS_CREATING_WORKSPACES = false; 091 /** 092 * This source supports creating references. 093 */ 094 protected static final boolean SUPPORTS_REFERENCES = false; 095 /** 096 * This source supports udpates by default, but each instance may be configured to {@link #setSupportsUpdates(boolean) be 097 * read-only or updateable}. 098 */ 099 public static final boolean DEFAULT_SUPPORTS_UPDATES = true; 100 101 public static final int DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS = 60 * 5; // 5 minutes 102 103 protected static final String SOURCE_NAME = "sourceName"; 104 protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy"; 105 protected static final String SVN_REPOS_JNDI_NAME = "svnReposJndiName"; 106 protected static final String SVN_REPOS_FACTORY_JNDI_NAME = "svnReposFactoryJndiName"; 107 protected static final String SVN_URL = "svnURL"; 108 protected static final String SVN_USERNAME = "svnUsername"; 109 protected static final String SVN_PASSWORD = "svnPassword"; 110 protected static final String RETRY_LIMIT = "retryLimit"; 111 112 private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); 113 private String name; 114 private String svnURL; 115 private String svnUsername; 116 private String svnPassword; 117 private CachePolicy defaultCachePolicy; 118 119 private RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(SUPPORTS_SAME_NAME_SIBLINGS, 120 DEFAULT_SUPPORTS_UPDATES, 121 SUPPORTS_EVENTS, 122 SUPPORTS_CREATING_WORKSPACES, 123 SUPPORTS_REFERENCES); 124 125 private transient Context jndiContext; 126 private transient RepositoryContext repositoryContext; 127 private transient SVNRepository svnRepository; 128 129 /** 130 * Create a repository source instance. 131 */ 132 public SVNRepositorySource() { 133 } 134 135 /** 136 * {@inheritDoc} 137 * 138 * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities() 139 */ 140 public RepositorySourceCapabilities getCapabilities() { 141 return capabilities; 142 } 143 144 /** 145 * {@inheritDoc} 146 * 147 * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext) 148 */ 149 public void initialize( RepositoryContext context ) throws RepositorySourceException { 150 this.repositoryContext = context; 151 } 152 153 /** 154 * @return repositoryContext 155 */ 156 public RepositoryContext getRepositoryContext() { 157 return repositoryContext; 158 } 159 160 /** 161 * {@inheritDoc} 162 */ 163 public String getName() { 164 return this.name; 165 } 166 167 /** 168 * {@inheritDoc} 169 * 170 * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit() 171 */ 172 public int getRetryLimit() { 173 return retryLimit.get(); 174 } 175 176 /** 177 * {@inheritDoc} 178 * 179 * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int) 180 */ 181 public void setRetryLimit( int limit ) { 182 retryLimit.set(limit < 0 ? 0 : limit); 183 } 184 185 /** 186 * Set the name of this source 187 * 188 * @param name the name for this source 189 */ 190 public synchronized void setName( String name ) { 191 if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged 192 this.name = name; 193 } 194 195 /** 196 * Get the default cache policy for this source, or null if the global default cache policy should be used 197 * 198 * @return the default cache policy, or null if this source has no explicit default cache policy 199 */ 200 public CachePolicy getDefaultCachePolicy() { 201 return defaultCachePolicy; 202 } 203 204 /** 205 * @param defaultCachePolicy Sets defaultCachePolicy to the specified value. 206 */ 207 public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) { 208 if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null 209 && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged 210 this.defaultCachePolicy = defaultCachePolicy; 211 } 212 213 public String getSVNURL() { 214 return this.svnURL; 215 } 216 217 /** 218 * Set the url for the subversion repository. 219 * 220 * @param url - the url location. 221 * @throws IllegalArgumentException If svn url is null or empty 222 */ 223 public void setSVNURL( String url ) { 224 CheckArg.isNotEmpty(url, "SVNURL"); 225 this.svnURL = url; 226 } 227 228 public String getSVNUsername() { 229 return this.svnUsername; 230 } 231 232 /** 233 * @param username 234 */ 235 public void setSVNUsername( String username ) { 236 this.svnUsername = username; 237 } 238 239 public String getSVNPassword() { 240 return this.svnPassword; 241 } 242 243 /** 244 * @param password 245 */ 246 public void setSVNPassword( String password ) { 247 this.svnPassword = password; 248 } 249 250 /** 251 * Get whether this source supports updates. 252 * 253 * @return true if this source supports updates, or false if this source only supports reading content. 254 */ 255 public boolean getSupportsUpdates() { 256 return capabilities.supportsUpdates(); 257 } 258 259 /** 260 * Set whether this source supports updates. 261 * 262 * @param supportsUpdates true if this source supports updating content, or false if this source only supports reading 263 * content. 264 */ 265 public synchronized void setSupportsUpdates( boolean supportsUpdates ) { 266 capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), supportsUpdates, 267 capabilities.supportsEvents(), capabilities.supportsCreatingWorkspaces(), 268 capabilities.supportsReferences()); 269 } 270 271 /** 272 * {@inheritDoc} 273 * 274 * @see org.jboss.dna.graph.connector.RepositorySource#getConnection() 275 */ 276 public RepositoryConnection getConnection() throws RepositorySourceException { 277 if (getName() == null) { 278 I18n msg = SVNRepositoryConnectorI18n.propertyIsRequired; 279 throw new RepositorySourceException(getName(), msg.text("name")); 280 } 281 SVNURL svnURL = null; 282 if (this.svnRepository == null) { 283 try { 284 svnURL = SVNURL.parseURIDecoded(getSVNURL()); 285 String usedProtocol = this.getSVNURL().substring(0, this.getSVNURL().indexOf(":")); 286 if (usedProtocol.equals(SVNProtocol.SVN.value()) || usedProtocol.equals(SVNProtocol.SVN_SSH.value())) { 287 SVNRepositoryFactoryImpl.setup(); 288 this.svnRepository = SVNRepositoryFactory.create(svnURL); 289 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(this.getSVNUsername(), 290 this.getSVNPassword()); 291 this.svnRepository.setAuthenticationManager(authManager); 292 } else if (usedProtocol.equals(SVNProtocol.HTTP.value()) || usedProtocol.equals(SVNProtocol.HTTPS.value())) { 293 DAVRepositoryFactory.setup(); 294 this.svnRepository = DAVRepositoryFactory.create(svnURL); 295 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(this.getSVNUsername(), 296 this.getSVNPassword()); 297 this.svnRepository.setAuthenticationManager(authManager); 298 } else if (usedProtocol.equals(SVNProtocol.FILE.value())) { 299 FSRepositoryFactory.setup(); 300 this.svnRepository = FSRepositoryFactory.create(svnURL); 301 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(this.getSVNUsername(), 302 this.getSVNPassword()); 303 this.svnRepository.setAuthenticationManager(authManager); 304 } else { 305 // protocol not supported by this connector 306 throw new RepositorySourceException(getSVNURL(), 307 "Protocol is not supported by this connector or there is problem in the svn url"); 308 } 309 310 } catch (SVNException ex) { 311 I18n msg = SVNRepositoryConnectorI18n.propertyIsRequired; 312 throw new RepositorySourceException(getSVNURL(), msg.text(this.getSVNURL()), ex); 313 } 314 } 315 boolean supportsUpdates = getSupportsUpdates(); 316 return new SVNRepositoryConnection(this.getName(), this.getDefaultCachePolicy(), supportsUpdates, this.svnRepository); 317 } 318 319 protected Context getContext() { 320 return this.jndiContext; 321 } 322 323 protected synchronized void setContext( Context context ) { 324 this.jndiContext = context; 325 } 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override 331 public boolean equals( Object obj ) { 332 if (obj == this) return true; 333 if (obj instanceof SVNRepositorySource) { 334 SVNRepositorySource that = (SVNRepositorySource)obj; 335 if (this.getName() == null) { 336 if (that.getName() != null) return false; 337 } else { 338 if (!this.getName().equals(that.getName())) return false; 339 } 340 return true; 341 } 342 return false; 343 } 344 345 /** 346 * {@inheritDoc} 347 * 348 * @see javax.naming.Referenceable#getReference() 349 */ 350 public synchronized Reference getReference() { 351 String className = getClass().getName(); 352 String factoryClassName = this.getClass().getName(); 353 Reference ref = new Reference(className, factoryClassName, null); 354 355 if (getName() != null) { 356 ref.add(new StringRefAddr(SOURCE_NAME, getName())); 357 } 358 if (getSVNURL() != null) { 359 ref.add(new StringRefAddr(SVN_URL, getSVNURL())); 360 } 361 if (getSVNUsername() != null) { 362 ref.add(new StringRefAddr(SVN_USERNAME, getSVNUsername())); 363 } 364 if (getSVNPassword() != null) { 365 ref.add(new StringRefAddr(SVN_PASSWORD, getSVNPassword())); 366 } 367 if (getDefaultCachePolicy() != null) { 368 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 369 CachePolicy policy = getDefaultCachePolicy(); 370 try { 371 ObjectOutputStream oos = new ObjectOutputStream(baos); 372 oos.writeObject(policy); 373 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray())); 374 } catch (IOException e) { 375 I18n msg = SVNRepositoryConnectorI18n.errorSerializingCachePolicyInSource; 376 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e); 377 } 378 } 379 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit()))); 380 return ref; 381 } 382 383 /** 384 * {@inheritDoc} 385 * 386 * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, 387 * java.util.Hashtable) 388 */ 389 public Object getObjectInstance( Object obj, 390 Name name, 391 Context nameCtx, 392 Hashtable<?, ?> environment ) throws Exception { 393 if (obj instanceof Reference) { 394 Map<String, Object> values = new HashMap<String, Object>(); 395 Reference ref = (Reference)obj; 396 Enumeration<?> en = ref.getAll(); 397 while (en.hasMoreElements()) { 398 RefAddr subref = (RefAddr)en.nextElement(); 399 if (subref instanceof StringRefAddr) { 400 String key = subref.getType(); 401 Object value = subref.getContent(); 402 if (value != null) values.put(key, value.toString()); 403 } else if (subref instanceof BinaryRefAddr) { 404 String key = subref.getType(); 405 Object value = subref.getContent(); 406 if (value instanceof byte[]) { 407 // Deserialize ... 408 ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value); 409 ObjectInputStream ois = new ObjectInputStream(bais); 410 value = ois.readObject(); 411 values.put(key, value); 412 } 413 } 414 } 415 String sourceName = (String)values.get(SOURCE_NAME); 416 String svnURL = (String)values.get(SVN_URL); 417 String svnUsername = (String)values.get(SVN_USERNAME); 418 String svnPassword = (String)values.get(SVN_PASSWORD); 419 Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY); 420 String retryLimit = (String)values.get(RETRY_LIMIT); 421 422 // Create the source instance ... 423 SVNRepositorySource source = new SVNRepositorySource(); 424 if (sourceName != null) source.setName(sourceName); 425 if (svnURL != null) source.setSVNURL(svnURL); 426 if (svnUsername != null) source.setSVNUsername(svnUsername); 427 if (svnPassword != null) source.setSVNPassword(svnPassword); 428 if (defaultCachePolicy instanceof CachePolicy) { 429 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy); 430 } 431 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); 432 return source; 433 } 434 return null; 435 } 436 437 }