View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.connector.svn;
25  
26  import java.util.Hashtable;
27  import java.util.List;
28  import java.util.Map;
29  import javax.naming.Context;
30  import javax.naming.Name;
31  import javax.naming.Reference;
32  import javax.naming.StringRefAddr;
33  import javax.naming.spi.ObjectFactory;
34  import net.jcip.annotations.ThreadSafe;
35  import org.modeshape.common.annotation.Category;
36  import org.modeshape.common.annotation.Description;
37  import org.modeshape.common.annotation.Label;
38  import org.modeshape.common.annotation.ReadOnly;
39  import org.modeshape.common.i18n.I18n;
40  import org.modeshape.common.util.CheckArg;
41  import org.modeshape.common.util.StringUtil;
42  import org.modeshape.connector.svn.SvnRepository.SvnTransaction;
43  import org.modeshape.graph.ExecutionContext;
44  import org.modeshape.graph.connector.RepositoryConnection;
45  import org.modeshape.graph.connector.RepositorySource;
46  import org.modeshape.graph.connector.RepositorySourceCapabilities;
47  import org.modeshape.graph.connector.RepositorySourceException;
48  import org.modeshape.graph.connector.base.AbstractRepositorySource;
49  import org.modeshape.graph.connector.base.Connection;
50  import org.modeshape.graph.connector.base.PathNode;
51  import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior;
52  
53  /**
54   * The {@link RepositorySource} for the connector that exposes an area of the local/remote svn repository as content in a
55   * repository. This source considers a workspace name to be the path to the directory on the repository's root directory location
56   * that represents the root of that workspace. New workspaces can be created, as long as the names represent valid paths to
57   * existing directories.
58   */
59  @ThreadSafe
60  public class SvnRepositorySource extends AbstractRepositorySource implements ObjectFactory {
61  
62      /**
63       * The first serialized version of this source. Version {@value} .
64       */
65      private static final long serialVersionUID = 1L;
66  
67      protected static final String SOURCE_NAME = "sourceName";
68      protected static final String SVN_REPOSITORY_ROOT_URL = "repositoryRootURL";
69      protected static final String SVN_USERNAME = "username";
70      protected static final String SVN_PASSWORD = "password";
71      protected static final String RETRY_LIMIT = "retryLimit";
72      protected static final String ROOT_NODE_UUID = "rootNodeUuid";
73      protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
74      protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
75      protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
76  
77      /**
78       * This source supports events.
79       */
80      protected static final boolean SUPPORTS_EVENTS = true;
81      /**
82       * This source supports same-name-siblings.
83       */
84      protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = false;
85      /**
86       * This source does support creating workspaces.
87       */
88      protected static final boolean DEFAULT_SUPPORTS_CREATING_WORKSPACES = true;
89      /**
90       * This source supports udpates by default, but each instance may be configured to be read-only or updateable}.
91       */
92      public static final boolean DEFAULT_SUPPORTS_UPDATES = false;
93      /**
94       * The default name of the {@link #defaultWorkspace} property.
95       */
96      public static final String DEFAULT_WORKSPACE_NAME = "trunk";
97  
98      /**
99       * This source supports creating references.
100      */
101     protected static final boolean SUPPORTS_REFERENCES = false;
102 
103     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "repositoryRootUrlPropertyDescription" )
104     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "repositoryRootUrlPropertyLabel" )
105     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "repositoryRootUrlPropertyCategory" )
106     private volatile String repositoryRootUrl;
107 
108     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "usernamePropertyDescription" )
109     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "usernamePropertyLabel" )
110     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "usernamePropertyCategory" )
111     private volatile String username;
112 
113     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "passwordPropertyDescription" )
114     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "passwordPropertyLabel" )
115     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "passwordPropertyCategory" )
116     private volatile String password;
117 
118     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "defaultWorkspaceNamePropertyDescription" )
119     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "defaultWorkspaceNamePropertyLabel" )
120     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "defaultWorkspaceNamePropertyCategory" )
121     private volatile String defaultWorkspace = DEFAULT_WORKSPACE_NAME;
122 
123     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "predefinedWorkspacesPropertyDescription" )
124     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "predefinedWorkspacesPropertyLabel" )
125     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "predefinedWorkspacesPropertyCategory" )
126     private volatile String[] predefinedWorkspaces = new String[] {};
127 
128     private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(
129                                                                                                   SUPPORTS_SAME_NAME_SIBLINGS,
130                                                                                                   DEFAULT_SUPPORTS_UPDATES,
131                                                                                                   SUPPORTS_EVENTS,
132                                                                                                   DEFAULT_SUPPORTS_CREATING_WORKSPACES,
133                                                                                                   SUPPORTS_REFERENCES);
134 
135     private transient SvnRepository repository;
136 
137     private ExecutionContext defaultContext = new ExecutionContext();
138 
139     /**
140      * Create a repository source instance.
141      */
142     public SvnRepositorySource() {
143     }
144 
145     /**
146      * {@inheritDoc}
147      * 
148      * @see org.modeshape.graph.connector.RepositorySource#getCapabilities()
149      */
150     public RepositorySourceCapabilities getCapabilities() {
151         return capabilities;
152     }
153 
154     /**
155      * @return the url
156      */
157     public String getRepositoryRootUrl() {
158         return this.repositoryRootUrl;
159     }
160 
161     /**
162      * Set the url for the subversion repository.
163      * 
164      * @param url - the url location.
165      * @throws IllegalArgumentException If svn url is null or empty
166      */
167     public synchronized void setRepositoryRootUrl( String url ) {
168         CheckArg.isNotEmpty(url, "RepositoryRootUrl");
169         this.repositoryRootUrl = url;
170     }
171 
172     public String getUsername() {
173         return this.username;
174     }
175 
176     /**
177      * @param username
178      */
179     public synchronized void setUsername( String username ) {
180         this.username = username;
181     }
182 
183     public String getPassword() {
184         return this.password;
185     }
186 
187     /**
188      * @param password
189      */
190     public synchronized void setPassword( String password ) {
191         this.password = password;
192     }
193 
194     /**
195      * Get whether this source supports updates.
196      * 
197      * @return true if this source supports updates, or false if this source only supports reading content.
198      */
199     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "updatesAllowedPropertyDescription" )
200     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "updatesAllowedPropertyLabel" )
201     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "updatesAllowedPropertyCategory" )
202     @ReadOnly
203     public boolean getSupportsUpdates() {
204         return capabilities.supportsUpdates();
205     }
206 
207     /**
208      * Get the path to the existing directory that should be used for the default workspace. This path should be relative to the
209      * {@link #getRepositoryRootUrl() repository root URL}. If the default is specified as a null String or is not a valid and
210      * resolvable path, this source will consider the default to be the current working directory of this virtual machine, as
211      * defined by the <code>new File(".")</code>.
212      * 
213      * @return the file system path to the directory representing the default workspace, or null if the default should be the
214      *         current working directory
215      */
216     public String getDefaultWorkspaceName() {
217         return defaultWorkspace;
218     }
219 
220     /**
221      * Set the file system path to the existing directory that should be used for the default workspace. This path should be
222      * relative to the {@link #getRepositoryRootUrl() repository root URL}. If the default is specified as a null String or is not
223      * a valid and resolvable path, this source will consider the default to be the current working directory of this virtual
224      * machine, as defined by the <code>new File(".")</code>.
225      * 
226      * @param pathToDirectoryForDefaultWorkspace the valid and resolvable file system path to the directory representing the
227      *        default workspace, or null if the current working directory should be used as the default workspace
228      */
229     public synchronized void setDefaultWorkspaceName( String pathToDirectoryForDefaultWorkspace ) {
230         this.defaultWorkspace = pathToDirectoryForDefaultWorkspace;
231     }
232 
233     /**
234      * Gets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path
235      * to a directory on the file system.
236      * 
237      * @return the names of the workspaces that this source starts with, or null if there are no such workspaces
238      * @see #setPredefinedWorkspaceNames(String[])
239      * @see #setCreatingWorkspacesAllowed(boolean)
240      */
241     public synchronized String[] getPredefinedWorkspaceNames() {
242         String[] copy = new String[predefinedWorkspaces.length];
243         System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length);
244         return copy;
245     }
246 
247     /**
248      * Sets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path
249      * to a directory on the file system.
250      * 
251      * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no
252      *        such workspaces
253      * @see #setCreatingWorkspacesAllowed(boolean)
254      * @see #getPredefinedWorkspaceNames()
255      */
256     public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) {
257         this.predefinedWorkspaces = predefinedWorkspaceNames;
258     }
259 
260     /**
261      * Get whether this source allows workspaces to be created dynamically.
262      * 
263      * @return true if this source allows workspaces to be created by clients, or false if the set of workspaces is fixed
264      * @see #setPredefinedWorkspaceNames(String[])
265      * @see #getPredefinedWorkspaceNames()
266      * @see #setCreatingWorkspacesAllowed(boolean)
267      */
268     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyDescription" )
269     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyLabel" )
270     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyCategory" )
271     public boolean isCreatingWorkspacesAllowed() {
272         return capabilities.supportsCreatingWorkspaces();
273     }
274 
275     /**
276      * Set whether this source allows workspaces to be created dynamically.
277      * 
278      * @param allowWorkspaceCreation true if this source allows workspaces to be created by clients, or false if the set of
279      *        workspaces is fixed
280      * @see #setPredefinedWorkspaceNames(String[])
281      * @see #getPredefinedWorkspaceNames()
282      * @see #isCreatingWorkspacesAllowed()
283      */
284     public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) {
285         capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), capabilities.supportsUpdates(),
286                                                         capabilities.supportsEvents(), allowWorkspaceCreation,
287                                                         capabilities.supportsReferences());
288     }
289 
290     /**
291      * Get whether this source allows updates.
292      * 
293      * @return true if this source allows updates by clients, or false if no updates are allowed
294      * @see #setUpdatesAllowed(boolean)
295      */
296     @Description( i18n = SvnRepositoryConnectorI18n.class, value = "updatesAllowedPropertyDescription" )
297     @Label( i18n = SvnRepositoryConnectorI18n.class, value = "updatesAllowedPropertyLabel" )
298     @Category( i18n = SvnRepositoryConnectorI18n.class, value = "updatesAllowedPropertyCategory" )
299     @Override
300     public boolean areUpdatesAllowed() {
301         return capabilities.supportsUpdates();
302     }
303 
304     /**
305      * Set whether this source allows updates to data within workspaces
306      * 
307      * @param allowUpdates true if this source allows updates to data within workspaces clients, or false if updates are not
308      *        allowed.
309      * @see #areUpdatesAllowed()
310      */
311     public synchronized void setUpdatesAllowed( boolean allowUpdates ) {
312         capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), allowUpdates,
313                                                         capabilities.supportsEvents(), capabilities.supportsCreatingWorkspaces(),
314                                                         capabilities.supportsReferences());
315     }
316 
317     /**
318      * {@inheritDoc}
319      */
320     @Override
321     public boolean equals( Object obj ) {
322         if (obj == this) return true;
323         if (obj instanceof SvnRepositorySource) {
324             SvnRepositorySource that = (SvnRepositorySource)obj;
325             if (this.getName() == null) {
326                 if (that.getName() != null) return false;
327             } else {
328                 if (!this.getName().equals(that.getName())) return false;
329             }
330             return true;
331         }
332         return false;
333     }
334 
335     @Override
336     public int hashCode() {
337         return getName().hashCode();
338     }
339 
340     /**
341      * {@inheritDoc}
342      * 
343      * @see javax.naming.Referenceable#getReference()
344      */
345     public synchronized Reference getReference() {
346         String className = getClass().getName();
347         String factoryClassName = this.getClass().getName();
348         Reference ref = new Reference(className, factoryClassName, null);
349 
350         if (getName() != null) {
351             ref.add(new StringRefAddr(SOURCE_NAME, getName()));
352         }
353         if (getRepositoryRootUrl() != null) {
354             ref.add(new StringRefAddr(SVN_REPOSITORY_ROOT_URL, getRepositoryRootUrl()));
355         }
356         if (getUsername() != null) {
357             ref.add(new StringRefAddr(SVN_USERNAME, getUsername()));
358         }
359         if (getPassword() != null) {
360             ref.add(new StringRefAddr(SVN_PASSWORD, getPassword()));
361         }
362         ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
363         ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuidObject().toString()));
364         ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName()));
365         ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
366         String[] workspaceNames = getPredefinedWorkspaceNames();
367         if (workspaceNames != null && workspaceNames.length != 0) {
368             ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames)));
369         }
370         return ref;
371 
372     }
373 
374     /**
375      * {@inheritDoc}
376      * 
377      * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context,
378      *      java.util.Hashtable)
379      */
380     public Object getObjectInstance( Object obj,
381                                      Name name,
382                                      Context nameCtx,
383                                      Hashtable<?, ?> environment ) throws Exception {
384         if (!(obj instanceof Reference)) return null;
385 
386         Map<String, Object> values = valuesFrom((Reference)obj);
387 
388         String sourceName = (String)values.get(SOURCE_NAME);
389         String repositoryRootUrl = (String)values.get(SVN_REPOSITORY_ROOT_URL);
390         String username = (String)values.get(SVN_USERNAME);
391         String password = (String)values.get(SVN_PASSWORD);
392         String retryLimit = (String)values.get(RETRY_LIMIT);
393         String rootNodeUuid = (String)values.get(ROOT_NODE_UUID);
394         String defaultWorkspace = (String)values.get(DEFAULT_WORKSPACE);
395         String createWorkspaces = (String)values.get(ALLOW_CREATING_WORKSPACES);
396 
397         String combinedWorkspaceNames = (String)values.get(PREDEFINED_WORKSPACE_NAMES);
398         String[] workspaceNames = null;
399         if (combinedWorkspaceNames != null) {
400             List<String> paths = StringUtil.splitLines(combinedWorkspaceNames);
401             workspaceNames = paths.toArray(new String[paths.size()]);
402         }
403         // Create the source instance ...
404         SvnRepositorySource source = new SvnRepositorySource();
405         if (sourceName != null) source.setName(sourceName);
406         if (repositoryRootUrl != null && repositoryRootUrl.length() > 0) source.setRepositoryRootUrl(repositoryRootUrl);
407         if (username != null) source.setUsername(username);
408         if (password != null) source.setPassword(password);
409         if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
410         if (rootNodeUuid != null) source.setRootNodeUuidObject(rootNodeUuid);
411         if (defaultWorkspace != null) source.setDefaultWorkspaceName(defaultWorkspace);
412         if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
413         if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
414         return source;
415     }
416 
417     /**
418      * {@inheritDoc}
419      * 
420      * @see org.modeshape.graph.connector.RepositorySource#getConnection()
421      */
422     public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
423 
424         String sourceName = getName();
425         if (sourceName == null || sourceName.trim().length() == 0) {
426             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
427             throw new RepositorySourceException(getName(), msg.text("name"));
428         }
429 
430         String sourceUsername = getUsername();
431         if (sourceUsername == null || sourceUsername.trim().length() == 0) {
432             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
433             throw new RepositorySourceException(getUsername(), msg.text("username"));
434         }
435 
436         String sourcePassword = getPassword();
437         if (sourcePassword == null) {
438             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
439             throw new RepositorySourceException(getPassword(), msg.text("password"));
440         }
441 
442         String repositoryRootURL = getRepositoryRootUrl();
443         if (repositoryRootURL == null || repositoryRootURL.trim().length() == 0) {
444             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
445             throw new RepositorySourceException(getRepositoryRootUrl(), msg.text("repositoryRootURL"));
446         }
447         repositoryRootURL = repositoryRootURL.trim();
448         if (repositoryRootURL.endsWith("/")) repositoryRootURL = repositoryRootURL + "/";
449 
450         if (repository == null) {
451             repository = new SvnRepository(this);
452 
453             ExecutionContext context = repositoryContext != null ? repositoryContext.getExecutionContext() : defaultContext;
454             SvnTransaction txn = repository.startTransaction(context, false);
455             try {
456                 // Create the set of initial workspaces ...
457                 for (String initialName : getPredefinedWorkspaceNames()) {
458                     repository.createWorkspace(txn, initialName, CreateConflictBehavior.DO_NOT_CREATE, null);
459                 }
460             } finally {
461                 txn.commit();
462             }
463 
464         }
465         return new Connection<PathNode, SvnWorkspace>(this, repository);
466     }
467 }