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