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 }