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  
25  package org.modeshape.connector.jcr;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.util.Enumeration;
33  import java.util.HashMap;
34  import java.util.Hashtable;
35  import java.util.Map;
36  import javax.jcr.Credentials;
37  import javax.jcr.Repository;
38  import javax.jcr.SimpleCredentials;
39  import javax.naming.BinaryRefAddr;
40  import javax.naming.Context;
41  import javax.naming.InitialContext;
42  import javax.naming.NamingException;
43  import javax.naming.RefAddr;
44  import javax.naming.Reference;
45  import javax.naming.StringRefAddr;
46  import javax.naming.spi.ObjectFactory;
47  import net.jcip.annotations.ThreadSafe;
48  import org.modeshape.common.i18n.I18n;
49  import org.modeshape.graph.cache.CachePolicy;
50  import org.modeshape.graph.connector.RepositoryConnection;
51  import org.modeshape.graph.connector.RepositoryContext;
52  import org.modeshape.graph.connector.RepositorySource;
53  import org.modeshape.graph.connector.RepositorySourceCapabilities;
54  import org.modeshape.graph.connector.RepositorySourceException;
55  
56  /**
57   * The {@link RepositorySource} for the connector that exposes an area of the local file system as content in a repository. This
58   * source considers a workspace name to be the path to the directory on the file system that represents the root of that
59   * workspace. New workspaces can be created, as long as the names represent valid paths to existing directories.
60   */
61  @ThreadSafe
62  public class JcrRepositorySource implements RepositorySource, ObjectFactory {
63  
64      /**
65       * The first serialized version of this source. Version {@value} .
66       */
67      private static final long serialVersionUID = 1L;
68  
69      protected static final String SOURCE_NAME = "sourceName";
70      protected static final String REPOSITORY_JNDI_NAME = "repositoryJndiName";
71      protected static final String USERNAME = "username";
72      protected static final String PASSWORD = "password";
73      protected static final String CREDENTIALS = "credentials";
74      protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy";
75      protected static final String RETRY_LIMIT = "retryLimit";
76  
77      /**
78       * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
79       */
80      public static final int DEFAULT_RETRY_LIMIT = 0;
81  
82      /**
83       * This source supports events.
84       */
85      protected static final boolean SUPPORTS_EVENTS = true;
86      /**
87       * This source supports same-name-siblings.
88       */
89      protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = true;
90      /**
91       * This source does support updates.
92       */
93      protected static final boolean SUPPORTS_UPDATES = true;
94      /**
95       * This source does not support creating new workspaces, since the JCR API does not provide a way of doing so.
96       */
97      protected static final boolean SUPPORTS_CREATING_WORKSPACES = false;
98      /**
99       * This source supports creating references.
100      */
101     protected static final boolean SUPPORTS_REFERENCES = false;
102 
103     private volatile String name;
104     private volatile String repositoryJndiName;
105     private volatile String username;
106     private volatile String password;
107     private volatile Credentials credentials;
108     private volatile CachePolicy defaultCachePolicy;
109     private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
110     private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(SUPPORTS_SAME_NAME_SIBLINGS,
111                                                                                                   SUPPORTS_UPDATES,
112                                                                                                   SUPPORTS_EVENTS,
113                                                                                                   SUPPORTS_CREATING_WORKSPACES,
114                                                                                                   SUPPORTS_REFERENCES);
115     private transient Repository repository;
116     private transient Context jndiContext;
117     private transient RepositoryContext repositoryContext;
118 
119     /**
120      * Create a new instance of a JCR repository source.
121      */
122     public JcrRepositorySource() {
123     }
124 
125     /**
126      * {@inheritDoc}
127      * 
128      * @see org.modeshape.graph.connector.RepositorySource#getName()
129      */
130     public String getName() {
131         return name;
132     }
133 
134     /**
135      * Set the name for the source
136      * 
137      * @param name the new name for the source
138      */
139     public void setName( String name ) {
140         if (name != null) {
141             name = name.trim();
142             if (name.length() == 0) name = null;
143         }
144         this.name = name;
145     }
146 
147     /**
148      * {@inheritDoc}
149      * 
150      * @see org.modeshape.graph.connector.RepositorySource#getCapabilities()
151      */
152     public RepositorySourceCapabilities getCapabilities() {
153         return capabilities;
154     }
155 
156     /**
157      * Get whether this source supports updates.
158      * 
159      * @return true if this source supports updates, or false if this source only supports reading content.
160      */
161     public boolean getUpdatesAllowed() {
162         return capabilities.supportsUpdates();
163     }
164 
165     /**
166      * @return dataSourceJndiName
167      */
168     public String getRepositoryJndiName() {
169         return repositoryJndiName;
170     }
171 
172     /**
173      * Set the name in JNDI where this source can find the JCR Repository object.
174      * 
175      * @param repositoryJndiName the name in JNDI where the JCR Repository object can be found
176      */
177     public void setRepositoryJndiName( String repositoryJndiName ) {
178         if (repositoryJndiName != null && repositoryJndiName.trim().length() == 0) repositoryJndiName = null;
179         this.repositoryJndiName = repositoryJndiName;
180     }
181 
182     /**
183      * Get the username that should be used to access the repository.
184      * 
185      * @return the username, which may be null if the
186      */
187     public String getUsername() {
188         return username;
189     }
190 
191     /**
192      * @param username Sets username to the specified value.
193      */
194     public synchronized void setUsername( String username ) {
195         this.username = username;
196     }
197 
198     /**
199      * @return password
200      */
201     public String getPassword() {
202         return password;
203     }
204 
205     /**
206      * @param password Sets password to the specified value.
207      */
208     public synchronized void setPassword( String password ) {
209         this.password = password;
210     }
211 
212     /**
213      * Get the JCR credentials that should be used.
214      * 
215      * @return the credentials, or null if no credentials should be used
216      */
217     public Credentials getCredentials() {
218         return credentials;
219     }
220 
221     /**
222      * Set the JCR credentials that should be used.
223      * 
224      * @param credentials the credentials, or null if no fixed credentials should be used
225      */
226     public void setCredentials( Credentials credentials ) {
227         this.credentials = credentials;
228     }
229 
230     /**
231      * Get the default cache policy for this source, or null if the global default cache policy should be used
232      * 
233      * @return the default cache policy, or null if this source has no explicit default cache policy
234      */
235     public CachePolicy getDefaultCachePolicy() {
236         return defaultCachePolicy;
237     }
238 
239     /**
240      * @param defaultCachePolicy Sets defaultCachePolicy to the specified value.
241      */
242     public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) {
243         if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null
244             && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged
245         this.defaultCachePolicy = defaultCachePolicy;
246     }
247 
248     /**
249      * {@inheritDoc}
250      * 
251      * @see org.modeshape.graph.connector.RepositorySource#getRetryLimit()
252      */
253     public int getRetryLimit() {
254         return retryLimit;
255     }
256 
257     /**
258      * {@inheritDoc}
259      * 
260      * @see org.modeshape.graph.connector.RepositorySource#setRetryLimit(int)
261      */
262     public synchronized void setRetryLimit( int limit ) {
263         retryLimit = limit < 0 ? 0 : limit;
264     }
265 
266     /**
267      * {@inheritDoc}
268      * 
269      * @see org.modeshape.graph.connector.RepositorySource#initialize(org.modeshape.graph.connector.RepositoryContext)
270      */
271     public void initialize( RepositoryContext context ) throws RepositorySourceException {
272         this.repositoryContext = context;
273     }
274 
275     protected RepositoryContext getRepositoryContext() {
276         return repositoryContext;
277     }
278 
279     protected synchronized Repository getRepository() {
280         return this.repository;
281     }
282 
283     protected synchronized void setRepository( Repository repository ) {
284         this.repository = repository;
285     }
286 
287     protected synchronized Context getContext() {
288         return this.jndiContext;
289     }
290 
291     protected synchronized void setContext( Context context ) {
292         this.jndiContext = context;
293     }
294 
295     /**
296      * {@inheritDoc}
297      * 
298      * @see javax.naming.Referenceable#getReference()
299      */
300     public synchronized Reference getReference() {
301         String className = getClass().getName();
302         String factoryClassName = this.getClass().getName();
303         Reference ref = new Reference(className, factoryClassName, null);
304 
305         if (getName() != null) {
306             ref.add(new StringRefAddr(SOURCE_NAME, getName()));
307         }
308         ref.add(new StringRefAddr(SOURCE_NAME, getName()));
309         ref.add(new StringRefAddr(REPOSITORY_JNDI_NAME, getRepositoryJndiName()));
310         ref.add(new StringRefAddr(USERNAME, getUsername()));
311         ref.add(new StringRefAddr(PASSWORD, getPassword()));
312         ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
313         if (getCredentials() != null) {
314             ByteArrayOutputStream baos = new ByteArrayOutputStream();
315             Credentials credentials = getCredentials();
316             try {
317                 ObjectOutputStream oos = new ObjectOutputStream(baos);
318                 oos.writeObject(credentials);
319                 ref.add(new BinaryRefAddr(CREDENTIALS, baos.toByteArray()));
320             } catch (IOException e) {
321                 I18n msg = JcrConnectorI18n.errorSerializingObjectUsedInSource;
322                 throw new RepositorySourceException(getName(), msg.text(credentials.getClass().getName(), getName()), e);
323             }
324         }
325         if (getDefaultCachePolicy() != null) {
326             ByteArrayOutputStream baos = new ByteArrayOutputStream();
327             CachePolicy policy = getDefaultCachePolicy();
328             try {
329                 ObjectOutputStream oos = new ObjectOutputStream(baos);
330                 oos.writeObject(policy);
331                 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray()));
332             } catch (IOException e) {
333                 I18n msg = JcrConnectorI18n.errorSerializingObjectUsedInSource;
334                 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e);
335             }
336         }
337         return ref;
338     }
339 
340     /**
341      * {@inheritDoc}
342      */
343     public Object getObjectInstance( Object obj,
344                                      javax.naming.Name name,
345                                      Context nameCtx,
346                                      Hashtable<?, ?> environment ) throws Exception {
347         if (obj instanceof Reference) {
348             Map<String, Object> values = new HashMap<String, Object>();
349             Reference ref = (Reference)obj;
350             Enumeration<?> en = ref.getAll();
351             while (en.hasMoreElements()) {
352                 RefAddr subref = (RefAddr)en.nextElement();
353                 if (subref instanceof StringRefAddr) {
354                     String key = subref.getType();
355                     Object value = subref.getContent();
356                     if (value != null) values.put(key, value.toString());
357                 } else if (subref instanceof BinaryRefAddr) {
358                     String key = subref.getType();
359                     Object value = subref.getContent();
360                     if (value instanceof byte[]) {
361                         // Deserialize ...
362                         ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value);
363                         ObjectInputStream ois = new ObjectInputStream(bais);
364                         value = ois.readObject();
365                         values.put(key, value);
366                     }
367                 }
368             }
369             String sourceName = (String)values.get(SOURCE_NAME);
370             String repositoryJndiName = (String)values.get(REPOSITORY_JNDI_NAME);
371             String username = (String)values.get(USERNAME);
372             String password = (String)values.get(PASSWORD);
373             String retryLimit = (String)values.get(RETRY_LIMIT);
374             Object credentials = values.get(CREDENTIALS);
375             Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY);
376 
377             // Create the source instance ...
378             JcrRepositorySource source = new JcrRepositorySource();
379             if (sourceName != null) source.setName(sourceName);
380             if (repositoryJndiName != null) source.setRepositoryJndiName(repositoryJndiName);
381             if (username != null) source.setUsername(username);
382             if (password != null) source.setPassword(password);
383             if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
384             if (credentials instanceof Credentials) {
385                 source.setCredentials((Credentials)credentials);
386             }
387             if (defaultCachePolicy instanceof CachePolicy) {
388                 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy);
389             }
390             return source;
391         }
392         return null;
393     }
394 
395     /**
396      * {@inheritDoc}
397      * 
398      * @see org.modeshape.graph.connector.RepositorySource#getConnection()
399      */
400     public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
401         if (name == null || name.trim().length() == 0) {
402             I18n msg = JcrConnectorI18n.propertyIsRequired;
403             throw new RepositorySourceException(name, msg.text("name"));
404         }
405         if (repositoryJndiName == null || repositoryJndiName.trim().length() == 0) {
406             I18n msg = JcrConnectorI18n.propertyIsRequired;
407             throw new RepositorySourceException(getName(), msg.text("repositoryJndiName"));
408         }
409         if (this.repository == null) {
410             Context context = getContext();
411             if (context == null) {
412                 try {
413                     context = new InitialContext();
414                 } catch (NamingException err) {
415                     throw new RepositorySourceException(name, err);
416                 }
417             }
418 
419             // Look for a cache manager in JNDI ...
420             Repository repository = null;
421             Object object = null;
422             try {
423                 object = context.lookup(repositoryJndiName);
424                 if (object != null) repository = (Repository)object;
425             } catch (ClassCastException err) {
426                 I18n msg = JcrConnectorI18n.objectFoundInJndiWasNotRepository;
427                 String className = object != null ? object.getClass().getName() : "null";
428                 throw new RepositorySourceException(getName(), msg.text(repositoryJndiName, this.getName(), className), err);
429             } catch (Throwable err) {
430                 if (err instanceof RuntimeException) throw (RuntimeException)err;
431                 throw new RepositorySourceException(getName(), err);
432             }
433             if (repository == null) {
434                 I18n msg = JcrConnectorI18n.repositoryObjectNotFoundInJndi;
435                 throw new RepositorySourceException(getName(), msg.text(repositoryJndiName));
436             }
437             this.repository = repository;
438         }
439         assert this.repository != null;
440 
441         // Create the appropriate credentials ...
442         Credentials credentials = getCredentials();
443         if (credentials == null || (username != null || password != null)) {
444             char[] passwd = this.password != null ? password.toCharArray() : new char[] {};
445             credentials = new SimpleCredentials(username, passwd);
446         }
447 
448         // Create the repositor connection ...
449         return new JcrRepositoryConnection(this, repository, credentials);
450     }
451 
452     /**
453      * {@inheritDoc}
454      * 
455      * @see org.modeshape.graph.connector.RepositorySource#close()
456      */
457     public void close() {
458         // Null the repository context and Repository references ...
459         this.repositoryContext = null;
460         this.repository = null;
461     }
462 }