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 }