001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.dna.connector.svn;
023
024 import java.io.ByteArrayInputStream;
025 import java.io.ByteArrayOutputStream;
026 import java.io.IOException;
027 import java.io.ObjectInputStream;
028 import java.io.ObjectOutputStream;
029 import java.util.Enumeration;
030 import java.util.HashMap;
031 import java.util.Hashtable;
032 import java.util.Map;
033 import java.util.UUID;
034 import java.util.concurrent.atomic.AtomicInteger;
035 import javax.naming.BinaryRefAddr;
036 import javax.naming.Context;
037 import javax.naming.Name;
038 import javax.naming.RefAddr;
039 import javax.naming.Reference;
040 import javax.naming.Referenceable;
041 import javax.naming.StringRefAddr;
042 import javax.naming.spi.ObjectFactory;
043 import org.jboss.dna.common.i18n.I18n;
044 import org.jboss.dna.common.util.CheckArg;
045 import org.jboss.dna.graph.DnaLexicon;
046 import org.jboss.dna.graph.cache.CachePolicy;
047 import org.jboss.dna.graph.connectors.RepositoryConnection;
048 import org.jboss.dna.graph.connectors.RepositoryContext;
049 import org.jboss.dna.graph.connectors.RepositorySource;
050 import org.jboss.dna.graph.connectors.RepositorySourceCapabilities;
051 import org.jboss.dna.graph.connectors.RepositorySourceException;
052 import org.jboss.dna.graph.properties.Property;
053 import org.tmatesoft.svn.core.SVNException;
054 import org.tmatesoft.svn.core.SVNURL;
055 import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
056 import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
057 import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
058 import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
059 import org.tmatesoft.svn.core.io.SVNRepository;
060 import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
061 import org.tmatesoft.svn.core.wc.SVNWCUtil;
062
063 /**
064 * A repository source that uses a SVN repository instance to manage the content. This source is capable of using an existing
065 * {@link SVNRepository} instance or creating a new instance. This process is controlled entirely by the JavaBean properties of
066 * the SVNRepositorySource instance. Like other {@link RepositorySource} classes, instances of SVNRepositorySource can be placed
067 * into JNDI and do support the creation of {@link Referenceable JNDI referenceable} objects and resolution of references into
068 * SVNRepositorySource. </p>
069 *
070 * @author Serge Pagop
071 */
072 public class SVNRepositorySource implements RepositorySource, ObjectFactory {
073
074 private static final long serialVersionUID = 1L;
075 /**
076 * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
077 */
078 public static final int DEFAULT_RETRY_LIMIT = 0;
079
080 protected static final RepositorySourceCapabilities CAPABILITIES = new RepositorySourceCapabilities(false, true);
081
082 public static final String DEFAULT_UUID_PROPERTY_NAME = DnaLexicon.UUID.getString();
083
084 protected static final String SOURCE_NAME = "sourceName";
085 protected static final String ROOT_NODE_UUID = "rootNodeUuid";
086 protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy";
087 protected static final String UUID_PROPERTY_NAME = "uuidPropertyName";
088 protected static final String SVN_REPOS_JNDI_NAME = "svnReposJndiName";
089 protected static final String SVN_REPOS_FACTORY_JNDI_NAME = "svnReposFactoryJndiName";
090 protected static final String SVN_URL = "svnURL";
091 protected static final String SVN_USERNAME = "svnUsername";
092 protected static final String SVN_PASSWORD = "svnPassword";
093 protected static final String RETRY_LIMIT = "retryLimit";
094
095 private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT);
096 private String name;
097 private UUID rootNodeUuid = UUID.randomUUID();
098 private String uuidPropertyName = DEFAULT_UUID_PROPERTY_NAME;
099 private String svnURL;
100 private String svnUsername;
101 private String svnPassword;
102 private CachePolicy defaultCachePolicy;
103
104 private transient Context jndiContext;
105 private transient RepositoryContext repositoryContext;
106 private transient SVNRepository svnRepository;
107
108 /**
109 * Create a repository source instance.
110 */
111 public SVNRepositorySource() {
112 }
113
114 /**
115 * {@inheritDoc}
116 *
117 * @see org.jboss.dna.graph.connectors.RepositorySource#initialize(org.jboss.dna.graph.connectors.RepositoryContext)
118 */
119 public void initialize( RepositoryContext context ) throws RepositorySourceException {
120 this.repositoryContext = context;
121 }
122
123 /**
124 * @return repositoryContext
125 */
126 public RepositoryContext getRepositoryContext() {
127 return repositoryContext;
128 }
129
130 /**
131 * {@inheritDoc}
132 */
133 public String getName() {
134 return this.name;
135 }
136
137 /**
138 * {@inheritDoc}
139 *
140 * @see org.jboss.dna.graph.connectors.RepositorySource#getRetryLimit()
141 */
142 public int getRetryLimit() {
143 return retryLimit.get();
144 }
145
146 /**
147 * {@inheritDoc}
148 *
149 * @see org.jboss.dna.graph.connectors.RepositorySource#setRetryLimit(int)
150 */
151 public void setRetryLimit( int limit ) {
152 retryLimit.set(limit < 0 ? 0 : limit);
153 }
154
155 /**
156 * Set the name of this source
157 *
158 * @param name the name for this source
159 */
160 public synchronized void setName( String name ) {
161 if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged
162 this.name = name;
163 }
164
165 /**
166 * Get the default cache policy for this source, or null if the global default cache policy should be used
167 *
168 * @return the default cache policy, or null if this source has no explicit default cache policy
169 */
170 public CachePolicy getDefaultCachePolicy() {
171 return defaultCachePolicy;
172 }
173
174 /**
175 * @param defaultCachePolicy Sets defaultCachePolicy to the specified value.
176 */
177 public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) {
178 if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null
179 && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged
180 this.defaultCachePolicy = defaultCachePolicy;
181 }
182
183 /**
184 * Get the UUID of the root node for the cache. If the cache exists, this UUID is not used but is instead set to the UUID of
185 * the existing root node.
186 *
187 * @return the UUID of the root node for the cache.
188 */
189 public String getRootNodeUuid() {
190 return this.rootNodeUuid.toString();
191 }
192
193 /**
194 * Get the UUID of the root node for the cache. If the cache exists, this UUID is not used but is instead set to the UUID of
195 * the existing root node.
196 *
197 * @return the UUID of the root node for the cache.
198 */
199 public UUID getRootNodeUuidObject() {
200 return this.rootNodeUuid;
201 }
202
203 /**
204 * Set the UUID of the root node in this repository. If the cache exists, this UUID is not used but is instead set to the UUID
205 * of the existing root node.
206 *
207 * @param rootNodeUuid the UUID of the root node for the cache, or null if the UUID should be randomly generated
208 */
209 public synchronized void setRootNodeUuid( String rootNodeUuid ) {
210 UUID uuid = null;
211 if (rootNodeUuid == null) uuid = UUID.randomUUID();
212 else uuid = UUID.fromString(rootNodeUuid);
213 if (this.rootNodeUuid.equals(uuid)) return; // unchanged
214 this.rootNodeUuid = uuid;
215 }
216
217 /**
218 * Get the {@link Property#getName() property name} where the UUID is stored for each node.
219 *
220 * @return the name of the UUID property; never null
221 */
222 public String getUuidPropertyName() {
223 return this.uuidPropertyName;
224 }
225
226 /**
227 * Set the {@link Property#getName() property name} where the UUID is stored for each node.
228 *
229 * @param uuidPropertyName the name of the UUID property, or null if the {@link #DEFAULT_UUID_PROPERTY_NAME default name}
230 * should be used
231 */
232 public synchronized void setUuidPropertyName( String uuidPropertyName ) {
233 if (uuidPropertyName == null || uuidPropertyName.trim().length() == 0) uuidPropertyName = DEFAULT_UUID_PROPERTY_NAME;
234 if (this.uuidPropertyName.equals(uuidPropertyName)) return; // unchanged
235 this.uuidPropertyName = uuidPropertyName;
236 }
237
238 public String getSVNURL() {
239 return this.svnURL;
240 }
241
242 /**
243 * Set the url for the subversion repository.
244 *
245 * @param url - the url location.
246 * @throws IllegalArgumentException If svn url is null or empty
247 */
248 public void setSVNURL( String url ) {
249 CheckArg.isNotEmpty(url, "SVNURL");
250 this.svnURL = url;
251 }
252
253 public String getSVNUsername() {
254 return this.svnUsername;
255 }
256
257 /**
258 * @param username
259 */
260 public void setSVNUsername( String username ) {
261 this.svnUsername = username;
262 }
263
264 public String getSVNPassword() {
265 return this.svnPassword;
266 }
267
268 /**
269 * @param password
270 */
271 public void setSVNPassword( String password ) {
272 this.svnPassword = password;
273 }
274
275 /**
276 * {@inheritDoc}
277 *
278 * @see org.jboss.dna.graph.connectors.RepositorySource#getCapabilities()
279 */
280 public RepositorySourceCapabilities getCapabilities() {
281 return CAPABILITIES;
282 }
283
284 /**
285 * {@inheritDoc}
286 *
287 * @see org.jboss.dna.graph.connectors.RepositorySource#getConnection()
288 */
289 public RepositoryConnection getConnection() throws RepositorySourceException {
290 if (getName() == null) {
291 I18n msg = SVNRepositoryConnectorI18n.propertyIsRequired;
292 throw new RepositorySourceException(getName(), msg.text("name"));
293 }
294 if (getUuidPropertyName() == null) {
295 I18n msg = SVNRepositoryConnectorI18n.propertyIsRequired;
296 throw new RepositorySourceException(getUuidPropertyName(), msg.text("uuidPropertyName"));
297 }
298 SVNURL svnURL = null;
299 if (this.svnRepository == null) {
300 try {
301 svnURL = SVNURL.parseURIDecoded(getSVNURL());
302 String usedProtocol = this.getSVNURL().substring(0, this.getSVNURL().lastIndexOf(":"));
303 if (usedProtocol.equals(SVNProtocol.SVN.value()) || usedProtocol.equals(SVNProtocol.SVN_SSH.value())) {
304 SVNRepositoryFactoryImpl.setup();
305 this.svnRepository = SVNRepositoryFactory.create(svnURL);
306 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(this.getSVNUsername(),
307 this.getSVNPassword());
308 this.svnRepository.setAuthenticationManager(authManager);
309 }
310 if (usedProtocol.equals(SVNProtocol.HTTP.value()) || usedProtocol.equals(SVNProtocol.HTTPS.value())) {
311 DAVRepositoryFactory.setup();
312 this.svnRepository = DAVRepositoryFactory.create(svnURL);
313 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(this.getSVNUsername(),
314 this.getSVNPassword());
315 this.svnRepository.setAuthenticationManager(authManager);
316 }
317 if (usedProtocol.equals(SVNProtocol.FILE.value())) {
318 FSRepositoryFactory.setup();
319 this.svnRepository = FSRepositoryFactory.create(svnURL);
320 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(this.getSVNUsername(),
321 this.getSVNPassword());
322 this.svnRepository.setAuthenticationManager(authManager);
323 }
324
325 } catch (SVNException ex) {
326 I18n msg = SVNRepositoryConnectorI18n.propertyIsRequired;
327 throw new RepositorySourceException(getSVNURL(), msg.text(this.getSVNURL()), ex);
328 }
329 }
330 return new SVNRepositoryConnection(this.getName(), this.getDefaultCachePolicy(), this.getUuidPropertyName(),
331 this.svnRepository);
332 }
333
334 protected Context getContext() {
335 return this.jndiContext;
336 }
337
338 protected synchronized void setContext( Context context ) {
339 this.jndiContext = context;
340 }
341
342 /**
343 * {@inheritDoc}
344 */
345 @Override
346 public boolean equals( Object obj ) {
347 if (obj == this) return true;
348 if (obj instanceof SVNRepositorySource) {
349 SVNRepositorySource that = (SVNRepositorySource)obj;
350 if (this.getName() == null) {
351 if (that.getName() != null) return false;
352 } else {
353 if (!this.getName().equals(that.getName())) return false;
354 }
355 return true;
356 }
357 return false;
358 }
359
360 /**
361 * {@inheritDoc}
362 *
363 * @see javax.naming.Referenceable#getReference()
364 */
365 public synchronized Reference getReference() {
366 String className = getClass().getName();
367 String factoryClassName = this.getClass().getName();
368 Reference ref = new Reference(className, factoryClassName, null);
369
370 if (getName() != null) {
371 ref.add(new StringRefAddr(SOURCE_NAME, getName()));
372 }
373 if (getRootNodeUuid() != null) {
374 ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString()));
375 }
376 if (getUuidPropertyName() != null) {
377 ref.add(new StringRefAddr(UUID_PROPERTY_NAME, getUuidPropertyName()));
378 }
379 if (getSVNURL() != null) {
380 ref.add(new StringRefAddr(SVN_URL, getSVNURL()));
381 }
382 if (getSVNUsername() != null) {
383 ref.add(new StringRefAddr(SVN_USERNAME, getSVNUsername()));
384 }
385 if (getSVNPassword() != null) {
386 ref.add(new StringRefAddr(SVN_PASSWORD, getSVNPassword()));
387 }
388 if (getDefaultCachePolicy() != null) {
389 ByteArrayOutputStream baos = new ByteArrayOutputStream();
390 CachePolicy policy = getDefaultCachePolicy();
391 try {
392 ObjectOutputStream oos = new ObjectOutputStream(baos);
393 oos.writeObject(policy);
394 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray()));
395 } catch (IOException e) {
396 I18n msg = SVNRepositoryConnectorI18n.errorSerializingCachePolicyInSource;
397 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e);
398 }
399 }
400 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
401 return ref;
402 }
403
404 /**
405 * {@inheritDoc}
406 *
407 * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context,
408 * java.util.Hashtable)
409 */
410 public Object getObjectInstance( Object obj,
411 Name name,
412 Context nameCtx,
413 Hashtable<?, ?> environment ) throws Exception {
414 if (obj instanceof Reference) {
415 Map<String, Object> values = new HashMap<String, Object>();
416 Reference ref = (Reference)obj;
417 Enumeration<?> en = ref.getAll();
418 while (en.hasMoreElements()) {
419 RefAddr subref = (RefAddr)en.nextElement();
420 if (subref instanceof StringRefAddr) {
421 String key = subref.getType();
422 Object value = subref.getContent();
423 if (value != null) values.put(key, value.toString());
424 } else if (subref instanceof BinaryRefAddr) {
425 String key = subref.getType();
426 Object value = subref.getContent();
427 if (value instanceof byte[]) {
428 // Deserialize ...
429 ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value);
430 ObjectInputStream ois = new ObjectInputStream(bais);
431 value = ois.readObject();
432 values.put(key, value);
433 }
434 }
435 }
436 String sourceName = (String)values.get(SOURCE_NAME);
437 String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID);
438 String uuidPropertyName = (String)values.get(UUID_PROPERTY_NAME);
439 String svnURL = (String)values.get(SVN_URL);
440 String svnUsername = (String)values.get(SVN_USERNAME);
441 String svnPassword = (String)values.get(SVN_PASSWORD);
442 Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY);
443 String retryLimit = (String)values.get(RETRY_LIMIT);
444
445 // Create the source instance ...
446 SVNRepositorySource source = new SVNRepositorySource();
447 if (sourceName != null) source.setName(sourceName);
448 if (rootNodeUuidString != null) source.setRootNodeUuid(rootNodeUuidString);
449 if (uuidPropertyName != null) source.setUuidPropertyName(uuidPropertyName);
450 if (svnURL != null) source.setSVNURL(svnURL);
451 if (svnUsername != null) source.setSVNUsername(svnUsername);
452 if (svnPassword != null) source.setSVNPassword(svnPassword);
453 if (defaultCachePolicy instanceof CachePolicy) {
454 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy);
455 }
456 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
457 return source;
458 }
459 return null;
460 }
461
462 }