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 }