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.i18n.I18n;
36  import org.modeshape.common.util.CheckArg;
37  import org.modeshape.common.util.StringUtil;
38  import org.modeshape.connector.svn.SvnRepository.SvnTransaction;
39  import org.modeshape.graph.ExecutionContext;
40  import org.modeshape.graph.connector.RepositoryConnection;
41  import org.modeshape.graph.connector.RepositorySource;
42  import org.modeshape.graph.connector.RepositorySourceCapabilities;
43  import org.modeshape.graph.connector.RepositorySourceException;
44  import org.modeshape.graph.connector.base.AbstractRepositorySource;
45  import org.modeshape.graph.connector.base.Connection;
46  import org.modeshape.graph.connector.base.PathNode;
47  import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior;
48  
49  /**
50   * The {@link RepositorySource} for the connector that exposes an area of the local/remote svn repository as content in a
51   * repository. This source considers a workspace name to be the path to the directory on the repository's root directory location
52   * that represents the root of that workspace. New workspaces can be created, as long as the names represent valid paths to
53   * existing directories.
54   */
55  @ThreadSafe
56  public class SvnRepositorySource extends AbstractRepositorySource implements ObjectFactory {
57  
58      /**
59       * The first serialized version of this source. Version {@value} .
60       */
61      private static final long serialVersionUID = 1L;
62  
63      protected static final String SOURCE_NAME = "sourceName";
64      protected static final String SVN_REPOSITORY_ROOT_URL = "repositoryRootURL";
65      protected static final String SVN_USERNAME = "username";
66      protected static final String SVN_PASSWORD = "password";
67      protected static final String RETRY_LIMIT = "retryLimit";
68      protected static final String ROOT_NODE_UUID = "rootNodeUuid";
69      protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
70      protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
71      protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
72  
73      /**
74       * This source supports events.
75       */
76      protected static final boolean SUPPORTS_EVENTS = true;
77      /**
78       * This source supports same-name-siblings.
79       */
80      protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = false;
81      /**
82       * This source does support creating workspaces.
83       */
84      protected static final boolean DEFAULT_SUPPORTS_CREATING_WORKSPACES = true;
85      /**
86       * This source supports udpates by default, but each instance may be configured to be read-only or updateable}.
87       */
88      public static final boolean DEFAULT_SUPPORTS_UPDATES = false;
89  
90      /**
91       * This source supports creating references.
92       */
93      protected static final boolean SUPPORTS_REFERENCES = false;
94  
95      private volatile String repositoryRootUrl;
96      private volatile String username;
97      private volatile String password;
98      private volatile String defaultWorkspace;
99      private volatile String[] predefinedWorkspaces = new String[] {};
100     private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(
101                                                                                                   SUPPORTS_SAME_NAME_SIBLINGS,
102                                                                                                   DEFAULT_SUPPORTS_UPDATES,
103                                                                                                   SUPPORTS_EVENTS,
104                                                                                                   DEFAULT_SUPPORTS_CREATING_WORKSPACES,
105                                                                                                   SUPPORTS_REFERENCES);
106 
107     private transient SvnRepository repository;
108 
109     private ExecutionContext defaultContext = new ExecutionContext();
110 
111     /**
112      * Create a repository source instance.
113      */
114     public SvnRepositorySource() {
115     }
116 
117     /**
118      * {@inheritDoc}
119      * 
120      * @see org.modeshape.graph.connector.RepositorySource#getCapabilities()
121      */
122     public RepositorySourceCapabilities getCapabilities() {
123         return capabilities;
124     }
125 
126     /**
127      * @return the url
128      */
129     public String getRepositoryRootUrl() {
130         return this.repositoryRootUrl;
131     }
132 
133     /**
134      * Set the url for the subversion repository.
135      * 
136      * @param url - the url location.
137      * @throws IllegalArgumentException If svn url is null or empty
138      */
139     public synchronized void setRepositoryRootUrl( String url ) {
140         CheckArg.isNotEmpty(url, "RepositoryRootUrl");
141         this.repositoryRootUrl = url;
142     }
143 
144     public String getUsername() {
145         return this.username;
146     }
147 
148     /**
149      * @param username
150      */
151     public synchronized void setUsername( String username ) {
152         this.username = username;
153     }
154 
155     public String getPassword() {
156         return this.password;
157     }
158 
159     /**
160      * @param password
161      */
162     public synchronized void setPassword( String password ) {
163         this.password = password;
164     }
165 
166     /**
167      * Get whether this source supports updates.
168      * 
169      * @return true if this source supports updates, or false if this source only supports reading content.
170      */
171     public boolean getSupportsUpdates() {
172         return capabilities.supportsUpdates();
173     }
174 
175     /**
176      * Get the path to the existing directory that should be used for the default workspace. This path should be relative to the
177      * {@link #getRepositoryRootUrl() repository root URL}. If the default is specified as a null String or is not a valid and
178      * resolvable path, this source will consider the default to be the current working directory of this virtual machine, as
179      * defined by the <code>new File(".")</code>.
180      * 
181      * @return the file system path to the directory representing the default workspace, or null if the default should be the
182      *         current working directory
183      */
184     public String getDefaultWorkspaceName() {
185         return defaultWorkspace;
186     }
187 
188     /**
189      * Set the file system path to the existing directory that should be used for the default workspace. This path should be
190      * relative to the {@link #getRepositoryRootUrl() repository root URL}. If the default is specified as a null String or is not
191      * a valid and resolvable path, this source will consider the default to be the current working directory of this virtual
192      * machine, as defined by the <code>new File(".")</code>.
193      * 
194      * @param pathToDirectoryForDefaultWorkspace the valid and resolvable file system path to the directory representing the
195      *        default workspace, or null if the current working directory should be used as the default workspace
196      */
197     public synchronized void setDefaultWorkspaceName( String pathToDirectoryForDefaultWorkspace ) {
198         this.defaultWorkspace = pathToDirectoryForDefaultWorkspace;
199     }
200 
201     /**
202      * Gets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path
203      * to a directory on the file system.
204      * 
205      * @return the names of the workspaces that this source starts with, or null if there are no such workspaces
206      * @see #setPredefinedWorkspaceNames(String[])
207      * @see #setCreatingWorkspacesAllowed(boolean)
208      */
209     public synchronized String[] getPredefinedWorkspaceNames() {
210         String[] copy = new String[predefinedWorkspaces.length];
211         System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length);
212         return copy;
213     }
214 
215     /**
216      * Sets the names of the workspaces that are available when this source is created. Each workspace name corresponds to a path
217      * to a directory on the file system.
218      * 
219      * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no
220      *        such workspaces
221      * @see #setCreatingWorkspacesAllowed(boolean)
222      * @see #getPredefinedWorkspaceNames()
223      */
224     public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) {
225         this.predefinedWorkspaces = predefinedWorkspaceNames;
226     }
227 
228     /**
229      * Get whether this source allows workspaces to be created dynamically.
230      * 
231      * @return true if this source allows workspaces to be created by clients, or false if the set of workspaces is fixed
232      * @see #setPredefinedWorkspaceNames(String[])
233      * @see #getPredefinedWorkspaceNames()
234      * @see #setCreatingWorkspacesAllowed(boolean)
235      */
236     public boolean isCreatingWorkspacesAllowed() {
237         return capabilities.supportsCreatingWorkspaces();
238     }
239 
240     /**
241      * Set whether this source allows workspaces to be created dynamically.
242      * 
243      * @param allowWorkspaceCreation true if this source allows workspaces to be created by clients, or false if the set of
244      *        workspaces is fixed
245      * @see #setPredefinedWorkspaceNames(String[])
246      * @see #getPredefinedWorkspaceNames()
247      * @see #isCreatingWorkspacesAllowed()
248      */
249     public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) {
250         capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), capabilities.supportsUpdates(),
251                                                         capabilities.supportsEvents(), allowWorkspaceCreation,
252                                                         capabilities.supportsReferences());
253     }
254 
255     /**
256      * Get whether this source allows updates.
257      * 
258      * @return true if this source allows updates by clients, or false if no updates are allowed
259      * @see #setUpdatesAllowed(boolean)
260      */
261     @Override
262     public boolean areUpdatesAllowed() {
263         return capabilities.supportsUpdates();
264     }
265 
266     /**
267      * Set whether this source allows updates to data within workspaces
268      * 
269      * @param allowUpdates true if this source allows updates to data within workspaces clients, or false if updates are not
270      *        allowed.
271      * @see #areUpdatesAllowed()
272      */
273     public synchronized void setUpdatesAllowed( boolean allowUpdates ) {
274         capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), allowUpdates,
275                                                         capabilities.supportsEvents(), capabilities.supportsCreatingWorkspaces(),
276                                                         capabilities.supportsReferences());
277     }
278 
279     /**
280      * {@inheritDoc}
281      */
282     @Override
283     public boolean equals( Object obj ) {
284         if (obj == this) return true;
285         if (obj instanceof SvnRepositorySource) {
286             SvnRepositorySource that = (SvnRepositorySource)obj;
287             if (this.getName() == null) {
288                 if (that.getName() != null) return false;
289             } else {
290                 if (!this.getName().equals(that.getName())) return false;
291             }
292             return true;
293         }
294         return false;
295     }
296 
297     @Override
298     public int hashCode() {
299         return getName().hashCode();
300     }
301 
302     /**
303      * {@inheritDoc}
304      * 
305      * @see javax.naming.Referenceable#getReference()
306      */
307     public synchronized Reference getReference() {
308         String className = getClass().getName();
309         String factoryClassName = this.getClass().getName();
310         Reference ref = new Reference(className, factoryClassName, null);
311 
312         if (getName() != null) {
313             ref.add(new StringRefAddr(SOURCE_NAME, getName()));
314         }
315         if (getRepositoryRootUrl() != null) {
316             ref.add(new StringRefAddr(SVN_REPOSITORY_ROOT_URL, getRepositoryRootUrl()));
317         }
318         if (getUsername() != null) {
319             ref.add(new StringRefAddr(SVN_USERNAME, getUsername()));
320         }
321         if (getPassword() != null) {
322             ref.add(new StringRefAddr(SVN_PASSWORD, getPassword()));
323         }
324         ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
325         ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuidObject().toString()));
326         ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName()));
327         ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
328         String[] workspaceNames = getPredefinedWorkspaceNames();
329         if (workspaceNames != null && workspaceNames.length != 0) {
330             ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames)));
331         }
332         return ref;
333 
334     }
335 
336     /**
337      * {@inheritDoc}
338      * 
339      * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context,
340      *      java.util.Hashtable)
341      */
342     public Object getObjectInstance( Object obj,
343                                      Name name,
344                                      Context nameCtx,
345                                      Hashtable<?, ?> environment ) throws Exception {
346         if (!(obj instanceof Reference)) return null;
347 
348         Map<String, Object> values = valuesFrom((Reference)obj);
349 
350         String sourceName = (String)values.get(SOURCE_NAME);
351         String repositoryRootUrl = (String)values.get(SVN_REPOSITORY_ROOT_URL);
352         String username = (String)values.get(SVN_USERNAME);
353         String password = (String)values.get(SVN_PASSWORD);
354         String retryLimit = (String)values.get(RETRY_LIMIT);
355         String rootNodeUuid = (String)values.get(ROOT_NODE_UUID);
356         String defaultWorkspace = (String)values.get(DEFAULT_WORKSPACE);
357         String createWorkspaces = (String)values.get(ALLOW_CREATING_WORKSPACES);
358 
359         String combinedWorkspaceNames = (String)values.get(PREDEFINED_WORKSPACE_NAMES);
360         String[] workspaceNames = null;
361         if (combinedWorkspaceNames != null) {
362             List<String> paths = StringUtil.splitLines(combinedWorkspaceNames);
363             workspaceNames = paths.toArray(new String[paths.size()]);
364         }
365         // Create the source instance ...
366         SvnRepositorySource source = new SvnRepositorySource();
367         if (sourceName != null) source.setName(sourceName);
368         if (repositoryRootUrl != null && repositoryRootUrl.length() > 0) source.setRepositoryRootUrl(repositoryRootUrl);
369         if (username != null) source.setUsername(username);
370         if (password != null) source.setPassword(password);
371         if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
372         if (rootNodeUuid != null) source.setRootNodeUuidObject(rootNodeUuid);
373         if (defaultWorkspace != null) source.setDefaultWorkspaceName(defaultWorkspace);
374         if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
375         if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
376         return source;
377     }
378 
379     /**
380      * {@inheritDoc}
381      * 
382      * @see org.modeshape.graph.connector.RepositorySource#getConnection()
383      */
384     public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
385 
386         String sourceName = getName();
387         if (sourceName == null || sourceName.trim().length() == 0) {
388             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
389             throw new RepositorySourceException(getName(), msg.text("name"));
390         }
391 
392         String sourceUsername = getUsername();
393         if (sourceUsername == null || sourceUsername.trim().length() == 0) {
394             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
395             throw new RepositorySourceException(getUsername(), msg.text("username"));
396         }
397 
398         String sourcePassword = getPassword();
399         if (sourcePassword == null) {
400             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
401             throw new RepositorySourceException(getPassword(), msg.text("password"));
402         }
403 
404         String repositoryRootURL = getRepositoryRootUrl();
405         if (repositoryRootURL == null || repositoryRootURL.trim().length() == 0) {
406             I18n msg = SvnRepositoryConnectorI18n.propertyIsRequired;
407             throw new RepositorySourceException(getRepositoryRootUrl(), msg.text("repositoryRootURL"));
408         }
409         repositoryRootURL = repositoryRootURL.trim();
410         if (repositoryRootURL.endsWith("/")) repositoryRootURL = repositoryRootURL + "/";
411 
412         if (repository == null) {
413             repository = new SvnRepository(this);
414 
415             ExecutionContext context = repositoryContext != null ? repositoryContext.getExecutionContext() : defaultContext;
416             SvnTransaction txn = repository.startTransaction(context, false);
417             try {
418                 // Create the set of initial workspaces ...
419                 for (String initialName : getPredefinedWorkspaceNames()) {
420                     repository.createWorkspace(txn, initialName, CreateConflictBehavior.DO_NOT_CREATE, null);
421                 }
422             } finally {
423                 txn.commit();
424             }
425 
426         }
427         return new Connection<PathNode, SvnWorkspace>(this, repository);
428     }
429 }