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