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    }