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    }