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.store.jpa;
25  
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Enumeration;
31  import java.util.HashMap;
32  import java.util.Hashtable;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Properties;
36  import java.util.UUID;
37  import javax.naming.Context;
38  import javax.naming.InitialContext;
39  import javax.naming.RefAddr;
40  import javax.naming.Reference;
41  import javax.naming.StringRefAddr;
42  import javax.naming.spi.ObjectFactory;
43  import javax.persistence.EntityManager;
44  import javax.persistence.EntityManagerFactory;
45  import javax.sql.DataSource;
46  import net.jcip.annotations.Immutable;
47  import net.jcip.annotations.ThreadSafe;
48  import org.hibernate.SessionFactory;
49  import org.hibernate.ejb.Ejb3Configuration;
50  import org.modeshape.common.annotation.AllowedValues;
51  import org.modeshape.common.annotation.Category;
52  import org.modeshape.common.annotation.Description;
53  import org.modeshape.common.annotation.Label;
54  import org.modeshape.common.i18n.I18n;
55  import org.modeshape.common.util.CheckArg;
56  import org.modeshape.common.util.Logger;
57  import org.modeshape.common.util.StringUtil;
58  import org.modeshape.connector.store.jpa.model.simple.SimpleModel;
59  import org.modeshape.connector.store.jpa.util.StoreOptionEntity;
60  import org.modeshape.connector.store.jpa.util.StoreOptions;
61  import org.modeshape.graph.ExecutionContext;
62  import org.modeshape.graph.cache.CachePolicy;
63  import org.modeshape.graph.connector.RepositoryConnection;
64  import org.modeshape.graph.connector.RepositoryContext;
65  import org.modeshape.graph.connector.RepositorySource;
66  import org.modeshape.graph.connector.RepositorySourceCapabilities;
67  import org.modeshape.graph.connector.RepositorySourceException;
68  
69  /**
70   * The {@link RepositorySource} for the connector that stores content in a (custom) relational database. This connector uses Java
71   * Persistence API as the interface to the database, with Hibernate as the JPA implementation. (Note that some Hibernate-specific
72   * features are used.)
73   */
74  @ThreadSafe
75  public class JpaSource implements RepositorySource, ObjectFactory {
76  
77      private final Logger LOGGER = Logger.getLogger(JpaSource.class);
78  
79      /**
80       * This source is capable of using different database schemas
81       * 
82       * @author Randall Hauch
83       */
84      public static class Models {
85          public static final Model SIMPLE = new SimpleModel();
86          private static final Model[] ALL_ARRAY = new Model[] {SIMPLE};
87          private static final List<Model> MODIFIABLE_MODELS = new ArrayList<Model>(Arrays.asList(ALL_ARRAY));
88          public static final Collection<Model> ALL = Collections.unmodifiableCollection(MODIFIABLE_MODELS);
89          public static final Model DEFAULT = SIMPLE;
90  
91          public static boolean addModel( Model model ) {
92              CheckArg.isNotNull(model, "modelName");
93              for (Model existing : MODIFIABLE_MODELS) {
94                  if (existing.getName().equals(model.getName())) return false;
95              }
96              return MODIFIABLE_MODELS.add(model);
97          }
98  
99          public static Model getModel( String name ) {
100             CheckArg.isNotEmpty(name, "name");
101             name = name.trim();
102             for (Model existing : ALL) {
103                 if (existing.getName().equals(name)) return existing;
104             }
105             return null;
106         }
107     }
108 
109     protected static final String SOURCE_NAME = "sourceName";
110     protected static final String ROOT_NODE_UUID = "rootNodeUuid";
111     protected static final String DATA_SOURCE_JNDI_NAME = "dataSourceJndiName";
112     protected static final String DIALECT = "dialect";
113     protected static final String USERNAME = "username";
114     protected static final String PASSWORD = "password";
115     protected static final String URL = "url";
116     protected static final String DRIVER_CLASS_NAME = "driverClassName";
117     protected static final String DRIVER_CLASSLOADER_NAME = "driverClassloaderName";
118     protected static final String MAXIMUM_CONNECTIONS_IN_POOL = "maximumConnectionsInPool";
119     protected static final String MINIMUM_CONNECTIONS_IN_POOL = "minimumConnectionsInPool";
120     protected static final String MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS = "maximumConnectionIdleTimeInSeconds";
121     protected static final String MAXIMUM_SIZE_OF_STATEMENT_CACHE = "maximumSizeOfStatementCache";
122     protected static final String NUMBER_OF_CONNECTIONS_TO_BE_ACQUIRED_AS_NEEDED = "numberOfConnectionsToBeAcquiredAsNeeded";
123     protected static final String IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS = "idleTimeInSecondsBeforeTestingConnections";
124     protected static final String CACHE_TIME_TO_LIVE_IN_MILLISECONDS = "cacheTimeToLiveInMilliseconds";
125     protected static final String RETRY_LIMIT = "retryLimit";
126     protected static final String MODEL_NAME = "modelName";
127     protected static final String LARGE_VALUE_SIZE_IN_BYTES = "largeValueSizeInBytes";
128     protected static final String COMPRESS_DATA = "compressData";
129     protected static final String ENFORCE_REFERENTIAL_INTEGRITY = "enforceReferentialIntegrity";
130     protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
131     protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
132     protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
133     protected static final String AUTO_GENERATE_SCHEMA = "autoGenerateSchema";
134 
135     /**
136      * This source supports events.
137      */
138     protected static final boolean SUPPORTS_EVENTS = true;
139     /**
140      * This source supports same-name-siblings.
141      */
142     protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = true;
143     /**
144      * This source supports creating references.
145      */
146     protected static final boolean SUPPORTS_REFERENCES = true;
147     /**
148      * This source supports updates by default, but each instance may be configured to {@link #setAllowsUpdates(boolean) be
149      * read-only or updateable}.
150      */
151     public static final boolean DEFAULT_ALLOWS_UPDATES = true;
152     /**
153      * This source does not output executed SQL by default, but this can be overridden by calling {@link #setShowSql(boolean)}.
154      */
155     public static final boolean DEFAULT_SHOW_SQL = false;
156     /**
157      * This source does support creating workspaces.
158      */
159     public static final boolean DEFAULT_SUPPORTS_CREATING_WORKSPACES = true;
160 
161     /**
162      * The default UUID that is used for root nodes in a store.
163      */
164     public static final String DEFAULT_ROOT_NODE_UUID = "1497b6fe-8c7e-4bbb-aaa2-24f3d4942668";
165 
166     /**
167      * The initial {@link #getDefaultWorkspaceName() name of the default workspace} is "{@value} ", unless otherwise specified.
168      */
169     public static final String DEFAULT_NAME_OF_DEFAULT_WORKSPACE = "default";
170 
171     private static final int DEFAULT_RETRY_LIMIT = 0;
172     private static final int DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS = 60 * 5; // 5 minutes
173     private static final int DEFAULT_MAXIMUM_FETCH_DEPTH = 3;
174     private static final int DEFAULT_MAXIMUM_CONNECTIONS_IN_POOL = 5;
175     private static final int DEFAULT_MINIMUM_CONNECTIONS_IN_POOL = 0;
176     private static final int DEFAULT_MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS = 60 * 10; // 10 minutes
177     private static final int DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE = 100;
178     private static final int DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED = 1;
179     private static final int DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS = 60 * 3; // 3 minutes
180     private static final int DEFAULT_LARGE_VALUE_SIZE_IN_BYTES = 1 << 10; // 1 kilobyte
181     private static final boolean DEFAULT_COMPRESS_DATA = true;
182     private static final boolean DEFAULT_ENFORCE_REFERENTIAL_INTEGRITY = true;
183 
184     /**
185      * The initial {@link #getAutoGenerateSchema() automatic schema generation setting} is "{@value} ", unless otherwise
186      * specified.
187      */
188     public static final String DEFAULT_AUTO_GENERATE_SCHEMA = "validate";
189 
190     /**
191      * The first serialized version of this source.
192      */
193     private static final long serialVersionUID = 1L;
194 
195     @Description( i18n = JpaConnectorI18n.class, value = "namePropertyDescription" )
196     @Label( i18n = JpaConnectorI18n.class, value = "namePropertyLabel" )
197     @Category( i18n = JpaConnectorI18n.class, value = "namePropertyCategory" )
198     private volatile String name;
199 
200     @Description( i18n = JpaConnectorI18n.class, value = "dataSourceJndiNamePropertyDescription" )
201     @Label( i18n = JpaConnectorI18n.class, value = "dataSourceJndiNamePropertyLabel" )
202     @Category( i18n = JpaConnectorI18n.class, value = "dataSourceJndiNamePropertyCategory" )
203     private volatile String dataSourceJndiName;
204 
205     @Description( i18n = JpaConnectorI18n.class, value = "dialectPropertyDescription" )
206     @Label( i18n = JpaConnectorI18n.class, value = "dialectPropertyLabel" )
207     @Category( i18n = JpaConnectorI18n.class, value = "dialectPropertyCategory" )
208     private volatile String dialect;
209 
210     @Description( i18n = JpaConnectorI18n.class, value = "usernamePropertyDescription" )
211     @Label( i18n = JpaConnectorI18n.class, value = "usernamePropertyLabel" )
212     @Category( i18n = JpaConnectorI18n.class, value = "usernamePropertyCategory" )
213     private volatile String username;
214 
215     @Description( i18n = JpaConnectorI18n.class, value = "passwordPropertyDescription" )
216     @Label( i18n = JpaConnectorI18n.class, value = "passwordPropertyLabel" )
217     @Category( i18n = JpaConnectorI18n.class, value = "passwordPropertyCategory" )
218     private volatile String password;
219 
220     @Description( i18n = JpaConnectorI18n.class, value = "urlPropertyDescription" )
221     @Label( i18n = JpaConnectorI18n.class, value = "urlPropertyLabel" )
222     @Category( i18n = JpaConnectorI18n.class, value = "urlPropertyCategory" )
223     private volatile String url;
224 
225     @Description( i18n = JpaConnectorI18n.class, value = "driverClassNamePropertyDescription" )
226     @Label( i18n = JpaConnectorI18n.class, value = "driverClassNamePropertyLabel" )
227     @Category( i18n = JpaConnectorI18n.class, value = "driverClassNamePropertyCategory" )
228     private volatile String driverClassName;
229 
230     // @Description( i18n = JpaConnectorI18n.class, value = "driverClassloaderNamePropertyDescription" )
231     // @Label( i18n = JpaConnectorI18n.class, value = "driverClassloaderNamePropertyLabel" )
232     // @Category( i18n = JpaConnectorI18n.class, value = "driverClassloaderNamePropertyCategory" )
233     private volatile String driverClassloaderName;
234 
235     @Description( i18n = JpaConnectorI18n.class, value = "rootNodeUuidPropertyDescription" )
236     @Label( i18n = JpaConnectorI18n.class, value = "rootNodeUuidPropertyLabel" )
237     @Category( i18n = JpaConnectorI18n.class, value = "rootNodeUuidPropertyCategory" )
238     private volatile String rootNodeUuid = DEFAULT_ROOT_NODE_UUID;
239 
240     @Description( i18n = JpaConnectorI18n.class, value = "maximumConnectionsInPoolPropertyDescription" )
241     @Label( i18n = JpaConnectorI18n.class, value = "maximumConnectionsInPoolPropertyLabel" )
242     @Category( i18n = JpaConnectorI18n.class, value = "maximumConnectionsInPoolPropertyCategory" )
243     private volatile int maximumConnectionsInPool = DEFAULT_MAXIMUM_CONNECTIONS_IN_POOL;
244 
245     @Description( i18n = JpaConnectorI18n.class, value = "minimumConnectionsInPoolPropertyDescription" )
246     @Label( i18n = JpaConnectorI18n.class, value = "minimumConnectionsInPoolPropertyLabel" )
247     @Category( i18n = JpaConnectorI18n.class, value = "minimumConnectionsInPoolPropertyCategory" )
248     private volatile int minimumConnectionsInPool = DEFAULT_MINIMUM_CONNECTIONS_IN_POOL;
249 
250     @Description( i18n = JpaConnectorI18n.class, value = "maximumConnectionIdleTimeInSecondsPropertyDescription" )
251     @Label( i18n = JpaConnectorI18n.class, value = "maximumConnectionIdleTimeInSecondsPropertyLabel" )
252     @Category( i18n = JpaConnectorI18n.class, value = "maximumConnectionIdleTimeInSecondsPropertyCategory" )
253     private volatile int maximumConnectionIdleTimeInSeconds = DEFAULT_MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS;
254 
255     @Description( i18n = JpaConnectorI18n.class, value = "maximumSizeOfStatementCachePropertyDescription" )
256     @Label( i18n = JpaConnectorI18n.class, value = "maximumSizeOfStatementCachePropertyLabel" )
257     @Category( i18n = JpaConnectorI18n.class, value = "maximumSizeOfStatementCachePropertyCategory" )
258     private volatile int maximumSizeOfStatementCache = DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE;
259 
260     @Description( i18n = JpaConnectorI18n.class, value = "numberOfConnectionsToAcquireAsNeededPropertyDescription" )
261     @Label( i18n = JpaConnectorI18n.class, value = "numberOfConnectionsToAcquireAsNeededPropertyLabel" )
262     @Category( i18n = JpaConnectorI18n.class, value = "numberOfConnectionsToAcquireAsNeededPropertyCategory" )
263     private volatile int numberOfConnectionsToAcquireAsNeeded = DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED;
264 
265     @Description( i18n = JpaConnectorI18n.class, value = "idleTimeInSecondsBeforeTestingConnectionsPropertyDescription" )
266     @Label( i18n = JpaConnectorI18n.class, value = "idleTimeInSecondsBeforeTestingConnectionsPropertyLabel" )
267     @Category( i18n = JpaConnectorI18n.class, value = "idleTimeInSecondsBeforeTestingConnectionsPropertyCategory" )
268     private volatile int idleTimeInSecondsBeforeTestingConnections = DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS;
269 
270     @Description( i18n = JpaConnectorI18n.class, value = "retryLimitPropertyDescription" )
271     @Label( i18n = JpaConnectorI18n.class, value = "retryLimitPropertyLabel" )
272     @Category( i18n = JpaConnectorI18n.class, value = "retryLimitPropertyCategory" )
273     private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
274 
275     @Description( i18n = JpaConnectorI18n.class, value = "cacheTimeToLiveInMillisecondsPropertyDescription" )
276     @Label( i18n = JpaConnectorI18n.class, value = "cacheTimeToLiveInMillisecondsPropertyLabel" )
277     @Category( i18n = JpaConnectorI18n.class, value = "cacheTimeToLiveInMillisecondsPropertyCategory" )
278     private volatile int cacheTimeToLiveInMilliseconds = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS * 1000;
279 
280     @Description( i18n = JpaConnectorI18n.class, value = "largeValueSizeInBytesPropertyDescription" )
281     @Label( i18n = JpaConnectorI18n.class, value = "largeValueSizeInBytesPropertyLabel" )
282     @Category( i18n = JpaConnectorI18n.class, value = "largeValueSizeInBytesPropertyCategory" )
283     private volatile long largeValueSizeInBytes = DEFAULT_LARGE_VALUE_SIZE_IN_BYTES;
284 
285     @Description( i18n = JpaConnectorI18n.class, value = "showSqlPropertyDescription" )
286     @Label( i18n = JpaConnectorI18n.class, value = "showSqlPropertyLabel" )
287     @Category( i18n = JpaConnectorI18n.class, value = "showSqlPropertyCategory" )
288     private volatile boolean showSql = DEFAULT_SHOW_SQL;
289 
290     @Description( i18n = JpaConnectorI18n.class, value = "compressDataPropertyDescription" )
291     @Label( i18n = JpaConnectorI18n.class, value = "compressDataPropertyLabel" )
292     @Category( i18n = JpaConnectorI18n.class, value = "compressDataPropertyCategory" )
293     private volatile boolean compressData = DEFAULT_COMPRESS_DATA;
294 
295     // @Description( i18n = JpaConnectorI18n.class, value = "referentialIntegrityEnforcedPropertyDescription" )
296     // @Label( i18n = JpaConnectorI18n.class, value = "referentialIntegrityEnforcedPropertyLabel" )
297     // @Category( i18n = JpaConnectorI18n.class, value = "referentialIntegrityEnforcedPropertyCategory" )
298     private volatile boolean referentialIntegrityEnforced = DEFAULT_ENFORCE_REFERENTIAL_INTEGRITY;
299 
300     @Description( i18n = JpaConnectorI18n.class, value = "autoGenerateSchemaPropertyDescription" )
301     @Label( i18n = JpaConnectorI18n.class, value = "autoGenerateSchemaPropertyLabel" )
302     @Category( i18n = JpaConnectorI18n.class, value = "autoGenerateSchemaPropertyCategory" )
303     @AllowedValues( {"create", "create-drop", "update", "validate"} )
304     private volatile String autoGenerateSchema = DEFAULT_AUTO_GENERATE_SCHEMA;
305 
306     @Description( i18n = JpaConnectorI18n.class, value = "defaultWorkspaceNamePropertyDescription" )
307     @Label( i18n = JpaConnectorI18n.class, value = "defaultWorkspaceNamePropertyLabel" )
308     @Category( i18n = JpaConnectorI18n.class, value = "defaultWorkspaceNamePropertyCategory" )
309     private volatile String defaultWorkspace = DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
310 
311     @Description( i18n = JpaConnectorI18n.class, value = "predefinedWorkspacesPropertyDescription" )
312     @Label( i18n = JpaConnectorI18n.class, value = "predefinedWorkspacesPropertyLabel" )
313     @Category( i18n = JpaConnectorI18n.class, value = "predefinedWorkspacesPropertyCategory" )
314     private volatile String[] predefinedWorkspaces = new String[] {};
315 
316     private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(
317                                                                                                   SUPPORTS_SAME_NAME_SIBLINGS,
318                                                                                                   DEFAULT_ALLOWS_UPDATES,
319                                                                                                   SUPPORTS_EVENTS,
320                                                                                                   DEFAULT_SUPPORTS_CREATING_WORKSPACES,
321                                                                                                   SUPPORTS_REFERENCES);
322 
323     @Description( i18n = JpaConnectorI18n.class, value = "modelNamePropertyDescription" )
324     @Label( i18n = JpaConnectorI18n.class, value = "modelNamePropertyLabel" )
325     @Category( i18n = JpaConnectorI18n.class, value = "modelNamePropertyCategory" )
326     private volatile String modelName;
327     private transient Model model;
328     private transient DataSource dataSource;
329     private transient EntityManagers entityManagers;
330     private transient CachePolicy cachePolicy;
331     private transient RepositoryContext repositoryContext;
332     private transient UUID rootUuid = UUID.fromString(rootNodeUuid);
333 
334     /**
335      * {@inheritDoc}
336      * 
337      * @see org.modeshape.graph.connector.RepositorySource#getName()
338      */
339     public String getName() {
340         return name;
341     }
342 
343     protected Logger getLogger() {
344         return LOGGER;
345     }
346 
347     /**
348      * Set the name for the source
349      * 
350      * @param name the new name for the source
351      */
352     public void setName( String name ) {
353         if (name != null) {
354             name = name.trim();
355             if (name.length() == 0) name = null;
356         }
357         this.name = name;
358     }
359 
360     /**
361      * {@inheritDoc}
362      * 
363      * @see org.modeshape.graph.connector.RepositorySource#getCapabilities()
364      */
365     public RepositorySourceCapabilities getCapabilities() {
366         return capabilities;
367     }
368 
369     /**
370      * Get whether this source allows updates.
371      * 
372      * @return true if this source allows updates, or false if this source only supports reading content.
373      */
374     @Description( i18n = JpaConnectorI18n.class, value = "updatesAllowedPropertyDescription" )
375     @Label( i18n = JpaConnectorI18n.class, value = "updatesAllowedPropertyLabel" )
376     @Category( i18n = JpaConnectorI18n.class, value = "updatesAllowedPropertyCategory" )
377     public boolean areUpdatesAllowed() {
378         return capabilities.supportsUpdates();
379     }
380 
381     /**
382      * Set whether this source allows updates.
383      * 
384      * @param allowsUpdates true if this source allows updating content, or false if this source only allows reading content.
385      */
386     public synchronized void setAllowsUpdates( boolean allowsUpdates ) {
387         capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), allowsUpdates,
388                                                         capabilities.supportsEvents(), capabilities.supportsCreatingWorkspaces(),
389                                                         capabilities.supportsReferences());
390     }
391 
392     /**
393      * Get whether this source outputs the SQL that it executes
394      * 
395      * @return whether this source outputs the SQL that it executes
396      */
397     public boolean getShowSql() {
398         return this.showSql;
399     }
400 
401     /**
402      * Sets whether this source should output the SQL that it executes
403      * 
404      * @param showSql true if this source should output the SQL that it executes, otherwise false
405      */
406     public synchronized void setShowSql( boolean showSql ) {
407         this.showSql = showSql;
408     }
409 
410     /**
411      * Get the Hibernate setting dictating what it does with the database schema upon first connection. For more information, see
412      * {@link #setAutoGenerateSchema(String)}.
413      * 
414      * @return the setting; never null
415      */
416     public String getAutoGenerateSchema() {
417         return this.autoGenerateSchema;
418     }
419 
420     /**
421      * Sets the Hibernate setting dictating what it does with the database schema upon first connection. Valid values are as
422      * follows (though the value is not checked):
423      * <ul>
424      * <li>"<code>create</code>" - Create the database schema objects when the {@link EntityManagerFactory} is created (actually
425      * when Hibernate's {@link SessionFactory} is created by the entity manager factory). If a file named "import.sql" exists in
426      * the root of the class path (e.g., '/import.sql') Hibernate will read and execute the SQL statements in this file after it
427      * has created the database objects. Note that Hibernate first delete all tables, constraints, or any other database object
428      * that is going to be created in the process of building the schema.</li>
429      * <li>"<code>create-drop</code>" - Same as "<code>create</code>", except that the schema will be dropped after the
430      * {@link EntityManagerFactory} is closed.</li>
431      * <li>"<code>update</code>" - Attempt to update the database structure to the current mapping (but does not read and invoke
432      * the SQL statements from "import.sql"). <i>Use with caution.</i></li>
433      * <li>"<code>validate</code>" - Validates the existing schema with the current entities configuration, but does not make any
434      * changes to the schema (and does not read and invoke the SQL statements from "import.sql"). This is often the proper setting
435      * to use in production, and thus this is the default value.</li>
436      * </ul>
437      * 
438      * @param autoGenerateSchema the setting for the auto-generation, or null if the default should be used
439      */
440     public synchronized void setAutoGenerateSchema( String autoGenerateSchema ) {
441         this.autoGenerateSchema = autoGenerateSchema != null ? autoGenerateSchema.trim() : DEFAULT_AUTO_GENERATE_SCHEMA;
442     }
443 
444     /**
445      * {@inheritDoc}
446      * 
447      * @see org.modeshape.graph.connector.RepositorySource#getRetryLimit()
448      */
449     public int getRetryLimit() {
450         return retryLimit;
451     }
452 
453     /**
454      * {@inheritDoc}
455      * 
456      * @see org.modeshape.graph.connector.RepositorySource#setRetryLimit(int)
457      */
458     public synchronized void setRetryLimit( int limit ) {
459         if (limit < 0) limit = 0;
460         this.retryLimit = limit;
461     }
462 
463     /**
464      * Get the time in milliseconds that content returned from this source may used while in the cache.
465      * 
466      * @return the time to live, in milliseconds, or 0 if the time to live is not specified by this source
467      */
468     public int getCacheTimeToLiveInMilliseconds() {
469         return cacheTimeToLiveInMilliseconds;
470     }
471 
472     /**
473      * Set the time in milliseconds that content returned from this source may used while in the cache.
474      * 
475      * @param cacheTimeToLive the time to live, in milliseconds; 0 if the time to live is not specified by this source; or a
476      *        negative number for the default value
477      */
478     public synchronized void setCacheTimeToLiveInMilliseconds( int cacheTimeToLive ) {
479         if (cacheTimeToLive < 0) cacheTimeToLive = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS;
480         this.cacheTimeToLiveInMilliseconds = cacheTimeToLive;
481         this.cachePolicy = cacheTimeToLiveInMilliseconds > 0 ? new JpaCachePolicy(cacheTimeToLiveInMilliseconds) : null;
482     }
483 
484     /**
485      * Returns the current cache policy
486      * 
487      * @return the current cache policy
488      */
489     public CachePolicy getCachePolicy() {
490         return cachePolicy;
491     }
492 
493     /**
494      * Returns the current {@code EntityManagers} reference.
495      * 
496      * @return the current {@code EntityManagers} reference.
497      */
498     public EntityManagers getEntityManagers() {
499         return entityManagers;
500     }
501 
502     /**
503      * @return rootNodeUuid
504      */
505     public String getRootNodeUuid() {
506         return rootNodeUuid;
507     }
508 
509     /**
510      * @return rootUuid
511      */
512     public UUID getRootUuid() {
513         return rootUuid;
514     }
515 
516     /**
517      * @param rootNodeUuid Sets rootNodeUuid to the specified value.
518      * @throws IllegalArgumentException if the string value cannot be converted to UUID
519      */
520     public void setRootNodeUuid( String rootNodeUuid ) {
521         if (rootNodeUuid != null && rootNodeUuid.trim().length() == 0) rootNodeUuid = DEFAULT_ROOT_NODE_UUID;
522         this.rootUuid = UUID.fromString(rootNodeUuid);
523         this.rootNodeUuid = rootNodeUuid;
524     }
525 
526     /**
527      * Get the name of the default workspace.
528      * 
529      * @return the name of the workspace that should be used by default, or null if there is no default workspace
530      */
531     public String getDefaultWorkspaceName() {
532         return defaultWorkspace;
533     }
534 
535     /**
536      * Set the name of the workspace that should be used when clients don't specify a workspace.
537      * 
538      * @param nameOfDefaultWorkspace the name of the workspace that should be used by default, or null if the
539      *        {@link #DEFAULT_NAME_OF_DEFAULT_WORKSPACE default name} should be used
540      */
541     public synchronized void setDefaultWorkspaceName( String nameOfDefaultWorkspace ) {
542         this.defaultWorkspace = nameOfDefaultWorkspace != null ? nameOfDefaultWorkspace : DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
543     }
544 
545     /**
546      * Gets the names of the workspaces that are available when this source is created.
547      * 
548      * @return the names of the workspaces that this source starts with, or null if there are no such workspaces
549      * @see #setPredefinedWorkspaceNames(String[])
550      * @see #setCreatingWorkspacesAllowed(boolean)
551      */
552     public synchronized String[] getPredefinedWorkspaceNames() {
553         String[] copy = new String[predefinedWorkspaces.length];
554         System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length);
555         return copy;
556     }
557 
558     /**
559      * Sets the names of the workspaces that are available when this source is created.
560      * 
561      * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no
562      *        such workspaces
563      * @see #setCreatingWorkspacesAllowed(boolean)
564      * @see #getPredefinedWorkspaceNames()
565      */
566     public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) {
567         if (predefinedWorkspaceNames != null && predefinedWorkspaceNames.length == 1) {
568             predefinedWorkspaceNames = predefinedWorkspaceNames[0].split("\\s*,\\s*");
569         }
570         this.predefinedWorkspaces = predefinedWorkspaceNames != null ? predefinedWorkspaceNames : new String[] {};
571     }
572 
573     /**
574      * Get whether this source allows workspaces to be created dynamically.
575      * 
576      * @return true if this source allows workspaces to be created by clients, or false if the
577      *         {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
578      * @see #setPredefinedWorkspaceNames(String[])
579      * @see #getPredefinedWorkspaceNames()
580      * @see #setCreatingWorkspacesAllowed(boolean)
581      */
582     @Description( i18n = JpaConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyDescription" )
583     @Label( i18n = JpaConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyLabel" )
584     @Category( i18n = JpaConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyCategory" )
585     public boolean isCreatingWorkspacesAllowed() {
586         return capabilities.supportsCreatingWorkspaces();
587     }
588 
589     /**
590      * Set whether this source allows workspaces to be created dynamically.
591      * 
592      * @param allowWorkspaceCreation true if this source allows workspaces to be created by clients, or false if the
593      *        {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
594      * @see #setPredefinedWorkspaceNames(String[])
595      * @see #getPredefinedWorkspaceNames()
596      * @see #isCreatingWorkspacesAllowed()
597      */
598     public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) {
599         capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), capabilities.supportsUpdates(),
600                                                         capabilities.supportsEvents(), allowWorkspaceCreation,
601                                                         capabilities.supportsReferences());
602     }
603 
604     /**
605      * @return dialect
606      */
607     public String getDialect() {
608         return dialect;
609     }
610 
611     /**
612      * @param dialect Sets dialect to the specified value.
613      */
614     public synchronized void setDialect( String dialect ) {
615         if (dialect != null && dialect.trim().length() == 0) dialect = null;
616         this.dialect = dialect;
617     }
618 
619     /**
620      * @return dataSourceJndiName
621      */
622     public String getDataSourceJndiName() {
623         return dataSourceJndiName;
624     }
625 
626     /**
627      * @param dataSourceJndiName Sets dataSourceJndiName to the specified value.
628      */
629     public void setDataSourceJndiName( String dataSourceJndiName ) {
630         if (dataSourceJndiName != null && dataSourceJndiName.trim().length() == 0) dataSourceJndiName = null;
631         this.dataSourceJndiName = dataSourceJndiName;
632     }
633 
634     /**
635      * @return driverClassName
636      */
637     public String getDriverClassName() {
638         return driverClassName;
639     }
640 
641     /**
642      * @param driverClassName Sets driverClassName to the specified value.
643      */
644     public synchronized void setDriverClassName( String driverClassName ) {
645         if (driverClassName != null && driverClassName.trim().length() == 0) driverClassName = null;
646         this.driverClassName = driverClassName;
647     }
648 
649     /**
650      * @return driverClassloaderName
651      */
652     public String getDriverClassloaderName() {
653         return driverClassloaderName;
654     }
655 
656     /**
657      * @param driverClassloaderName Sets driverClassloaderName to the specified value.
658      */
659     public void setDriverClassloaderName( String driverClassloaderName ) {
660         if (driverClassloaderName != null && driverClassloaderName.trim().length() == 0) driverClassloaderName = null;
661         this.driverClassloaderName = driverClassloaderName;
662     }
663 
664     /**
665      * @return username
666      */
667     public String getUsername() {
668         return username;
669     }
670 
671     /**
672      * @param username Sets username to the specified value.
673      */
674     public synchronized void setUsername( String username ) {
675         this.username = username;
676     }
677 
678     /**
679      * @return password
680      */
681     public String getPassword() {
682         return password;
683     }
684 
685     /**
686      * @param password Sets password to the specified value.
687      */
688     public synchronized void setPassword( String password ) {
689         this.password = password;
690     }
691 
692     /**
693      * @return url
694      */
695     public String getUrl() {
696         return url;
697     }
698 
699     /**
700      * @param url Sets url to the specified value.
701      */
702     public synchronized void setUrl( String url ) {
703         if (url != null && url.trim().length() == 0) url = null;
704         this.url = url;
705     }
706 
707     /**
708      * @return maximumConnectionsInPool
709      */
710     public int getMaximumConnectionsInPool() {
711         return maximumConnectionsInPool;
712     }
713 
714     /**
715      * @param maximumConnectionsInPool Sets maximumConnectionsInPool to the specified value.
716      */
717     public synchronized void setMaximumConnectionsInPool( int maximumConnectionsInPool ) {
718         if (maximumConnectionsInPool < 0) maximumConnectionsInPool = DEFAULT_MAXIMUM_CONNECTIONS_IN_POOL;
719         this.maximumConnectionsInPool = maximumConnectionsInPool;
720     }
721 
722     /**
723      * @return minimumConnectionsInPool
724      */
725     public int getMinimumConnectionsInPool() {
726         return minimumConnectionsInPool;
727     }
728 
729     /**
730      * @param minimumConnectionsInPool Sets minimumConnectionsInPool to the specified value.
731      */
732     public synchronized void setMinimumConnectionsInPool( int minimumConnectionsInPool ) {
733         if (minimumConnectionsInPool < 0) minimumConnectionsInPool = DEFAULT_MINIMUM_CONNECTIONS_IN_POOL;
734         this.minimumConnectionsInPool = minimumConnectionsInPool;
735     }
736 
737     /**
738      * @return maximumConnectionIdleTimeInSeconds
739      */
740     public int getMaximumConnectionIdleTimeInSeconds() {
741         return maximumConnectionIdleTimeInSeconds;
742     }
743 
744     /**
745      * @param maximumConnectionIdleTimeInSeconds Sets maximumConnectionIdleTimeInSeconds to the specified value.
746      */
747     public synchronized void setMaximumConnectionIdleTimeInSeconds( int maximumConnectionIdleTimeInSeconds ) {
748         if (maximumConnectionIdleTimeInSeconds < 0) maximumConnectionIdleTimeInSeconds = DEFAULT_MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS;
749         this.maximumConnectionIdleTimeInSeconds = maximumConnectionIdleTimeInSeconds;
750     }
751 
752     /**
753      * @return maximumSizeOfStatementCache
754      */
755     public int getMaximumSizeOfStatementCache() {
756         return maximumSizeOfStatementCache;
757     }
758 
759     /**
760      * @param maximumSizeOfStatementCache Sets maximumSizeOfStatementCache to the specified value.
761      */
762     public synchronized void setMaximumSizeOfStatementCache( int maximumSizeOfStatementCache ) {
763         if (maximumSizeOfStatementCache < 0) maximumSizeOfStatementCache = DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE;
764         this.maximumSizeOfStatementCache = maximumSizeOfStatementCache;
765     }
766 
767     /**
768      * @return numberOfConnectionsToAcquireAsNeeded
769      */
770     public int getNumberOfConnectionsToAcquireAsNeeded() {
771         return numberOfConnectionsToAcquireAsNeeded;
772     }
773 
774     /**
775      * @param numberOfConnectionsToAcquireAsNeeded Sets numberOfConnectionsToAcquireAsNeeded to the specified value.
776      */
777     public synchronized void setNumberOfConnectionsToAcquireAsNeeded( int numberOfConnectionsToAcquireAsNeeded ) {
778         if (numberOfConnectionsToAcquireAsNeeded < 0) numberOfConnectionsToAcquireAsNeeded = DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED;
779         this.numberOfConnectionsToAcquireAsNeeded = numberOfConnectionsToAcquireAsNeeded;
780     }
781 
782     /**
783      * @return idleTimeInSecondsBeforeTestingConnections
784      */
785     public int getIdleTimeInSecondsBeforeTestingConnections() {
786         return idleTimeInSecondsBeforeTestingConnections;
787     }
788 
789     /**
790      * @param idleTimeInSecondsBeforeTestingConnections Sets idleTimeInSecondsBeforeTestingConnections to the specified value.
791      */
792     public synchronized void setIdleTimeInSecondsBeforeTestingConnections( int idleTimeInSecondsBeforeTestingConnections ) {
793         if (idleTimeInSecondsBeforeTestingConnections < 0) idleTimeInSecondsBeforeTestingConnections = DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS;
794         this.idleTimeInSecondsBeforeTestingConnections = idleTimeInSecondsBeforeTestingConnections;
795     }
796 
797     /**
798      * Get the {@link DataSource} object that this source is to use.
799      * 
800      * @return the data source; may be null if no data source has been set or found in JNDI
801      * @see #setDataSource(DataSource)
802      * @see #setDataSourceJndiName(String)
803      */
804     /*package*/DataSource getDataSource() {
805         return dataSource;
806     }
807 
808     /**
809      * Set the {@link DataSource} instance that this source should use.
810      * 
811      * @param dataSource the data source; may be null
812      * @see #getDataSource()
813      * @see #setDataSourceJndiName(String)
814      */
815     /*package*/synchronized void setDataSource( DataSource dataSource ) {
816         this.dataSource = dataSource;
817     }
818 
819     /**
820      * Get the model that will be used. This may be null if not yet connected, but after connections will reflect the type of
821      * model that is being used in the store.
822      * 
823      * @return the name of the model
824      */
825     public String getModel() {
826         return modelName;
827     }
828 
829     /**
830      * Set the model that should be used for this store. If the store already has a model, specifying a different value has no
831      * effect, since the store's model will not be changed. After connection, this value will reflect the actual store value.
832      * 
833      * @param modelName the name of the model that should be used for new stores, or null if the default should be used
834      */
835     public synchronized void setModel( String modelName ) {
836         if (modelName != null) {
837             modelName = modelName.trim();
838             if (modelName.length() == 0) modelName = null;
839         }
840         if (modelName == null) {
841             model = null;
842             return;
843         }
844         Model model = Models.getModel(modelName);
845         if (model == null) {
846             StringBuilder sb = new StringBuilder();
847             boolean first = true;
848             for (Model existing : Models.ALL) {
849                 if (!first) {
850                     first = false;
851                     sb.append(", ");
852                 }
853                 sb.append('"').append(existing.getName()).append('"');
854             }
855             String modelNames = sb.toString();
856             throw new IllegalArgumentException(JpaConnectorI18n.unknownModelName.text(model, modelNames));
857         }
858         this.model = model;
859         this.modelName = modelName;
860     }
861 
862     /**
863      * @return largeValueSizeInBytes
864      */
865     public long getLargeValueSizeInBytes() {
866         return largeValueSizeInBytes;
867     }
868 
869     /**
870      * @param largeValueSizeInBytes Sets largeValueSizeInBytes to the specified value.
871      */
872     public void setLargeValueSizeInBytes( long largeValueSizeInBytes ) {
873         if (largeValueSizeInBytes < 0) largeValueSizeInBytes = DEFAULT_LARGE_VALUE_SIZE_IN_BYTES;
874         this.largeValueSizeInBytes = largeValueSizeInBytes;
875     }
876 
877     /**
878      * @return compressData
879      */
880     public boolean isCompressData() {
881         return compressData;
882     }
883 
884     /**
885      * @param compressData Sets compressData to the specified value.
886      */
887     public void setCompressData( boolean compressData ) {
888         this.compressData = compressData;
889     }
890 
891     /**
892      * @return referentialIntegrityEnforced
893      */
894     public boolean isReferentialIntegrityEnforced() {
895         return referentialIntegrityEnforced;
896     }
897 
898     /**
899      * @param referentialIntegrityEnforced Sets referentialIntegrityEnforced to the specified value.
900      */
901     public void setReferentialIntegrityEnforced( boolean referentialIntegrityEnforced ) {
902         this.referentialIntegrityEnforced = referentialIntegrityEnforced;
903     }
904 
905     /**
906      * {@inheritDoc}
907      * 
908      * @see org.modeshape.graph.connector.RepositorySource#initialize(org.modeshape.graph.connector.RepositoryContext)
909      */
910     public void initialize( RepositoryContext context ) throws RepositorySourceException {
911         this.repositoryContext = context;
912     }
913 
914     /**
915      * {@inheritDoc}
916      * 
917      * @see javax.naming.Referenceable#getReference()
918      */
919     public Reference getReference() {
920         String className = getClass().getName();
921         String factoryClassName = this.getClass().getName();
922         Reference ref = new Reference(className, factoryClassName, null);
923 
924         ref.add(new StringRefAddr(SOURCE_NAME, getName()));
925         ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid()));
926         ref.add(new StringRefAddr(DATA_SOURCE_JNDI_NAME, getDataSourceJndiName()));
927         ref.add(new StringRefAddr(DIALECT, getDialect()));
928         ref.add(new StringRefAddr(USERNAME, getUsername()));
929         ref.add(new StringRefAddr(PASSWORD, getPassword()));
930         ref.add(new StringRefAddr(URL, getUrl()));
931         ref.add(new StringRefAddr(DRIVER_CLASS_NAME, getDriverClassName()));
932         ref.add(new StringRefAddr(DRIVER_CLASSLOADER_NAME, getDriverClassloaderName()));
933         ref.add(new StringRefAddr(MAXIMUM_CONNECTIONS_IN_POOL, Integer.toString(getMaximumConnectionsInPool())));
934         ref.add(new StringRefAddr(MINIMUM_CONNECTIONS_IN_POOL, Integer.toString(getMinimumConnectionsInPool())));
935         ref.add(new StringRefAddr(MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS,
936                                   Integer.toString(getMaximumConnectionIdleTimeInSeconds())));
937         ref.add(new StringRefAddr(MAXIMUM_SIZE_OF_STATEMENT_CACHE, Integer.toString(getMaximumSizeOfStatementCache())));
938         ref.add(new StringRefAddr(NUMBER_OF_CONNECTIONS_TO_BE_ACQUIRED_AS_NEEDED,
939                                   Integer.toString(getNumberOfConnectionsToAcquireAsNeeded())));
940         ref.add(new StringRefAddr(IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS,
941                                   Integer.toString(getIdleTimeInSecondsBeforeTestingConnections())));
942         ref.add(new StringRefAddr(CACHE_TIME_TO_LIVE_IN_MILLISECONDS, Integer.toString(getCacheTimeToLiveInMilliseconds())));
943         ref.add(new StringRefAddr(LARGE_VALUE_SIZE_IN_BYTES, Long.toString(getLargeValueSizeInBytes())));
944         ref.add(new StringRefAddr(COMPRESS_DATA, Boolean.toString(isCompressData())));
945         ref.add(new StringRefAddr(ENFORCE_REFERENTIAL_INTEGRITY, Boolean.toString(isReferentialIntegrityEnforced())));
946         ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName()));
947         ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
948         ref.add(new StringRefAddr(AUTO_GENERATE_SCHEMA, getAutoGenerateSchema()));
949         String[] workspaceNames = getPredefinedWorkspaceNames();
950         if (workspaceNames != null && workspaceNames.length != 0) {
951             ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames)));
952         }
953         if (getModel() != null) {
954             ref.add(new StringRefAddr(MODEL_NAME, getModel()));
955         }
956         ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
957         return ref;
958     }
959 
960     /**
961      * Returns the current repository context for the source, as set with a call to {@link #initialize(RepositoryContext)}.
962      * 
963      * @return the current repository context for the source
964      */
965     public RepositoryContext getRepositoryContext() {
966         return repositoryContext;
967     }
968 
969     /**
970      * {@inheritDoc}
971      */
972     public Object getObjectInstance( Object obj,
973                                      javax.naming.Name name,
974                                      Context nameCtx,
975                                      Hashtable<?, ?> environment ) throws Exception {
976         if (obj instanceof Reference) {
977             Map<String, String> values = new HashMap<String, String>();
978             Reference ref = (Reference)obj;
979             Enumeration<?> en = ref.getAll();
980             while (en.hasMoreElements()) {
981                 RefAddr subref = (RefAddr)en.nextElement();
982                 if (subref instanceof StringRefAddr) {
983                     String key = subref.getType();
984                     Object value = subref.getContent();
985                     if (value != null) values.put(key, value.toString());
986                 }
987             }
988             String sourceName = values.get(SOURCE_NAME);
989             String rootNodeUuid = values.get(ROOT_NODE_UUID);
990             String dataSourceJndiName = values.get(DATA_SOURCE_JNDI_NAME);
991             String dialect = values.get(DIALECT);
992             String username = values.get(USERNAME);
993             String password = values.get(PASSWORD);
994             String url = values.get(URL);
995             String driverClassName = values.get(DRIVER_CLASS_NAME);
996             String driverClassloaderName = values.get(DRIVER_CLASSLOADER_NAME);
997             String maxConnectionsInPool = values.get(MAXIMUM_CONNECTIONS_IN_POOL);
998             String minConnectionsInPool = values.get(MINIMUM_CONNECTIONS_IN_POOL);
999             String maxConnectionIdleTimeInSec = values.get(MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS);
1000             String maxSizeOfStatementCache = values.get(MAXIMUM_SIZE_OF_STATEMENT_CACHE);
1001             String acquisitionIncrement = values.get(NUMBER_OF_CONNECTIONS_TO_BE_ACQUIRED_AS_NEEDED);
1002             String idleTimeInSeconds = values.get(IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS);
1003             String cacheTtlInMillis = values.get(CACHE_TIME_TO_LIVE_IN_MILLISECONDS);
1004             String modelName = values.get(MODEL_NAME);
1005             String retryLimit = values.get(RETRY_LIMIT);
1006             String largeModelSize = values.get(LARGE_VALUE_SIZE_IN_BYTES);
1007             String compressData = values.get(COMPRESS_DATA);
1008             String refIntegrity = values.get(ENFORCE_REFERENTIAL_INTEGRITY);
1009             String defaultWorkspace = values.get(DEFAULT_WORKSPACE);
1010             String createWorkspaces = values.get(ALLOW_CREATING_WORKSPACES);
1011             String autoGenerateSchema = values.get(AUTO_GENERATE_SCHEMA);
1012 
1013             String combinedWorkspaceNames = values.get(PREDEFINED_WORKSPACE_NAMES);
1014             String[] workspaceNames = null;
1015             if (combinedWorkspaceNames != null) {
1016                 List<String> paths = StringUtil.splitLines(combinedWorkspaceNames);
1017                 workspaceNames = paths.toArray(new String[paths.size()]);
1018             }
1019 
1020             // Create the source instance ...
1021             JpaSource source = new JpaSource();
1022             if (sourceName != null) source.setName(sourceName);
1023             if (rootNodeUuid != null) source.setRootNodeUuid(rootNodeUuid);
1024             if (dataSourceJndiName != null) source.setDataSourceJndiName(dataSourceJndiName);
1025             if (dialect != null) source.setDialect(dialect);
1026             if (username != null) source.setUsername(username);
1027             if (password != null) source.setPassword(password);
1028             if (url != null) source.setUrl(url);
1029             if (driverClassName != null) source.setDriverClassName(driverClassName);
1030             if (driverClassloaderName != null) source.setDriverClassloaderName(driverClassloaderName);
1031             if (maxConnectionsInPool != null) source.setMaximumConnectionsInPool(Integer.parseInt(maxConnectionsInPool));
1032             if (minConnectionsInPool != null) source.setMinimumConnectionsInPool(Integer.parseInt(minConnectionsInPool));
1033             if (maxConnectionIdleTimeInSec != null) source.setMaximumConnectionIdleTimeInSeconds(Integer.parseInt(maxConnectionIdleTimeInSec));
1034             if (maxSizeOfStatementCache != null) source.setMaximumSizeOfStatementCache(Integer.parseInt(maxSizeOfStatementCache));
1035             if (acquisitionIncrement != null) source.setNumberOfConnectionsToAcquireAsNeeded(Integer.parseInt(acquisitionIncrement));
1036             if (idleTimeInSeconds != null) source.setIdleTimeInSecondsBeforeTestingConnections(Integer.parseInt(idleTimeInSeconds));
1037             if (cacheTtlInMillis != null) source.setCacheTimeToLiveInMilliseconds(Integer.parseInt(cacheTtlInMillis));
1038             if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
1039             if (modelName != null) source.setModel(modelName);
1040             if (largeModelSize != null) source.setLargeValueSizeInBytes(Long.parseLong(largeModelSize));
1041             if (compressData != null) source.setCompressData(Boolean.parseBoolean(compressData));
1042             if (refIntegrity != null) source.setReferentialIntegrityEnforced(Boolean.parseBoolean(refIntegrity));
1043             if (defaultWorkspace != null) source.setDefaultWorkspaceName(defaultWorkspace);
1044             if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
1045             if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
1046             if (autoGenerateSchema != null) source.setAutoGenerateSchema(autoGenerateSchema);
1047             return source;
1048         }
1049         return null;
1050     }
1051 
1052     /**
1053      * {@inheritDoc}
1054      * 
1055      * @see org.modeshape.graph.connector.RepositorySource#getConnection()
1056      */
1057     public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
1058         if (this.name == null || this.name.trim().length() == 0) {
1059             throw new RepositorySourceException(JpaConnectorI18n.repositorySourceMustHaveName.text());
1060         }
1061         assert rootNodeUuid != null;
1062         assert rootUuid != null;
1063         if (entityManagers == null) {
1064             // Create the JPA EntityManagerFactory by programmatically configuring Hibernate Entity Manager ...
1065             Ejb3Configuration configurator = new Ejb3Configuration();
1066 
1067             // Configure the entity classes ...
1068             configurator.addAnnotatedClass(StoreOptionEntity.class);
1069 
1070             // Configure additional properties, which may be overridden by subclasses ...
1071             configure(configurator);
1072 
1073             // Now set the mandatory information, overwriting anything that the subclasses may have tried ...
1074             if (this.dataSource == null && this.dataSourceJndiName != null) {
1075                 // Try to load the DataSource from JNDI ...
1076                 try {
1077                     Context context = new InitialContext();
1078                     dataSource = (DataSource)context.lookup(this.dataSourceJndiName);
1079                 } catch (Throwable t) {
1080                     Logger.getLogger(getClass())
1081                           .error(t, JpaConnectorI18n.errorFindingDataSourceInJndi, name, dataSourceJndiName);
1082                 }
1083             }
1084 
1085             if (this.dataSource != null) {
1086                 // Set the data source ...
1087                 configurator.setDataSource(this.dataSource);
1088             } else {
1089                 // Set the context class loader, so that the driver could be found ...
1090                 if (this.repositoryContext != null && this.driverClassloaderName != null) {
1091                     try {
1092                         ExecutionContext context = this.repositoryContext.getExecutionContext();
1093                         ClassLoader loader = context.getClassLoader(this.driverClassloaderName);
1094                         if (loader != null) {
1095                             Thread.currentThread().setContextClassLoader(loader);
1096                         }
1097                     } catch (Throwable t) {
1098                         I18n msg = JpaConnectorI18n.errorSettingContextClassLoader;
1099                         Logger.getLogger(getClass()).error(t, msg, name, driverClassloaderName);
1100                     }
1101                 }
1102                 // Set the connection properties ...
1103                 setProperty(configurator, "hibernate.dialect", this.dialect);
1104                 setProperty(configurator, "hibernate.connection.driver_class", this.driverClassName);
1105                 setProperty(configurator, "hibernate.connection.username", this.username);
1106                 setProperty(configurator, "hibernate.connection.password", this.password);
1107                 setProperty(configurator, "hibernate.connection.url", this.url);
1108                 setProperty(configurator, "hibernate.connection.max_fetch_depth", DEFAULT_MAXIMUM_FETCH_DEPTH);
1109                 setProperty(configurator, "hibernate.connection.pool_size", 0); // don't use the built-in pool
1110                 setProperty(configurator, "hibernate.show_sql", String.valueOf(this.showSql));
1111             }
1112 
1113             Logger logger = getLogger();
1114             if (logger.isDebugEnabled()) {
1115                 logger.debug("Properties for Hibernate configuration used for ModeShape JPA Source {0}:", getName());
1116                 Properties props = configurator.getProperties();
1117                 for (Map.Entry<Object, Object> entry : props.entrySet()) {
1118                     String propName = entry.getKey().toString();
1119                     if (propName.startsWith("hibernate")) {
1120                         logger.debug("  {0} = {1}", propName, entry.getValue());
1121                     }
1122                 }
1123             }
1124 
1125             EntityManagerFactory entityManagerFactory = configurator.buildEntityManagerFactory();
1126             try {
1127                 // Establish a connection and obtain the store options...
1128                 EntityManager entityManager = entityManagerFactory.createEntityManager();
1129                 try {
1130 
1131                     // Find and update/set the root node's UUID ...
1132                     StoreOptions options = new StoreOptions(entityManager);
1133                     UUID actualUuid = options.getRootNodeUuid();
1134                     if (actualUuid != null) {
1135                         this.setRootNodeUuid(actualUuid.toString());
1136                     } else {
1137                         options.setRootNodeUuid(this.rootUuid);
1138                     }
1139 
1140                     // Find or set the type of model that will be used.
1141                     String actualModelName = options.getModelName();
1142                     if (actualModelName == null) {
1143                         // This is a new store, so set to the specified model ...
1144                         if (model == null) setModel(Models.DEFAULT.getName());
1145                         assert model != null;
1146                         options.setModelName(model);
1147                     } else {
1148                         // Set the model to the what's listed in the database ...
1149                         try {
1150                             setModel(actualModelName);
1151                         } catch (Throwable e) {
1152                             // The actual model name doesn't match what's available in the software ...
1153                             String msg = JpaConnectorI18n.existingStoreSpecifiesUnknownModel.text(name, actualModelName);
1154                             throw new RepositorySourceException(msg);
1155                         }
1156                     }
1157                 } finally {
1158                     entityManager.close();
1159                 }
1160             } finally {
1161                 entityManagerFactory.close();
1162             }
1163 
1164             // The model has not yet configured itself, so do that now ...
1165             model.configure(configurator);
1166 
1167             // Now, create another entity manager with the classes from the correct model and without changing the schema...
1168             entityManagers = new EntityManagers(configurator);
1169         }
1170 
1171         return model.createConnection(this);
1172     }
1173 
1174     /**
1175      * {@inheritDoc}
1176      * 
1177      * @see org.modeshape.graph.connector.RepositorySource#close()
1178      */
1179     public synchronized void close() {
1180         if (entityManagers != null) {
1181             try {
1182                 // Close this object; existing connections will continue to work, and the last connection closed
1183                 // will actually shut the lights off...
1184                 entityManagers.close();
1185             } finally {
1186                 entityManagers = null;
1187             }
1188         }
1189     }
1190 
1191     /**
1192      * Set up the JPA configuration using Hibernate, except for the entity classes (which will already be configured when this
1193      * method is called) and the data source or connection information (which will be set after this method returns). Subclasses
1194      * may override this method to customize the configuration.
1195      * <p>
1196      * This method sets up the C3P0 connection pooling, the cache provider, and some DDL options.
1197      * </p>
1198      * 
1199      * @param configuration the Hibernate configuration; never null
1200      */
1201     protected void configure( Ejb3Configuration configuration ) {
1202         // Set the connection pooling properties (to use C3P0) ...
1203         setProperty(configuration, "hibernate.connection.provider_class", "org.hibernate.connection.C3P0ConnectionProvider");
1204         setProperty(configuration, "hibernate.c3p0.max_size", this.maximumConnectionsInPool);
1205         setProperty(configuration, "hibernate.c3p0.min_size", this.minimumConnectionsInPool);
1206         setProperty(configuration, "hibernate.c3p0.timeout", this.maximumConnectionIdleTimeInSeconds);
1207         setProperty(configuration, "hibernate.c3p0.max_statements", this.maximumSizeOfStatementCache);
1208         setProperty(configuration, "hibernate.c3p0.idle_test_period", this.idleTimeInSecondsBeforeTestingConnections);
1209         setProperty(configuration, "hibernate.c3p0.acquire_increment", this.numberOfConnectionsToAcquireAsNeeded);
1210         setProperty(configuration, "hibernate.c3p0.validate", "false");
1211 
1212         // Disable the second-level cache ...
1213         setProperty(configuration, "hibernate.cache.provider_class", "org.hibernate.cache.NoCacheProvider");
1214 
1215         // Set up the schema and DDL options ...
1216         // setProperty(configuration, "hibernate.show_sql", "true"); // writes all SQL statements to console
1217         setProperty(configuration, "hibernate.format_sql", "true");
1218         setProperty(configuration, "hibernate.use_sql_comments", "true");
1219         setProperty(configuration, "hibernate.hbm2ddl.auto", this.autoGenerateSchema);
1220     }
1221 
1222     protected void setProperty( Ejb3Configuration configurator,
1223                                 String propertyName,
1224                                 String propertyValue ) {
1225         assert configurator != null;
1226         assert propertyName != null;
1227         assert propertyName.trim().length() != 0;
1228         if (propertyValue != null) {
1229             configurator.setProperty(propertyName, propertyValue.trim());
1230         }
1231     }
1232 
1233     protected void setProperty( Ejb3Configuration configurator,
1234                                 String propertyName,
1235                                 int propertyValue ) {
1236         assert configurator != null;
1237         assert propertyName != null;
1238         assert propertyName.trim().length() != 0;
1239         configurator.setProperty(propertyName, Integer.toString(propertyValue));
1240     }
1241 
1242     @Immutable
1243     /*package*/class JpaCachePolicy implements CachePolicy {
1244         private static final long serialVersionUID = 1L;
1245         private final int ttl;
1246 
1247         /*package*/JpaCachePolicy( int ttl ) {
1248             this.ttl = ttl;
1249         }
1250 
1251         public long getTimeToLive() {
1252             return ttl;
1253         }
1254 
1255     }
1256 
1257 }