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