001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.connector.store.jpa;
025
026 import java.util.ArrayList;
027 import java.util.Arrays;
028 import java.util.Collection;
029 import java.util.Collections;
030 import java.util.Enumeration;
031 import java.util.HashMap;
032 import java.util.Hashtable;
033 import java.util.List;
034 import java.util.Map;
035 import java.util.UUID;
036 import javax.naming.Context;
037 import javax.naming.InitialContext;
038 import javax.naming.RefAddr;
039 import javax.naming.Reference;
040 import javax.naming.StringRefAddr;
041 import javax.naming.spi.ObjectFactory;
042 import javax.persistence.EntityManager;
043 import javax.persistence.EntityManagerFactory;
044 import javax.sql.DataSource;
045 import net.jcip.annotations.Immutable;
046 import org.hibernate.ejb.Ejb3Configuration;
047 import org.jboss.dna.common.i18n.I18n;
048 import org.jboss.dna.common.util.CheckArg;
049 import org.jboss.dna.common.util.Logger;
050 import org.jboss.dna.common.util.StringUtil;
051 import org.jboss.dna.connector.store.jpa.model.basic.BasicModel;
052 import org.jboss.dna.connector.store.jpa.util.StoreOptionEntity;
053 import org.jboss.dna.connector.store.jpa.util.StoreOptions;
054 import org.jboss.dna.graph.ExecutionContext;
055 import org.jboss.dna.graph.cache.CachePolicy;
056 import org.jboss.dna.graph.connector.RepositoryConnection;
057 import org.jboss.dna.graph.connector.RepositoryContext;
058 import org.jboss.dna.graph.connector.RepositorySource;
059 import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
060 import org.jboss.dna.graph.connector.RepositorySourceException;
061
062 /**
063 * The {@link RepositorySource} for the connector that stores content in a (custom) relational database. This connector uses Java
064 * Persistence API as the interface to the database, with Hibernate as the JPA implementation. (Note that some Hibernate-specific
065 * features are used.)
066 *
067 * @author Randall Hauch
068 */
069 public class JpaSource implements RepositorySource, ObjectFactory {
070
071 /**
072 * This source is capable of using different database schemas
073 *
074 * @author Randall Hauch
075 */
076 public static class Models {
077 public static final Model BASIC = new BasicModel();
078 private static final Model[] ALL_ARRAY = new Model[] {BASIC};
079 private static final List<Model> MODIFIABLE_MODELS = new ArrayList<Model>(Arrays.asList(ALL_ARRAY));
080 public static final Collection<Model> ALL = Collections.unmodifiableCollection(MODIFIABLE_MODELS);
081 public static final Model DEFAULT = BASIC;
082
083 public static boolean addModel( Model model ) {
084 CheckArg.isNotNull(model, "modelName");
085 for (Model existing : MODIFIABLE_MODELS) {
086 if (existing.getName().equals(model.getName())) return false;
087 }
088 return MODIFIABLE_MODELS.add(model);
089 }
090
091 public static Model getModel( String name ) {
092 CheckArg.isNotEmpty(name, "name");
093 name = name.trim();
094 for (Model existing : ALL) {
095 if (existing.getName().equals(name)) return existing;
096 }
097 return null;
098 }
099 }
100
101 protected static final String SOURCE_NAME = "sourceName";
102 protected static final String ROOT_NODE_UUID = "rootNodeUuid";
103 protected static final String DATA_SOURCE_JNDI_NAME = "dataSourceJndiName";
104 protected static final String DIALECT = "dialect";
105 protected static final String USERNAME = "username";
106 protected static final String PASSWORD = "password";
107 protected static final String URL = "url";
108 protected static final String DRIVER_CLASS_NAME = "driverClassName";
109 protected static final String DRIVER_CLASSLOADER_NAME = "driverClassloaderName";
110 protected static final String MAXIMUM_CONNECTIONS_IN_POOL = "maximumConnectionsInPool";
111 protected static final String MINIMUM_CONNECTIONS_IN_POOL = "minimumConnectionsInPool";
112 protected static final String MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS = "maximumConnectionIdleTimeInSeconds";
113 protected static final String MAXIMUM_SIZE_OF_STATEMENT_CACHE = "maximumSizeOfStatementCache";
114 protected static final String NUMBER_OF_CONNECTIONS_TO_BE_ACQUIRED_AS_NEEDED = "numberOfConnectionsToBeAcquiredAsNeeded";
115 protected static final String IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS = "idleTimeInSecondsBeforeTestingConnections";
116 protected static final String CACHE_TIME_TO_LIVE_IN_MILLISECONDS = "cacheTimeToLiveInMilliseconds";
117 protected static final String RETRY_LIMIT = "retryLimit";
118 protected static final String MODEL_NAME = "modelName";
119 protected static final String LARGE_VALUE_SIZE_IN_BYTES = "largeValueSizeInBytes";
120 protected static final String COMPRESS_DATA = "compressData";
121 protected static final String ENFORCE_REFERENTIAL_INTEGRITY = "enforceReferentialIntegrity";
122 protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
123 protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
124 protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
125
126 /**
127 * This source supports events.
128 */
129 protected static final boolean SUPPORTS_EVENTS = true;
130 /**
131 * This source supports same-name-siblings.
132 */
133 protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = true;
134 /**
135 * This source supports creating references.
136 */
137 protected static final boolean SUPPORTS_REFERENCES = true;
138 /**
139 * This source supports udpates by default, but each instance may be configured to {@link #setSupportsUpdates(boolean) be
140 * read-only or updateable}.
141 */
142 public static final boolean DEFAULT_SUPPORTS_UPDATES = true;
143 /**
144 * This source does support creating workspaces.
145 */
146 public static final boolean DEFAULT_SUPPORTS_CREATING_WORKSPACES = true;
147
148 /**
149 * The default UUID that is used for root nodes in a store.
150 */
151 public static final String DEFAULT_ROOT_NODE_UUID = "1497b6fe-8c7e-4bbb-aaa2-24f3d4942668";
152
153 /**
154 * The initial {@link #getNameOfDefaultWorkspace() name of the default workspace} is "{@value} ", unless otherwise specified.
155 */
156 public static final String DEFAULT_NAME_OF_DEFAULT_WORKSPACE = "default";
157
158 private static final int DEFAULT_RETRY_LIMIT = 0;
159 private static final int DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS = 60 * 5; // 5 minutes
160 private static final int DEFAULT_MAXIMUM_FETCH_DEPTH = 3;
161 private static final int DEFAULT_MAXIMUM_CONNECTIONS_IN_POOL = 5;
162 private static final int DEFAULT_MINIMUM_CONNECTIONS_IN_POOL = 0;
163 private static final int DEFAULT_MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS = 60 * 10; // 10 minutes
164 private static final int DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE = 100;
165 private static final int DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED = 1;
166 private static final int DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS = 60 * 3; // 3 minutes
167 private static final int DEFAULT_LARGE_VALUE_SIZE_IN_BYTES = 2 ^ 10; // 1 kilobyte
168 private static final boolean DEFAULT_COMPRESS_DATA = true;
169 private static final boolean DEFAULT_ENFORCE_REFERENTIAL_INTEGRITY = true;
170
171 /**
172 * The first serialized version of this source.
173 */
174 private static final long serialVersionUID = 1L;
175
176 private volatile String name;
177 private volatile String dataSourceJndiName;
178 private volatile String dialect;
179 private volatile String username;
180 private volatile String password;
181 private volatile String url;
182 private volatile String driverClassName;
183 private volatile String driverClassloaderName;
184 private volatile String rootNodeUuid = DEFAULT_ROOT_NODE_UUID;
185 private volatile int maximumConnectionsInPool = DEFAULT_MAXIMUM_CONNECTIONS_IN_POOL;
186 private volatile int minimumConnectionsInPool = DEFAULT_MINIMUM_CONNECTIONS_IN_POOL;
187 private volatile int maximumConnectionIdleTimeInSeconds = DEFAULT_MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS;
188 private volatile int maximumSizeOfStatementCache = DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE;
189 private volatile int numberOfConnectionsToAcquireAsNeeded = DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED;
190 private volatile int idleTimeInSecondsBeforeTestingConnections = DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS;
191 private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
192 private volatile int cacheTimeToLiveInMilliseconds = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS * 1000;
193 private volatile long largeValueSizeInBytes = DEFAULT_LARGE_VALUE_SIZE_IN_BYTES;
194 private volatile boolean compressData = DEFAULT_COMPRESS_DATA;
195 private volatile boolean referentialIntegrityEnforced = DEFAULT_ENFORCE_REFERENTIAL_INTEGRITY;
196 private volatile String defaultWorkspace = DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
197 private volatile String[] predefinedWorkspaces = new String[] {};
198 private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(
199 SUPPORTS_SAME_NAME_SIBLINGS,
200 DEFAULT_SUPPORTS_UPDATES,
201 SUPPORTS_EVENTS,
202 DEFAULT_SUPPORTS_CREATING_WORKSPACES,
203 SUPPORTS_REFERENCES);
204 private volatile String modelName;
205 private transient Model model;
206 private transient DataSource dataSource;
207 private transient EntityManagerFactory entityManagerFactory;
208 private transient CachePolicy cachePolicy;
209 private transient RepositoryContext repositoryContext;
210 private transient UUID rootUuid = UUID.fromString(rootNodeUuid);
211
212 /**
213 * {@inheritDoc}
214 *
215 * @see org.jboss.dna.graph.connector.RepositorySource#getName()
216 */
217 public String getName() {
218 return name;
219 }
220
221 /**
222 * Set the name for the source
223 *
224 * @param name the new name for the source
225 */
226 public void setName( String name ) {
227 if (name != null) {
228 name = name.trim();
229 if (name.length() == 0) name = null;
230 }
231 this.name = name;
232 }
233
234 /**
235 * {@inheritDoc}
236 *
237 * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities()
238 */
239 public RepositorySourceCapabilities getCapabilities() {
240 return capabilities;
241 }
242
243 /**
244 * Get whether this source supports updates.
245 *
246 * @return true if this source supports updates, or false if this source only supports reading content.
247 */
248 public boolean getSupportsUpdates() {
249 return capabilities.supportsUpdates();
250 }
251
252 /**
253 * Set whether this source supports updates.
254 *
255 * @param supportsUpdates true if this source supports updating content, or false if this source only supports reading
256 * content.
257 */
258 public synchronized void setSupportsUpdates( boolean supportsUpdates ) {
259 capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), supportsUpdates,
260 capabilities.supportsEvents(), capabilities.supportsCreatingWorkspaces(),
261 capabilities.supportsReferences());
262 }
263
264 /**
265 * {@inheritDoc}
266 *
267 * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit()
268 */
269 public int getRetryLimit() {
270 return retryLimit;
271 }
272
273 /**
274 * {@inheritDoc}
275 *
276 * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int)
277 */
278 public synchronized void setRetryLimit( int limit ) {
279 if (limit < 0) limit = 0;
280 this.retryLimit = limit;
281 }
282
283 /**
284 * Get the time in milliseconds that content returned from this source may used while in the cache.
285 *
286 * @return the time to live, in milliseconds, or 0 if the time to live is not specified by this source
287 */
288 public int getCacheTimeToLiveInMilliseconds() {
289 return cacheTimeToLiveInMilliseconds;
290 }
291
292 /**
293 * Set the time in milliseconds that content returned from this source may used while in the cache.
294 *
295 * @param cacheTimeToLive the time to live, in milliseconds; 0 if the time to live is not specified by this source; or a
296 * negative number for the default value
297 */
298 public synchronized void setCacheTimeToLiveInMilliseconds( int cacheTimeToLive ) {
299 if (cacheTimeToLive < 0) cacheTimeToLive = DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS;
300 this.cacheTimeToLiveInMilliseconds = cacheTimeToLive;
301 this.cachePolicy = cacheTimeToLiveInMilliseconds > 0 ? new JpaCachePolicy(cacheTimeToLiveInMilliseconds) : null;
302 }
303
304 /**
305 * @return rootNodeUuid
306 */
307 public String getRootNodeUuid() {
308 return rootNodeUuid;
309 }
310
311 /**
312 * @param rootNodeUuid Sets rootNodeUuid to the specified value.
313 * @throws IllegalArgumentException if the string value cannot be converted to UUID
314 */
315 public void setRootNodeUuid( String rootNodeUuid ) {
316 if (rootNodeUuid != null && rootNodeUuid.trim().length() == 0) rootNodeUuid = DEFAULT_ROOT_NODE_UUID;
317 this.rootUuid = UUID.fromString(rootNodeUuid);
318 this.rootNodeUuid = rootNodeUuid;
319 }
320
321 /**
322 * Get the name of the default workspace.
323 *
324 * @return the name of the workspace that should be used by default, or null if there is no default workspace
325 */
326 public String getNameOfDefaultWorkspace() {
327 return defaultWorkspace;
328 }
329
330 /**
331 * Set the name of the workspace that should be used when clients don't specify a workspace.
332 *
333 * @param nameOfDefaultWorkspace the name of the workspace that should be used by default, or null if the
334 * {@link #DEFAULT_NAME_OF_DEFAULT_WORKSPACE default name} should be used
335 */
336 public synchronized void setNameOfDefaultWorkspace( String nameOfDefaultWorkspace ) {
337 this.defaultWorkspace = nameOfDefaultWorkspace != null ? nameOfDefaultWorkspace : DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
338 }
339
340 /**
341 * Gets the names of the workspaces that are available when this source is created.
342 *
343 * @return the names of the workspaces that this source starts with, or null if there are no such workspaces
344 * @see #setPredefinedWorkspaceNames(String[])
345 * @see #setCreatingWorkspacesAllowed(boolean)
346 */
347 public synchronized String[] getPredefinedWorkspaceNames() {
348 String[] copy = new String[predefinedWorkspaces.length];
349 System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length);
350 return copy;
351 }
352
353 /**
354 * Sets the names of the workspaces that are available when this source is created.
355 *
356 * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no
357 * such workspaces
358 * @see #setCreatingWorkspacesAllowed(boolean)
359 * @see #getPredefinedWorkspaceNames()
360 */
361 public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) {
362 this.predefinedWorkspaces = predefinedWorkspaceNames != null ? predefinedWorkspaceNames : new String[] {};
363 }
364
365 /**
366 * Get whether this source allows workspaces to be created dynamically.
367 *
368 * @return true if this source allows workspaces to be created by clients, or false if the
369 * {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
370 * @see #setPredefinedWorkspaceNames(String[])
371 * @see #getPredefinedWorkspaceNames()
372 * @see #setCreatingWorkspacesAllowed(boolean)
373 */
374 public boolean isCreatingWorkspacesAllowed() {
375 return capabilities.supportsCreatingWorkspaces();
376 }
377
378 /**
379 * Set whether this source allows workspaces to be created dynamically.
380 *
381 * @param allowWorkspaceCreation true if this source allows workspaces to be created by clients, or false if the
382 * {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
383 * @see #setPredefinedWorkspaceNames(String[])
384 * @see #getPredefinedWorkspaceNames()
385 * @see #isCreatingWorkspacesAllowed()
386 */
387 public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) {
388 capabilities = new RepositorySourceCapabilities(capabilities.supportsSameNameSiblings(), capabilities.supportsUpdates(),
389 capabilities.supportsEvents(), allowWorkspaceCreation,
390 capabilities.supportsReferences());
391 }
392
393 /**
394 * @return dialect
395 */
396 public String getDialect() {
397 return dialect;
398 }
399
400 /**
401 * @param dialect Sets dialect to the specified value.
402 */
403 public synchronized void setDialect( String dialect ) {
404 if (dialect != null && dialect.trim().length() == 0) dialect = null;
405 this.dialect = dialect;
406 }
407
408 /**
409 * @return dataSourceJndiName
410 */
411 public String getDataSourceJndiName() {
412 return dataSourceJndiName;
413 }
414
415 /**
416 * @param dataSourceJndiName Sets dataSourceJndiName to the specified value.
417 */
418 public void setDataSourceJndiName( String dataSourceJndiName ) {
419 if (dataSourceJndiName != null && dataSourceJndiName.trim().length() == 0) dataSourceJndiName = null;
420 this.dataSourceJndiName = dataSourceJndiName;
421 }
422
423 /**
424 * @return driverClassName
425 */
426 public String getDriverClassName() {
427 return driverClassName;
428 }
429
430 /**
431 * @param driverClassName Sets driverClassName to the specified value.
432 */
433 public synchronized void setDriverClassName( String driverClassName ) {
434 if (driverClassName != null && driverClassName.trim().length() == 0) driverClassName = null;
435 this.driverClassName = driverClassName;
436 }
437
438 /**
439 * @return driverClassloaderName
440 */
441 public String getDriverClassloaderName() {
442 return driverClassloaderName;
443 }
444
445 /**
446 * @param driverClassloaderName Sets driverClassloaderName to the specified value.
447 */
448 public void setDriverClassloaderName( String driverClassloaderName ) {
449 if (driverClassloaderName != null && driverClassloaderName.trim().length() == 0) driverClassloaderName = null;
450 this.driverClassloaderName = driverClassloaderName;
451 }
452
453 /**
454 * @return username
455 */
456 public String getUsername() {
457 return username;
458 }
459
460 /**
461 * @param username Sets username to the specified value.
462 */
463 public synchronized void setUsername( String username ) {
464 this.username = username;
465 }
466
467 /**
468 * @return password
469 */
470 public String getPassword() {
471 return password;
472 }
473
474 /**
475 * @param password Sets password to the specified value.
476 */
477 public synchronized void setPassword( String password ) {
478 this.password = password;
479 }
480
481 /**
482 * @return url
483 */
484 public String getUrl() {
485 return url;
486 }
487
488 /**
489 * @param url Sets url to the specified value.
490 */
491 public synchronized void setUrl( String url ) {
492 if (url != null && url.trim().length() == 0) url = null;
493 this.url = url;
494 }
495
496 /**
497 * @return maximumConnectionsInPool
498 */
499 public int getMaximumConnectionsInPool() {
500 return maximumConnectionsInPool;
501 }
502
503 /**
504 * @param maximumConnectionsInPool Sets maximumConnectionsInPool to the specified value.
505 */
506 public synchronized void setMaximumConnectionsInPool( int maximumConnectionsInPool ) {
507 if (maximumConnectionsInPool < 0) maximumConnectionsInPool = DEFAULT_MAXIMUM_CONNECTIONS_IN_POOL;
508 this.maximumConnectionsInPool = maximumConnectionsInPool;
509 }
510
511 /**
512 * @return minimumConnectionsInPool
513 */
514 public int getMinimumConnectionsInPool() {
515 return minimumConnectionsInPool;
516 }
517
518 /**
519 * @param minimumConnectionsInPool Sets minimumConnectionsInPool to the specified value.
520 */
521 public synchronized void setMinimumConnectionsInPool( int minimumConnectionsInPool ) {
522 if (minimumConnectionsInPool < 0) minimumConnectionsInPool = DEFAULT_MINIMUM_CONNECTIONS_IN_POOL;
523 this.minimumConnectionsInPool = minimumConnectionsInPool;
524 }
525
526 /**
527 * @return maximumConnectionIdleTimeInSeconds
528 */
529 public int getMaximumConnectionIdleTimeInSeconds() {
530 return maximumConnectionIdleTimeInSeconds;
531 }
532
533 /**
534 * @param maximumConnectionIdleTimeInSeconds Sets maximumConnectionIdleTimeInSeconds to the specified value.
535 */
536 public synchronized void setMaximumConnectionIdleTimeInSeconds( int maximumConnectionIdleTimeInSeconds ) {
537 if (maximumConnectionIdleTimeInSeconds < 0) maximumConnectionIdleTimeInSeconds = DEFAULT_MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS;
538 this.maximumConnectionIdleTimeInSeconds = maximumConnectionIdleTimeInSeconds;
539 }
540
541 /**
542 * @return maximumSizeOfStatementCache
543 */
544 public int getMaximumSizeOfStatementCache() {
545 return maximumSizeOfStatementCache;
546 }
547
548 /**
549 * @param maximumSizeOfStatementCache Sets maximumSizeOfStatementCache to the specified value.
550 */
551 public synchronized void setMaximumSizeOfStatementCache( int maximumSizeOfStatementCache ) {
552 if (maximumSizeOfStatementCache < 0) maximumSizeOfStatementCache = DEFAULT_MAXIMUM_NUMBER_OF_STATEMENTS_TO_CACHE;
553 this.maximumSizeOfStatementCache = maximumSizeOfStatementCache;
554 }
555
556 /**
557 * @return numberOfConnectionsToAcquireAsNeeded
558 */
559 public int getNumberOfConnectionsToAcquireAsNeeded() {
560 return numberOfConnectionsToAcquireAsNeeded;
561 }
562
563 /**
564 * @param numberOfConnectionsToAcquireAsNeeded Sets numberOfConnectionsToAcquireAsNeeded to the specified value.
565 */
566 public synchronized void setNumberOfConnectionsToAcquireAsNeeded( int numberOfConnectionsToAcquireAsNeeded ) {
567 if (numberOfConnectionsToAcquireAsNeeded < 0) numberOfConnectionsToAcquireAsNeeded = DEFAULT_NUMBER_OF_CONNECTIONS_TO_ACQUIRE_AS_NEEDED;
568 this.numberOfConnectionsToAcquireAsNeeded = numberOfConnectionsToAcquireAsNeeded;
569 }
570
571 /**
572 * @return idleTimeInSecondsBeforeTestingConnections
573 */
574 public int getIdleTimeInSecondsBeforeTestingConnections() {
575 return idleTimeInSecondsBeforeTestingConnections;
576 }
577
578 /**
579 * @param idleTimeInSecondsBeforeTestingConnections Sets idleTimeInSecondsBeforeTestingConnections to the specified value.
580 */
581 public synchronized void setIdleTimeInSecondsBeforeTestingConnections( int idleTimeInSecondsBeforeTestingConnections ) {
582 if (idleTimeInSecondsBeforeTestingConnections < 0) idleTimeInSecondsBeforeTestingConnections = DEFAULT_IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS;
583 this.idleTimeInSecondsBeforeTestingConnections = idleTimeInSecondsBeforeTestingConnections;
584 }
585
586 /**
587 * Get the {@link DataSource} object that this source is to use.
588 *
589 * @return the data source; may be null if no data source has been set or found in JNDI
590 * @see #setDataSource(DataSource)
591 * @see #setDataSourceJndiName(String)
592 */
593 /*package*/DataSource getDataSource() {
594 return dataSource;
595 }
596
597 /**
598 * Set the {@link DataSource} instance that this source should use.
599 *
600 * @param dataSource the data source; may be null
601 * @see #getDataSource()
602 * @see #setDataSourceJndiName(String)
603 */
604 /*package*/synchronized void setDataSource( DataSource dataSource ) {
605 this.dataSource = dataSource;
606 }
607
608 /**
609 * Get the model that will be used. This may be null if not yet connected, but after connections will reflect the type of
610 * model that is being used in the store.
611 *
612 * @return the name of the model
613 */
614 public String getModel() {
615 return modelName;
616 }
617
618 /**
619 * Set the model that should be used for this store. If the store already has a model, specifying a different value has no
620 * effect, since the store's model will not be changed. After connection, this value will reflect the actual store value.
621 *
622 * @param modelName the name of the model that should be used for new stores, or null if the default should be used
623 */
624 public synchronized void setModel( String modelName ) {
625 if (modelName != null) {
626 modelName = modelName.trim();
627 if (modelName.length() == 0) modelName = null;
628 }
629 if (modelName == null) {
630 model = null;
631 return;
632 }
633 Model model = Models.getModel(modelName);
634 if (model == null) {
635 StringBuilder sb = new StringBuilder();
636 boolean first = true;
637 for (Model existing : Models.ALL) {
638 if (!first) {
639 first = false;
640 sb.append(", ");
641 }
642 sb.append('"').append(existing.getName()).append('"');
643 }
644 String modelNames = sb.toString();
645 throw new IllegalArgumentException(JpaConnectorI18n.unknownModelName.text(model, modelNames));
646 }
647 this.model = model;
648 this.modelName = modelName;
649 }
650
651 /**
652 * @return largeValueSizeInBytes
653 */
654 public long getLargeValueSizeInBytes() {
655 return largeValueSizeInBytes;
656 }
657
658 /**
659 * @param largeValueSizeInBytes Sets largeValueSizeInBytes to the specified value.
660 */
661 public void setLargeValueSizeInBytes( long largeValueSizeInBytes ) {
662 if (largeValueSizeInBytes < 0) largeValueSizeInBytes = DEFAULT_LARGE_VALUE_SIZE_IN_BYTES;
663 this.largeValueSizeInBytes = largeValueSizeInBytes;
664 }
665
666 /**
667 * @return compressData
668 */
669 public boolean isCompressData() {
670 return compressData;
671 }
672
673 /**
674 * @param compressData Sets compressData to the specified value.
675 */
676 public void setCompressData( boolean compressData ) {
677 this.compressData = compressData;
678 }
679
680 /**
681 * @return referentialIntegrityEnforced
682 */
683 public boolean isReferentialIntegrityEnforced() {
684 return referentialIntegrityEnforced;
685 }
686
687 /**
688 * @param referentialIntegrityEnforced Sets referentialIntegrityEnforced to the specified value.
689 */
690 public void setReferentialIntegrityEnforced( boolean referentialIntegrityEnforced ) {
691 this.referentialIntegrityEnforced = referentialIntegrityEnforced;
692 }
693
694 /**
695 * {@inheritDoc}
696 *
697 * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext)
698 */
699 public void initialize( RepositoryContext context ) throws RepositorySourceException {
700 this.repositoryContext = context;
701 }
702
703 /**
704 * {@inheritDoc}
705 *
706 * @see javax.naming.Referenceable#getReference()
707 */
708 public Reference getReference() {
709 String className = getClass().getName();
710 String factoryClassName = this.getClass().getName();
711 Reference ref = new Reference(className, factoryClassName, null);
712
713 ref.add(new StringRefAddr(SOURCE_NAME, getName()));
714 ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid()));
715 ref.add(new StringRefAddr(DATA_SOURCE_JNDI_NAME, getDataSourceJndiName()));
716 ref.add(new StringRefAddr(DIALECT, getDialect()));
717 ref.add(new StringRefAddr(USERNAME, getUsername()));
718 ref.add(new StringRefAddr(PASSWORD, getPassword()));
719 ref.add(new StringRefAddr(URL, getUrl()));
720 ref.add(new StringRefAddr(DRIVER_CLASS_NAME, getDriverClassName()));
721 ref.add(new StringRefAddr(DRIVER_CLASSLOADER_NAME, getDriverClassloaderName()));
722 ref.add(new StringRefAddr(MAXIMUM_CONNECTIONS_IN_POOL, Integer.toString(getMaximumConnectionsInPool())));
723 ref.add(new StringRefAddr(MINIMUM_CONNECTIONS_IN_POOL, Integer.toString(getMinimumConnectionsInPool())));
724 ref.add(new StringRefAddr(MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS,
725 Integer.toString(getMaximumConnectionIdleTimeInSeconds())));
726 ref.add(new StringRefAddr(MAXIMUM_SIZE_OF_STATEMENT_CACHE, Integer.toString(getMaximumSizeOfStatementCache())));
727 ref.add(new StringRefAddr(NUMBER_OF_CONNECTIONS_TO_BE_ACQUIRED_AS_NEEDED,
728 Integer.toString(getNumberOfConnectionsToAcquireAsNeeded())));
729 ref.add(new StringRefAddr(IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS,
730 Integer.toString(getIdleTimeInSecondsBeforeTestingConnections())));
731 ref.add(new StringRefAddr(CACHE_TIME_TO_LIVE_IN_MILLISECONDS, Integer.toString(getCacheTimeToLiveInMilliseconds())));
732 ref.add(new StringRefAddr(LARGE_VALUE_SIZE_IN_BYTES, Long.toString(getLargeValueSizeInBytes())));
733 ref.add(new StringRefAddr(COMPRESS_DATA, Boolean.toString(isCompressData())));
734 ref.add(new StringRefAddr(ENFORCE_REFERENTIAL_INTEGRITY, Boolean.toString(isReferentialIntegrityEnforced())));
735 ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getNameOfDefaultWorkspace()));
736 ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
737 String[] workspaceNames = getPredefinedWorkspaceNames();
738 if (workspaceNames != null && workspaceNames.length != 0) {
739 ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames)));
740 }
741 if (getModel() != null) {
742 ref.add(new StringRefAddr(MODEL_NAME, getModel()));
743 }
744 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
745 return ref;
746 }
747
748 /**
749 * {@inheritDoc}
750 */
751 public Object getObjectInstance( Object obj,
752 javax.naming.Name name,
753 Context nameCtx,
754 Hashtable<?, ?> environment ) throws Exception {
755 if (obj instanceof Reference) {
756 Map<String, String> values = new HashMap<String, String>();
757 Reference ref = (Reference)obj;
758 Enumeration<?> en = ref.getAll();
759 while (en.hasMoreElements()) {
760 RefAddr subref = (RefAddr)en.nextElement();
761 if (subref instanceof StringRefAddr) {
762 String key = subref.getType();
763 Object value = subref.getContent();
764 if (value != null) values.put(key, value.toString());
765 }
766 }
767 String sourceName = values.get(SOURCE_NAME);
768 String rootNodeUuid = values.get(ROOT_NODE_UUID);
769 String dataSourceJndiName = values.get(DATA_SOURCE_JNDI_NAME);
770 String dialect = values.get(DIALECT);
771 String username = values.get(USERNAME);
772 String password = values.get(PASSWORD);
773 String url = values.get(URL);
774 String driverClassName = values.get(DRIVER_CLASS_NAME);
775 String driverClassloaderName = values.get(DRIVER_CLASSLOADER_NAME);
776 String maxConnectionsInPool = values.get(MAXIMUM_CONNECTIONS_IN_POOL);
777 String minConnectionsInPool = values.get(MINIMUM_CONNECTIONS_IN_POOL);
778 String maxConnectionIdleTimeInSec = values.get(MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS);
779 String maxSizeOfStatementCache = values.get(MAXIMUM_SIZE_OF_STATEMENT_CACHE);
780 String acquisitionIncrement = values.get(NUMBER_OF_CONNECTIONS_TO_BE_ACQUIRED_AS_NEEDED);
781 String idleTimeInSeconds = values.get(IDLE_TIME_IN_SECONDS_BEFORE_TESTING_CONNECTIONS);
782 String cacheTtlInMillis = values.get(CACHE_TIME_TO_LIVE_IN_MILLISECONDS);
783 String modelName = values.get(MODEL_NAME);
784 String retryLimit = values.get(RETRY_LIMIT);
785 String largeModelSize = values.get(LARGE_VALUE_SIZE_IN_BYTES);
786 String compressData = values.get(COMPRESS_DATA);
787 String refIntegrity = values.get(ENFORCE_REFERENTIAL_INTEGRITY);
788 String defaultWorkspace = values.get(DEFAULT_WORKSPACE);
789 String createWorkspaces = values.get(ALLOW_CREATING_WORKSPACES);
790
791 String combinedWorkspaceNames = values.get(PREDEFINED_WORKSPACE_NAMES);
792 String[] workspaceNames = null;
793 if (combinedWorkspaceNames != null) {
794 List<String> paths = StringUtil.splitLines(combinedWorkspaceNames);
795 workspaceNames = paths.toArray(new String[paths.size()]);
796 }
797
798 // Create the source instance ...
799 JpaSource source = new JpaSource();
800 if (sourceName != null) source.setName(sourceName);
801 if (rootNodeUuid != null) source.setRootNodeUuid(rootNodeUuid);
802 if (dataSourceJndiName != null) source.setDataSourceJndiName(dataSourceJndiName);
803 if (dialect != null) source.setDialect(dialect);
804 if (username != null) source.setUsername(username);
805 if (password != null) source.setPassword(password);
806 if (url != null) source.setUrl(url);
807 if (driverClassName != null) source.setDriverClassName(driverClassName);
808 if (driverClassloaderName != null) source.setDriverClassloaderName(driverClassloaderName);
809 if (maxConnectionsInPool != null) source.setMaximumConnectionsInPool(Integer.parseInt(maxConnectionsInPool));
810 if (minConnectionsInPool != null) source.setMinimumConnectionsInPool(Integer.parseInt(minConnectionsInPool));
811 if (maxConnectionIdleTimeInSec != null) source.setMaximumConnectionIdleTimeInSeconds(Integer.parseInt(maxConnectionIdleTimeInSec));
812 if (maxSizeOfStatementCache != null) source.setMaximumSizeOfStatementCache(Integer.parseInt(maxSizeOfStatementCache));
813 if (acquisitionIncrement != null) source.setNumberOfConnectionsToAcquireAsNeeded(Integer.parseInt(acquisitionIncrement));
814 if (idleTimeInSeconds != null) source.setIdleTimeInSecondsBeforeTestingConnections(Integer.parseInt(idleTimeInSeconds));
815 if (cacheTtlInMillis != null) source.setCacheTimeToLiveInMilliseconds(Integer.parseInt(cacheTtlInMillis));
816 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
817 if (modelName != null) source.setModel(modelName);
818 if (largeModelSize != null) source.setLargeValueSizeInBytes(Long.parseLong(largeModelSize));
819 if (compressData != null) source.setCompressData(Boolean.parseBoolean(compressData));
820 if (refIntegrity != null) source.setReferentialIntegrityEnforced(Boolean.parseBoolean(refIntegrity));
821 if (defaultWorkspace != null) source.setNameOfDefaultWorkspace(defaultWorkspace);
822 if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
823 if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
824 return source;
825 }
826 return null;
827 }
828
829 /**
830 * {@inheritDoc}
831 *
832 * @see org.jboss.dna.graph.connector.RepositorySource#getConnection()
833 */
834 public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
835 if (this.name == null || this.name.trim().length() == 0) {
836 throw new RepositorySourceException(JpaConnectorI18n.repositorySourceMustHaveName.text());
837 }
838 assert rootNodeUuid != null;
839 assert rootUuid != null;
840 EntityManager entityManager = null;
841 if (entityManagerFactory == null || !entityManagerFactory.isOpen()) {
842 // Create the JPA EntityManagerFactory by programmatically configuring Hibernate Entity Manager ...
843 Ejb3Configuration configurator = new Ejb3Configuration();
844
845 // Configure the entity classes ...
846 configurator.addAnnotatedClass(StoreOptionEntity.class);
847 if (model != null) model.configure(configurator);
848
849 // Configure additional properties, which may be overridden by subclasses ...
850 configure(configurator);
851
852 // Now set the mandatory information, overwriting anything that the subclasses may have tried ...
853 if (this.dataSource == null && this.dataSourceJndiName != null) {
854 // Try to load the DataSource from JNDI ...
855 try {
856 Context context = new InitialContext();
857 dataSource = (DataSource)context.lookup(this.dataSourceJndiName);
858 } catch (Throwable t) {
859 Logger.getLogger(getClass())
860 .error(t, JpaConnectorI18n.errorFindingDataSourceInJndi, name, dataSourceJndiName);
861 }
862 }
863
864 if (this.dataSource != null) {
865 // Set the data source ...
866 configurator.setDataSource(this.dataSource);
867 } else {
868 // Set the context class loader, so that the driver could be found ...
869 if (this.repositoryContext != null && this.driverClassloaderName != null) {
870 try {
871 ExecutionContext context = this.repositoryContext.getExecutionContext();
872 ClassLoader loader = context.getClassLoader(this.driverClassloaderName);
873 if (loader != null) {
874 Thread.currentThread().setContextClassLoader(loader);
875 }
876 } catch (Throwable t) {
877 I18n msg = JpaConnectorI18n.errorSettingContextClassLoader;
878 Logger.getLogger(getClass()).error(t, msg, name, driverClassloaderName);
879 }
880 }
881 // Set the connection properties ...
882 setProperty(configurator, "hibernate.dialect", this.dialect);
883 setProperty(configurator, "hibernate.connection.driver_class", this.driverClassName);
884 setProperty(configurator, "hibernate.connection.username", this.username);
885 setProperty(configurator, "hibernate.connection.password", this.password);
886 setProperty(configurator, "hibernate.connection.url", this.url);
887 setProperty(configurator, "hibernate.connection.max_fetch_depth", DEFAULT_MAXIMUM_FETCH_DEPTH);
888 setProperty(configurator, "hibernate.connection.pool_size", 0); // don't use the built-in pool
889 }
890
891 entityManagerFactory = configurator.buildEntityManagerFactory();
892
893 // Establish a connection and obtain the store options...
894 entityManager = entityManagerFactory.createEntityManager();
895
896 // Find and update/set the root node's UUID ...
897 StoreOptions options = new StoreOptions(entityManager);
898 UUID actualUuid = options.getRootNodeUuid();
899 if (actualUuid != null) {
900 this.setRootNodeUuid(actualUuid.toString());
901 } else {
902 options.setRootNodeUuid(this.rootUuid);
903 }
904
905 // Find or set the type of model that will be used.
906 String actualModelName = options.getModelName();
907 if (actualModelName == null) {
908 // This is a new store, so set to the specified model ...
909 if (model == null) setModel(Models.DEFAULT.getName());
910 assert model != null;
911 options.setModelName(model);
912 } else {
913 try {
914 setModel(actualModelName);
915 } catch (Throwable e) {
916 // The actual model name doesn't match what's available in the software ...
917 entityManagerFactory.close();
918 String msg = JpaConnectorI18n.existingStoreSpecifiesUnknownModel.text(name, actualModelName);
919 throw new RepositorySourceException(msg);
920 }
921 }
922 entityManagerFactory.close();
923
924 // Now, create another entity manager with the classes from the correct model
925 model.configure(configurator);
926 entityManagerFactory = configurator.buildEntityManagerFactory();
927 entityManager = entityManagerFactory.createEntityManager();
928 }
929 if (entityManager == null) {
930 entityManager = entityManagerFactory.createEntityManager();
931 }
932 return new JpaConnection(getName(), cachePolicy, entityManager, model, rootUuid, defaultWorkspace,
933 getPredefinedWorkspaceNames(), largeValueSizeInBytes, isCreatingWorkspacesAllowed(),
934 compressData, referentialIntegrityEnforced);
935 }
936
937 /**
938 * Set up the JPA configuration using Hibernate, except for the entity classes (which will already be configured when this
939 * method is called) and the data source or connection information (which will be set after this method returns). Subclasses
940 * may override this method to customize the configuration.
941 * <p>
942 * This method sets up the C3P0 connection pooling, the cache provider, and some DDL options.
943 * </p>
944 *
945 * @param configuration the Hibernate configuration; never null
946 */
947 protected void configure( Ejb3Configuration configuration ) {
948 // Set the connection pooling properties (to use C3P0) ...
949 setProperty(configuration, "hibernate.connection.provider_class", "org.hibernate.connection.C3P0ConnectionProvider");
950 setProperty(configuration, "hibernate.c3p0.max_size", this.maximumConnectionsInPool);
951 setProperty(configuration, "hibernate.c3p0.min_size", this.minimumConnectionsInPool);
952 setProperty(configuration, "hibernate.c3p0.timeout", this.idleTimeInSecondsBeforeTestingConnections);
953 setProperty(configuration, "hibernate.c3p0.max_statements", this.maximumSizeOfStatementCache);
954 setProperty(configuration, "hibernate.c3p0.idle_test_period", this.idleTimeInSecondsBeforeTestingConnections);
955 setProperty(configuration, "hibernate.c3p0.acquire_increment", this.numberOfConnectionsToAcquireAsNeeded);
956 setProperty(configuration, "hibernate.c3p0.validate", "false");
957
958 // Disable the second-level cache ...
959 setProperty(configuration, "hibernate.cache.provider_class", "org.hibernate.cache.NoCacheProvider");
960
961 // Set up the schema and DDL options ...
962 // setProperty(configuration, "hibernate.show_sql", "true"); // writes all SQL statements to console
963 setProperty(configuration, "hibernate.format_sql", "true");
964 setProperty(configuration, "hibernate.use_sql_comments", "true");
965 setProperty(configuration, "hibernate.hbm2ddl.auto", "create");
966 }
967
968 /**
969 * Close any resources held by this source. This will ensure that all connections are closed.
970 */
971 public synchronized void close() {
972 if (entityManagerFactory != null) {
973 try {
974 entityManagerFactory.close();
975 } finally {
976 entityManagerFactory = null;
977 }
978 }
979 }
980
981 protected void setProperty( Ejb3Configuration configurator,
982 String propertyName,
983 String propertyValue ) {
984 assert configurator != null;
985 assert propertyName != null;
986 assert propertyName.trim().length() != 0;
987 if (propertyValue != null) {
988 configurator.setProperty(propertyName, propertyValue.trim());
989 }
990 }
991
992 protected void setProperty( Ejb3Configuration configurator,
993 String propertyName,
994 int propertyValue ) {
995 assert configurator != null;
996 assert propertyName != null;
997 assert propertyName.trim().length() != 0;
998 configurator.setProperty(propertyName, Integer.toString(propertyValue));
999 }
1000
1001 @Immutable
1002 /*package*/class JpaCachePolicy implements CachePolicy {
1003 private static final long serialVersionUID = 1L;
1004 private final int ttl;
1005
1006 /*package*/JpaCachePolicy( int ttl ) {
1007 this.ttl = ttl;
1008 }
1009
1010 public long getTimeToLive() {
1011 return ttl;
1012 }
1013
1014 }
1015
1016 }