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.model.basic; 025 026 import java.io.ByteArrayInputStream; 027 import java.io.ByteArrayOutputStream; 028 import java.io.IOException; 029 import java.io.InputStream; 030 import java.io.ObjectInputStream; 031 import java.io.ObjectOutputStream; 032 import java.io.OutputStream; 033 import java.util.ArrayList; 034 import java.util.Arrays; 035 import java.util.Collection; 036 import java.util.Collections; 037 import java.util.HashMap; 038 import java.util.HashSet; 039 import java.util.Iterator; 040 import java.util.LinkedList; 041 import java.util.List; 042 import java.util.ListIterator; 043 import java.util.Map; 044 import java.util.Set; 045 import java.util.UUID; 046 import java.util.zip.GZIPInputStream; 047 import java.util.zip.GZIPOutputStream; 048 import javax.persistence.EntityManager; 049 import javax.persistence.EntityTransaction; 050 import javax.persistence.NoResultException; 051 import javax.persistence.Query; 052 import net.jcip.annotations.Immutable; 053 import net.jcip.annotations.NotThreadSafe; 054 import org.jboss.dna.common.util.IoUtil; 055 import org.jboss.dna.common.util.Logger; 056 import org.jboss.dna.common.util.StringUtil; 057 import org.jboss.dna.connector.store.jpa.JpaConnectorI18n; 058 import org.jboss.dna.connector.store.jpa.model.common.NamespaceEntity; 059 import org.jboss.dna.connector.store.jpa.model.common.WorkspaceEntity; 060 import org.jboss.dna.connector.store.jpa.util.Namespaces; 061 import org.jboss.dna.connector.store.jpa.util.RequestProcessorCache; 062 import org.jboss.dna.connector.store.jpa.util.Serializer; 063 import org.jboss.dna.connector.store.jpa.util.Workspaces; 064 import org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues; 065 import org.jboss.dna.graph.DnaLexicon; 066 import org.jboss.dna.graph.ExecutionContext; 067 import org.jboss.dna.graph.JcrLexicon; 068 import org.jboss.dna.graph.Location; 069 import org.jboss.dna.graph.property.Binary; 070 import org.jboss.dna.graph.property.Name; 071 import org.jboss.dna.graph.property.NameFactory; 072 import org.jboss.dna.graph.property.Path; 073 import org.jboss.dna.graph.property.PathFactory; 074 import org.jboss.dna.graph.property.PathNotFoundException; 075 import org.jboss.dna.graph.property.Property; 076 import org.jboss.dna.graph.property.PropertyType; 077 import org.jboss.dna.graph.property.Reference; 078 import org.jboss.dna.graph.property.ReferentialIntegrityException; 079 import org.jboss.dna.graph.property.UuidFactory; 080 import org.jboss.dna.graph.property.ValueFactories; 081 import org.jboss.dna.graph.property.ValueFactory; 082 import org.jboss.dna.graph.property.ValueFormatException; 083 import org.jboss.dna.graph.request.CloneWorkspaceRequest; 084 import org.jboss.dna.graph.request.CopyBranchRequest; 085 import org.jboss.dna.graph.request.CreateNodeRequest; 086 import org.jboss.dna.graph.request.CreateWorkspaceRequest; 087 import org.jboss.dna.graph.request.DeleteBranchRequest; 088 import org.jboss.dna.graph.request.DestroyWorkspaceRequest; 089 import org.jboss.dna.graph.request.GetWorkspacesRequest; 090 import org.jboss.dna.graph.request.InvalidRequestException; 091 import org.jboss.dna.graph.request.InvalidWorkspaceException; 092 import org.jboss.dna.graph.request.MoveBranchRequest; 093 import org.jboss.dna.graph.request.ReadAllChildrenRequest; 094 import org.jboss.dna.graph.request.ReadAllPropertiesRequest; 095 import org.jboss.dna.graph.request.ReadBlockOfChildrenRequest; 096 import org.jboss.dna.graph.request.ReadBranchRequest; 097 import org.jboss.dna.graph.request.ReadNextBlockOfChildrenRequest; 098 import org.jboss.dna.graph.request.ReadNodeRequest; 099 import org.jboss.dna.graph.request.ReadPropertyRequest; 100 import org.jboss.dna.graph.request.Request; 101 import org.jboss.dna.graph.request.UpdatePropertiesRequest; 102 import org.jboss.dna.graph.request.VerifyWorkspaceRequest; 103 import org.jboss.dna.graph.request.processor.RequestProcessor; 104 105 /** 106 * @author Randall Hauch 107 */ 108 @NotThreadSafe 109 public class BasicRequestProcessor extends RequestProcessor { 110 111 protected final EntityManager entities; 112 protected final ValueFactory<String> stringFactory; 113 protected final PathFactory pathFactory; 114 protected final NameFactory nameFactory; 115 protected final UuidFactory uuidFactory; 116 protected final Namespaces namespaces; 117 protected final Workspaces workspaces; 118 protected final UUID rootNodeUuid; 119 protected final String rootNodeUuidString; 120 protected final String nameOfDefaultWorkspace; 121 protected final String[] predefinedWorkspaceNames; 122 protected final boolean creatingWorkspacesAllowed; 123 protected final Serializer serializer; 124 protected final long largeValueMinimumSizeInBytes; 125 protected final boolean compressData; 126 protected final Logger logger; 127 protected final RequestProcessorCache cache; 128 protected final boolean enforceReferentialIntegrity; 129 private final Set<Long> workspaceIdsWithChangedReferences = new HashSet<Long>(); 130 131 /** 132 * @param sourceName 133 * @param context 134 * @param entityManager 135 * @param rootNodeUuid 136 * @param nameOfDefaultWorkspace 137 * @param predefinedWorkspaceNames 138 * @param largeValueMinimumSizeInBytes 139 * @param creatingWorkspacesAllowed 140 * @param compressData 141 * @param enforceReferentialIntegrity 142 */ 143 public BasicRequestProcessor( String sourceName, 144 ExecutionContext context, 145 EntityManager entityManager, 146 UUID rootNodeUuid, 147 String nameOfDefaultWorkspace, 148 String[] predefinedWorkspaceNames, 149 long largeValueMinimumSizeInBytes, 150 boolean creatingWorkspacesAllowed, 151 boolean compressData, 152 boolean enforceReferentialIntegrity ) { 153 super(sourceName, context); 154 assert entityManager != null; 155 assert rootNodeUuid != null; 156 assert predefinedWorkspaceNames != null; 157 this.entities = entityManager; 158 ValueFactories valuesFactory = context.getValueFactories(); 159 this.stringFactory = valuesFactory.getStringFactory(); 160 this.pathFactory = valuesFactory.getPathFactory(); 161 this.nameFactory = valuesFactory.getNameFactory(); 162 this.uuidFactory = valuesFactory.getUuidFactory(); 163 this.namespaces = new Namespaces(entityManager); 164 this.workspaces = new Workspaces(entityManager); 165 this.rootNodeUuid = rootNodeUuid; 166 this.rootNodeUuidString = this.rootNodeUuid.toString(); 167 this.nameOfDefaultWorkspace = nameOfDefaultWorkspace; 168 this.creatingWorkspacesAllowed = creatingWorkspacesAllowed; 169 this.largeValueMinimumSizeInBytes = largeValueMinimumSizeInBytes; 170 this.compressData = compressData; 171 this.enforceReferentialIntegrity = enforceReferentialIntegrity; 172 this.serializer = new Serializer(context, true); 173 this.logger = getExecutionContext().getLogger(getClass()); 174 this.cache = new RequestProcessorCache(this.pathFactory); 175 this.predefinedWorkspaceNames = predefinedWorkspaceNames; 176 177 // Start the transaction ... 178 this.entities.getTransaction().begin(); 179 } 180 181 /** 182 * {@inheritDoc} 183 * 184 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateNodeRequest) 185 */ 186 @Override 187 public void process( CreateNodeRequest request ) { 188 logger.trace(request.toString()); 189 Location actualLocation = null; 190 try { 191 // Find the workspace ... 192 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 193 if (workspace == null) return; 194 Long workspaceId = workspace.getId(); 195 assert workspaceId != null; 196 197 // Create nodes have to be defined via a path ... 198 Location parentLocation = request.under(); 199 ActualLocation actual = getActualLocation(workspace.getId(), parentLocation); 200 String parentUuidString = actual.uuid; 201 assert parentUuidString != null; 202 203 // We need to look for an existing UUID property in the request, 204 // so since we have to iterate through the properties, go ahead an serialize them right away ... 205 String uuidString = null; 206 for (Property property : request.properties()) { 207 if (property.getName().equals(DnaLexicon.UUID)) { 208 uuidString = stringFactory.create(property.getFirstValue()); 209 break; 210 } 211 } 212 if (uuidString == null) uuidString = UUID.randomUUID().toString(); 213 assert uuidString != null; 214 createProperties(workspaceId, uuidString, request.properties()); 215 216 // Find or create the namespace for the child ... 217 Name childName = request.named(); 218 String childNsUri = childName.getNamespaceUri(); 219 NamespaceEntity ns = namespaces.get(childNsUri, true); 220 assert ns != null; 221 final Path parentPath = actual.location.getPath(); 222 assert parentPath != null; 223 224 // Figure out the next SNS index and index-in-parent for this new child ... 225 actualLocation = addNewChild(workspaceId, actual, uuidString, childName, true); 226 227 // Since we've just created this node, we know about all the children (actually, there are none). 228 cache.setAllChildren(workspace.getId(), actualLocation.getPath(), new LinkedList<Location>()); 229 230 // Flush the entities ... 231 // entities.flush(); 232 233 } catch (Throwable e) { // Includes PathNotFoundException 234 request.setError(e); 235 logger.trace(e, "Problem " + request); 236 return; 237 } 238 request.setActualLocationOfNode(actualLocation); 239 } 240 241 /** 242 * Create a new child with the supplied UUID and name under the supplied parent. If the parent is null, then the child will be 243 * the root node. 244 * 245 * @param workspaceId the ID of the workspace in which the child is to be created 246 * @param parent the actual location of the parent, or null if the child is to be the root of the workspace 247 * @param childUuid the UUID of the child 248 * @param childName the name of the child 249 * @param allowSameNameChildrenInNewNode 250 * @return the location of the new child 251 */ 252 protected Location addNewChild( Long workspaceId, 253 ActualLocation parent, 254 String childUuid, 255 Name childName, 256 boolean allowSameNameChildrenInNewNode ) { 257 int nextSnsIndex = 1; // SNS index is 1-based 258 int nextIndexInParent = 0; // index-in-parent is 0-based 259 String childNsUri = childName.getNamespaceUri(); 260 NamespaceEntity ns = namespaces.get(childNsUri, true); 261 assert ns != null; 262 263 // If the parent is null, the create a root node ... 264 Path parentPath = null; 265 String parentUuid = null; 266 ChildEntity parentEntity = null; 267 if (parent == null) { 268 return Location.create(pathFactory.createRootPath(), UUID.fromString(childUuid)); 269 } 270 parentPath = parent.location.getPath(); 271 parentUuid = parent.uuid; 272 parentEntity = parent.childEntity; // may be null 273 274 assert workspaceId != null; 275 276 ChildId id = new ChildId(workspaceId, parentUuid, childUuid); 277 ChildEntity entity = null; 278 279 // Look in the cache for the children of the parent node. 280 LinkedList<Location> childrenOfParent = cache.getAllChildren(workspaceId, parentPath); 281 282 // Now create the entity ... 283 if (parentEntity == null || parentEntity.getAllowsSameNameChildren()) { 284 // The parent DOES allow same-name-siblings, so we need to find the SNS index ... 285 286 if (childrenOfParent != null) { 287 // The cache had the complete list of children for the parent node, which means 288 // we know about all of the children and can walk the children to figure out the next indexes. 289 nextIndexInParent = childrenOfParent.size(); 290 if (nextIndexInParent > 1) { 291 // Since we want the last indexes, process the list backwards ... 292 ListIterator<Location> iter = childrenOfParent.listIterator(childrenOfParent.size()); 293 while (iter.hasPrevious()) { 294 Location existing = iter.previous(); 295 Path.Segment segment = existing.getPath().getLastSegment(); 296 if (!segment.getName().equals(childName)) continue; 297 // Otherwise the name matched, so get the indexes ... 298 nextSnsIndex = segment.getIndex() + 1; 299 } 300 } 301 } else { 302 // The cache did not have the complete list of children for the parent node, 303 // so we need to look the values up by querying the database ... 304 305 // Find the largest SNS index in the existing ChildEntity objects with the same name ... 306 String childLocalName = childName.getLocalName(); 307 Query query = entities.createNamedQuery("ChildEntity.findMaximumSnsIndex"); 308 query.setParameter("workspaceId", workspaceId); 309 query.setParameter("parentUuid", parentUuid); 310 query.setParameter("ns", ns.getId()); 311 query.setParameter("childName", childLocalName); 312 try { 313 Integer result = (Integer)query.getSingleResult(); 314 nextSnsIndex = result != null ? result + 1 : 1; // SNS index is 1-based 315 } catch (NoResultException e) { 316 } 317 318 // Find the largest child index in the existing ChildEntity objects ... 319 query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex"); 320 query.setParameter("workspaceId", workspaceId); 321 query.setParameter("parentUuid", parentUuid); 322 try { 323 Integer result = (Integer)query.getSingleResult(); 324 nextIndexInParent = result != null ? result + 1 : 0; // index-in-parent is 0-based 325 } catch (NoResultException e) { 326 } 327 } 328 329 // Create the new ChildEntity ... 330 entity = new ChildEntity(id, nextIndexInParent, ns, childName.getLocalName(), nextSnsIndex); 331 } else { 332 // The parent does not allow same-name-siblings, so we only need to find the next index ... 333 // Find the largest child index in the existing ChildEntity objects ... 334 if (childrenOfParent != null) { 335 // The cache had the complete list of children for the parent node, which means 336 // we know about all of the children and can walk the children to figure out the next indexes. 337 nextIndexInParent = childrenOfParent.size(); 338 } else { 339 // We don't have the children cached, so we need to do a query ... 340 Query query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex"); 341 query.setParameter("workspaceId", workspaceId); 342 query.setParameter("parentUuid", parentUuid); 343 try { 344 Integer result = (Integer)query.getSingleResult(); 345 nextIndexInParent = result != null ? result + 1 : 0; // index-in-parent is 0-based 346 } catch (NoResultException e) { 347 } 348 } 349 350 // Create the new child entity ... 351 entity = new ChildEntity(id, nextIndexInParent, ns, childName.getLocalName(), 1); 352 } 353 354 // Persist the new child entity ... 355 entity.setAllowsSameNameChildren(allowSameNameChildrenInNewNode); 356 entities.persist(entity); 357 358 // Set the actual path, regardless of the supplied path... 359 Path path = pathFactory.create(parentPath, childName, nextSnsIndex); 360 Location actualLocation = Location.create(path, UUID.fromString(childUuid)); 361 362 // Finally, update the cache with the information we know ... 363 if (childrenOfParent != null) { 364 // Add to the cached list of children ... 365 childrenOfParent.add(actualLocation); 366 } else { 367 cache.setAllChildren(workspaceId, parentPath, null); 368 } 369 return actualLocation; 370 } 371 372 protected class NextChildIndexes { 373 protected final int nextIndexInParent; 374 protected final int nextSnsIndex; 375 376 protected NextChildIndexes( int nextIndexInParent, 377 int nextSnsIndex ) { 378 this.nextIndexInParent = nextIndexInParent; 379 this.nextSnsIndex = nextSnsIndex; 380 } 381 } 382 383 /** 384 * {@inheritDoc} 385 * 386 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadNodeRequest) 387 */ 388 @Override 389 public void process( ReadNodeRequest request ) { 390 logger.trace(request.toString()); 391 Location actualLocation = null; 392 try { 393 // Find the workspace ... 394 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 395 if (workspace == null) return; 396 Long workspaceId = workspace.getId(); 397 assert workspaceId != null; 398 399 Location location = request.at(); 400 ActualLocation actual = getActualLocation(workspaceId, location); 401 String parentUuidString = actual.uuid; 402 actualLocation = actual.location; 403 404 // Record the UUID as a property, since it's not stored in the serialized properties... 405 request.addProperty(actualLocation.getIdProperty(DnaLexicon.UUID)); 406 407 // Find the properties entity for this node ... 408 Query query = entities.createNamedQuery("PropertiesEntity.findByUuid"); 409 query.setParameter("workspaceId", workspaceId); 410 query.setParameter("uuid", parentUuidString); 411 try { 412 PropertiesEntity entity = (PropertiesEntity)query.getSingleResult(); 413 414 // Deserialize the properties ... 415 boolean compressed = entity.isCompressed(); 416 Collection<Property> properties = new LinkedList<Property>(); 417 byte[] data = entity.getData(); 418 if (data != null) { 419 LargeValueSerializer largeValues = new LargeValueSerializer(entity); 420 ByteArrayInputStream bais = new ByteArrayInputStream(data); 421 InputStream is = compressed ? new GZIPInputStream(bais) : bais; 422 ObjectInputStream ois = new ObjectInputStream(is); 423 try { 424 serializer.deserializeAllProperties(ois, properties, largeValues); 425 for (Property property : properties) { 426 request.addProperty(property); 427 } 428 } finally { 429 ois.close(); 430 } 431 } 432 433 } catch (NoResultException e) { 434 // No properties, but that's okay... 435 } 436 437 // Get the children for this node ... 438 for (Location childLocation : getAllChildren(workspaceId, actual)) { 439 request.addChild(childLocation); 440 } 441 } catch (NoResultException e) { 442 // there are no properties (probably not expected, but still okay) ... 443 } catch (Throwable e) { // Includes PathNotFoundException 444 request.setError(e); 445 return; 446 } 447 if (actualLocation != null) request.setActualLocationOfNode(actualLocation); 448 setCacheableInfo(request); 449 } 450 451 /** 452 * {@inheritDoc} 453 * 454 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllChildrenRequest) 455 */ 456 @Override 457 public void process( ReadAllChildrenRequest request ) { 458 logger.trace(request.toString()); 459 Location actualLocation = null; 460 try { 461 // Find the workspace ... 462 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 463 if (workspace == null) return; 464 Long workspaceId = workspace.getId(); 465 assert workspaceId != null; 466 467 Location location = request.of(); 468 ActualLocation actual = getActualLocation(workspaceId, location); 469 actualLocation = actual.location; 470 471 // Get the children for this node ... 472 for (Location childLocation : getAllChildren(workspaceId, actual)) { 473 request.addChild(childLocation); 474 } 475 } catch (NoResultException e) { 476 // there are no properties (probably not expected, but still okay) ... 477 } catch (Throwable e) { // Includes PathNotFoundException 478 request.setError(e); 479 return; 480 } 481 if (actualLocation != null) request.setActualLocationOfNode(actualLocation); 482 setCacheableInfo(request); 483 } 484 485 /** 486 * Utility method to obtain all of the children for a node, either from the cache (if all children are known to this 487 * processor) or by querying the database (and caching the list of children). 488 * 489 * @param workspaceId the ID of the workspace; may not be null 490 * @param parent the actual location of the parent node; may not be null 491 * @return the list of child locations 492 */ 493 protected LinkedList<Location> getAllChildren( Long workspaceId, 494 ActualLocation parent ) { 495 assert parent != null; 496 Path parentPath = parent.location.getPath(); 497 assert parentPath != null; 498 LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath); 499 if (cachedChildren != null) { 500 // The cache has all of the children for the node ... 501 return cachedChildren; 502 } 503 504 // Not found in the cache, so query the database ... 505 Query query = entities.createNamedQuery("ChildEntity.findAllUnderParent"); 506 query.setParameter("workspaceId", workspaceId); 507 query.setParameter("parentUuidString", parent.uuid); 508 LinkedList<Location> childLocations = new LinkedList<Location>(); 509 @SuppressWarnings( "unchecked" ) 510 List<ChildEntity> children = query.getResultList(); 511 for (ChildEntity child : children) { 512 String namespaceUri = child.getChildNamespace().getUri(); 513 String localName = child.getChildName(); 514 Name childName = nameFactory.create(namespaceUri, localName); 515 int sns = child.getSameNameSiblingIndex(); 516 Path childPath = pathFactory.create(parentPath, childName, sns); 517 String childUuidString = child.getId().getChildUuidString(); 518 Location childLocation = Location.create(childPath, UUID.fromString(childUuidString)); 519 childLocations.add(childLocation); 520 } 521 // Update the cache ... 522 cache.setAllChildren(workspaceId, parentPath, childLocations); 523 return childLocations; 524 } 525 526 /** 527 * {@inheritDoc} 528 * 529 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadBlockOfChildrenRequest) 530 */ 531 @Override 532 public void process( ReadBlockOfChildrenRequest request ) { 533 logger.trace(request.toString()); 534 Location actualLocation = null; 535 final int startingIndex = request.startingAtIndex(); 536 try { 537 // Find the workspace ... 538 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 539 if (workspace == null) return; 540 Long workspaceId = workspace.getId(); 541 assert workspaceId != null; 542 543 Location parentLocation = request.of(); 544 ActualLocation actualParent = getActualLocation(workspaceId, parentLocation); 545 actualLocation = actualParent.location; 546 547 Path parentPath = actualParent.location.getPath(); 548 assert parentPath != null; 549 LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath); 550 if (cachedChildren != null) { 551 // The cache has all of the children for the node ... 552 if (startingIndex < cachedChildren.size()) { 553 ListIterator<Location> iter = cachedChildren.listIterator(startingIndex); 554 for (int i = 0; i != request.count() && iter.hasNext(); ++i) { 555 Location child = iter.next(); 556 request.addChild(child); 557 } 558 } 559 } else { 560 // Nothing was cached, so we need to search the database for the children ... 561 Query query = entities.createNamedQuery("ChildEntity.findRangeUnderParent"); 562 query.setParameter("workspaceId", workspaceId); 563 query.setParameter("parentUuidString", actualParent.uuid); 564 query.setParameter("firstIndex", startingIndex); 565 query.setParameter("afterIndex", startingIndex + request.count()); 566 @SuppressWarnings( "unchecked" ) 567 List<ChildEntity> children = query.getResultList(); 568 for (ChildEntity child : children) { 569 String namespaceUri = child.getChildNamespace().getUri(); 570 String localName = child.getChildName(); 571 Name childName = nameFactory.create(namespaceUri, localName); 572 int sns = child.getSameNameSiblingIndex(); 573 Path childPath = pathFactory.create(parentPath, childName, sns); 574 String childUuidString = child.getId().getChildUuidString(); 575 Location childLocation = Location.create(childPath, UUID.fromString(childUuidString)); 576 request.addChild(childLocation); 577 } 578 // Do not update the cache, since we don't know all of the children. 579 } 580 581 } catch (NoResultException e) { 582 // there are no properties (probably not expected, but still okay) ... 583 } catch (Throwable e) { // Includes PathNotFoundException 584 request.setError(e); 585 return; 586 } 587 if (actualLocation != null) request.setActualLocationOfNode(actualLocation); 588 setCacheableInfo(request); 589 } 590 591 /** 592 * {@inheritDoc} 593 * 594 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadNextBlockOfChildrenRequest) 595 */ 596 @Override 597 public void process( ReadNextBlockOfChildrenRequest request ) { 598 logger.trace(request.toString()); 599 Location actualLocation = null; 600 final Location previousSibling = request.startingAfter(); 601 final int count = request.count(); 602 try { 603 // Find the workspace ... 604 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 605 if (workspace == null) return; 606 Long workspaceId = workspace.getId(); 607 assert workspaceId != null; 608 609 ActualLocation actualSibling = getActualLocation(workspaceId, previousSibling); 610 actualLocation = actualSibling.location; 611 if (!actualLocation.getPath().isRoot()) { 612 // First look in the cache for the children of the parent ... 613 Path parentPath = actualSibling.location.getPath().getParent(); 614 assert parentPath != null; 615 LinkedList<Location> cachedChildren = cache.getAllChildren(workspaceId, parentPath); 616 if (cachedChildren != null) { 617 // The cache has all of the children for the node. 618 // First find the location of the previous sibling ... 619 boolean accumulate = false; 620 int counter = 0; 621 for (Location child : cachedChildren) { 622 if (accumulate) { 623 // We're accumulating children ... 624 request.addChild(child); 625 ++counter; 626 if (counter <= count) continue; 627 break; 628 } 629 // Haven't found the previous sibling yet ... 630 if (child.isSame(previousSibling)) { 631 accumulate = true; 632 } 633 } 634 } else { 635 // The children were not found in the cache, so we have to search the database. 636 // We don't know the UUID of the parent, so find the previous sibling and 637 // then get the starting index and the parent UUID ... 638 ChildEntity previousChild = actualSibling.childEntity; 639 if (previousChild == null) { 640 Query query = entities.createNamedQuery("ChildEntity.findByChildUuid"); 641 query.setParameter("workspaceId", workspaceId); 642 query.setParameter("childUuidString", actualSibling.uuid); 643 previousChild = (ChildEntity)query.getSingleResult(); 644 } 645 int startingIndex = previousChild.getIndexInParent() + 1; 646 String parentUuid = previousChild.getId().getParentUuidString(); 647 648 // Now search the database for the children ... 649 Query query = entities.createNamedQuery("ChildEntity.findRangeUnderParent"); 650 query.setParameter("workspaceId", workspaceId); 651 query.setParameter("parentUuidString", parentUuid); 652 query.setParameter("firstIndex", startingIndex); 653 query.setParameter("afterIndex", startingIndex + request.count()); 654 @SuppressWarnings( "unchecked" ) 655 List<ChildEntity> children = query.getResultList(); 656 LinkedList<Location> allChildren = null; 657 if (startingIndex == 1 && children.size() < request.count()) { 658 // The previous child was the first sibling, and we got fewer children than 659 // the max count. This means we know all of the children, so accumulate the locations 660 // so they can be cached ... 661 allChildren = new LinkedList<Location>(); 662 allChildren.add(actualSibling.location); 663 } 664 for (ChildEntity child : children) { 665 String namespaceUri = child.getChildNamespace().getUri(); 666 String localName = child.getChildName(); 667 Name childName = nameFactory.create(namespaceUri, localName); 668 int sns = child.getSameNameSiblingIndex(); 669 Path childPath = pathFactory.create(parentPath, childName, sns); 670 String childUuidString = child.getId().getChildUuidString(); 671 Location childLocation = Location.create(childPath, UUID.fromString(childUuidString)); 672 request.addChild(childLocation); 673 if (allChildren != null) { 674 // We're going to cache the results, so add this child ... 675 allChildren.add(childLocation); 676 } 677 } 678 679 if (allChildren != null) { 680 cache.setAllChildren(workspaceId, parentPath, allChildren); 681 } 682 } 683 } 684 685 } catch (NoResultException e) { 686 // there are no properties (probably not expected, but still okay) ... 687 } catch (Throwable e) { // Includes PathNotFoundException 688 request.setError(e); 689 return; 690 } 691 if (actualLocation != null) request.setActualLocationOfStartingAfterNode(actualLocation); 692 setCacheableInfo(request); 693 } 694 695 /** 696 * {@inheritDoc} 697 * 698 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllPropertiesRequest) 699 */ 700 @Override 701 public void process( ReadAllPropertiesRequest request ) { 702 logger.trace(request.toString()); 703 Location actualLocation = null; 704 try { 705 // Find the workspace ... 706 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 707 if (workspace == null) return; 708 Long workspaceId = workspace.getId(); 709 assert workspaceId != null; 710 711 Location location = request.at(); 712 ActualLocation actual = getActualLocation(workspaceId, location); 713 String uuidString = actual.uuid; 714 actualLocation = actual.location; 715 716 // Record the UUID as a property, since it's not stored in the serialized properties... 717 request.addProperty(actualLocation.getIdProperty(DnaLexicon.UUID)); 718 719 // Find the properties entity for this node ... 720 Query query = entities.createNamedQuery("PropertiesEntity.findByUuid"); 721 query.setParameter("workspaceId", workspaceId); 722 query.setParameter("uuid", uuidString); 723 PropertiesEntity entity = (PropertiesEntity)query.getSingleResult(); 724 725 // Deserialize the properties ... 726 boolean compressed = entity.isCompressed(); 727 int propertyCount = entity.getPropertyCount(); 728 Collection<Property> properties = new ArrayList<Property>(propertyCount); 729 byte[] data = entity.getData(); 730 if (data != null) { 731 LargeValueSerializer largeValues = new LargeValueSerializer(entity); 732 ByteArrayInputStream bais = new ByteArrayInputStream(data); 733 InputStream is = compressed ? new GZIPInputStream(bais) : bais; 734 ObjectInputStream ois = new ObjectInputStream(is); 735 try { 736 serializer.deserializeAllProperties(ois, properties, largeValues); 737 for (Property property : properties) { 738 request.addProperty(property); 739 } 740 } finally { 741 ois.close(); 742 } 743 } 744 } catch (NoResultException e) { 745 // there are no properties (probably not expected, but still okay) ... 746 } catch (Throwable e) { // Includes PathNotFoundException 747 request.setError(e); 748 return; 749 } 750 if (actualLocation != null) request.setActualLocationOfNode(actualLocation); 751 setCacheableInfo(request); 752 } 753 754 /** 755 * {@inheritDoc} 756 * 757 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadPropertyRequest) 758 */ 759 @Override 760 public void process( ReadPropertyRequest request ) { 761 logger.trace(request.toString()); 762 // Process the one property that's requested ... 763 Location actualLocation = null; 764 try { 765 // Find the workspace ... 766 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 767 if (workspace == null) return; 768 Long workspaceId = workspace.getId(); 769 assert workspaceId != null; 770 771 // Small optimization ... 772 final Name propertyName = request.named(); 773 if (DnaLexicon.UUID.equals(propertyName)) { 774 try { 775 // Just get the UUID ... 776 Location location = request.on(); 777 ActualLocation actual = getActualLocation(workspaceId, location); // verifies the UUID 778 UUID uuid = actual.location.getUuid(); 779 Property uuidProperty = getExecutionContext().getPropertyFactory().create(propertyName, uuid); 780 request.setProperty(uuidProperty); 781 request.setActualLocationOfNode(actual.location); 782 setCacheableInfo(request); 783 } catch (Throwable e) { // Includes PathNotFoundException 784 request.setError(e); 785 } 786 return; 787 } 788 789 Location location = request.on(); 790 ActualLocation actual = getActualLocation(workspaceId, location); 791 String uuidString = actual.uuid; 792 actualLocation = actual.location; 793 794 // Find the properties entity for this node ... 795 Query query = entities.createNamedQuery("PropertiesEntity.findByUuid"); 796 query.setParameter("workspaceId", workspaceId); 797 query.setParameter("uuid", uuidString); 798 PropertiesEntity entity = (PropertiesEntity)query.getSingleResult(); 799 800 // Deserialize the stream of properties, but only materialize the one property ... 801 boolean compressed = entity.isCompressed(); 802 int propertyCount = entity.getPropertyCount(); 803 Collection<Property> properties = new ArrayList<Property>(propertyCount); 804 byte[] data = entity.getData(); 805 if (data != null) { 806 LargeValueSerializer largeValues = new LargeValueSerializer(entity); 807 ByteArrayInputStream bais = new ByteArrayInputStream(data); 808 InputStream is = compressed ? new GZIPInputStream(bais) : bais; 809 ObjectInputStream ois = new ObjectInputStream(is); 810 try { 811 Serializer.LargeValues skippedLargeValues = Serializer.NO_LARGE_VALUES; 812 serializer.deserializeSomeProperties(ois, properties, largeValues, skippedLargeValues, propertyName); 813 for (Property property : properties) { 814 request.setProperty(property); // should be only one property 815 } 816 } finally { 817 ois.close(); 818 } 819 } 820 } catch (NoResultException e) { 821 // there are no properties (probably not expected, but still okay) ... 822 } catch (Throwable e) { // Includes PathNotFoundException 823 request.setError(e); 824 return; 825 } 826 if (actualLocation != null) request.setActualLocationOfNode(actualLocation); 827 setCacheableInfo(request); 828 } 829 830 /** 831 * {@inheritDoc} 832 * 833 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.UpdatePropertiesRequest) 834 */ 835 @Override 836 public void process( UpdatePropertiesRequest request ) { 837 logger.trace(request.toString()); 838 Location actualLocation = null; 839 try { 840 // Find the workspace ... 841 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 842 if (workspace == null) return; 843 Long workspaceId = workspace.getId(); 844 assert workspaceId != null; 845 846 Location location = request.on(); 847 ActualLocation actual = getActualLocation(workspaceId, location); 848 actualLocation = actual.location; 849 850 // Find the properties entity for this node ... 851 Query query = entities.createNamedQuery("PropertiesEntity.findByUuid"); 852 query.setParameter("workspaceId", workspaceId); 853 query.setParameter("uuid", actual.uuid); 854 PropertiesEntity entity = null; 855 try { 856 entity = (PropertiesEntity)query.getSingleResult(); 857 858 // Prepare the streams so we can deserialize all existing properties and reserialize the old and updated 859 // properties ... 860 boolean compressed = entity.isCompressed(); 861 byte[] originalData = entity.getData(); 862 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 863 OutputStream os = compressed ? new GZIPOutputStream(baos) : baos; 864 ObjectOutputStream oos = new ObjectOutputStream(os); 865 int numProps = 0; 866 LargeValueSerializer largeValues = null; 867 Map<Name, Property> props = request.properties(); 868 References refs = enforceReferentialIntegrity ? new References() : null; 869 if (originalData == null) { 870 largeValues = new LargeValueSerializer(entity); 871 numProps = props.size(); 872 serializer.serializeProperties(oos, numProps, props.values(), largeValues, refs); 873 } else { 874 boolean hadLargeValues = !entity.getLargeValues().isEmpty(); 875 Set<String> largeValueHashesWritten = hadLargeValues ? new HashSet<String>() : null; 876 largeValues = new LargeValueSerializer(entity, largeValueHashesWritten); 877 ByteArrayInputStream bais = new ByteArrayInputStream(originalData); 878 InputStream is = compressed ? new GZIPInputStream(bais) : bais; 879 ObjectInputStream ois = new ObjectInputStream(is); 880 SkippedLargeValues removedValues = new SkippedLargeValues(largeValues); 881 try { 882 Serializer.ReferenceValues refValues = refs != null ? refs : Serializer.NO_REFERENCES_VALUES; 883 numProps = serializer.reserializeProperties(ois, oos, props, largeValues, removedValues, refValues); 884 } finally { 885 try { 886 ois.close(); 887 } finally { 888 oos.close(); 889 } 890 } 891 // The new large values were recorded and associated with the properties entity during reserialization. 892 // However, any values no longer used now need to be removed ... 893 if (hadLargeValues) { 894 // Remove any large value from the 'skipped' list that was also written ... 895 removedValues.skippedKeys.removeAll(largeValueHashesWritten); 896 for (String oldHexKey : removedValues.skippedKeys) { 897 LargeValueId id = new LargeValueId(oldHexKey); 898 entity.getLargeValues().remove(id); 899 } 900 } 901 902 if (refs != null) { 903 // Remove any existing references ... 904 if (refs.hasRemoved()) { 905 for (Reference reference : refs.getRemoved()) { 906 String toUuid = resolveToUuid(workspaceId, reference); 907 if (toUuid != null) { 908 ReferenceId id = new ReferenceId(workspaceId, actual.uuid, toUuid); 909 ReferenceEntity refEntity = entities.find(ReferenceEntity.class, id); 910 if (refEntity != null) { 911 entities.remove(refEntity); 912 workspaceIdsWithChangedReferences.add(workspaceId); 913 } 914 } 915 } 916 } 917 } 918 } 919 entity.setPropertyCount(numProps); 920 entity.setData(baos.toByteArray()); 921 entity.setCompressed(compressData); 922 923 if (refs != null && refs.hasWritten()) { 924 // If there were references from the updated node ... 925 Set<Reference> newReferences = refs.getWritten(); 926 // Remove any reference that was written (and not removed) ... 927 newReferences.removeAll(refs.getRead()); 928 if (newReferences.size() != 0) { 929 // Now save the new references ... 930 for (Reference reference : newReferences) { 931 String toUuid = resolveToUuid(workspaceId, reference); 932 if (toUuid != null) { 933 ReferenceId id = new ReferenceId(workspaceId, actual.uuid, toUuid); 934 ReferenceEntity refEntity = new ReferenceEntity(id); 935 entities.persist(refEntity); 936 workspaceIdsWithChangedReferences.add(workspaceId); 937 } 938 } 939 } 940 } 941 } catch (NoResultException e) { 942 // there are no properties yet ... 943 createProperties(workspaceId, actual.uuid, request.properties().values()); 944 } 945 946 } catch (Throwable e) { // Includes PathNotFoundException 947 request.setError(e); 948 return; 949 } 950 if (actualLocation != null) request.setActualLocationOfNode(actualLocation); 951 } 952 953 /** 954 * {@inheritDoc} 955 * 956 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadBranchRequest) 957 */ 958 @Override 959 public void process( ReadBranchRequest request ) { 960 logger.trace(request.toString()); 961 Location actualLocation = null; 962 try { 963 // Find the workspace ... 964 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 965 if (workspace == null) return; 966 Long workspaceId = workspace.getId(); 967 assert workspaceId != null; 968 969 Location location = request.at(); 970 ActualLocation actual = getActualLocation(workspaceId, location); 971 actualLocation = actual.location; 972 Path path = actualLocation.getPath(); 973 974 // Record the location of each node by its UUID; we'll use this when processing the properties ... 975 Map<String, Location> locationsByUuid = new HashMap<String, Location>(); 976 locationsByUuid.put(actual.uuid, location); 977 978 // Compute the subgraph, including the root ... 979 int maxDepth = request.maximumDepth(); 980 SubgraphQuery query = SubgraphQuery.create(getExecutionContext(), 981 entities, 982 workspaceId, 983 actualLocation.getUuid(), 984 path, 985 maxDepth); 986 987 try { 988 // Record all of the children ... 989 Path parent = path; 990 String parentUuid = actual.uuid; 991 Location parentLocation = actualLocation; 992 List<Location> children = new LinkedList<Location>(); 993 Map<Location, List<Location>> childrenByParentLocation = new HashMap<Location, List<Location>>(); 994 childrenByParentLocation.put(parentLocation, children); 995 boolean includeChildrenOfNodesAtMaxDepth = true; 996 for (ChildEntity child : query.getNodes(false, includeChildrenOfNodesAtMaxDepth)) { 997 String namespaceUri = child.getChildNamespace().getUri(); 998 String localName = child.getChildName(); 999 Name childName = nameFactory.create(namespaceUri, localName); 1000 int sns = child.getSameNameSiblingIndex(); 1001 // Figure out who the parent is ... 1002 String childParentUuid = child.getId().getParentUuidString(); 1003 if (!parentUuid.equals(childParentUuid)) { 1004 // Find the correct parent ... 1005 parentLocation = locationsByUuid.get(childParentUuid); 1006 parent = parentLocation.getPath(); 1007 parentUuid = childParentUuid; 1008 // See if there is already a list of children for this parent ... 1009 children = childrenByParentLocation.get(parentLocation); 1010 if (children == null) { 1011 children = new LinkedList<Location>(); 1012 childrenByParentLocation.put(parentLocation, children); 1013 } 1014 } 1015 assert children != null; 1016 Path childPath = pathFactory.create(parent, childName, sns); 1017 String childUuidString = child.getId().getChildUuidString(); 1018 Location childLocation = Location.create(childPath, UUID.fromString(childUuidString)); 1019 locationsByUuid.put(childUuidString, childLocation); 1020 children.add(childLocation); 1021 } 1022 // Now add the list of children to the results ... 1023 for (Map.Entry<Location, List<Location>> entry : childrenByParentLocation.entrySet()) { 1024 // Don't add if there are no children ... 1025 if (!entry.getValue().isEmpty()) { 1026 request.setChildren(entry.getKey(), entry.getValue()); 1027 } 1028 } 1029 1030 // Note that we've found children for nodes that are at the maximum depth. This is so that the nodes 1031 // in the subgraph all have the correct children. However, we don't want to store the properties for 1032 // any node whose depth is greater than the maximum depth. Therefore, only get the properties that 1033 // include nodes within the maximum depth... 1034 includeChildrenOfNodesAtMaxDepth = false; 1035 1036 // Now record all of the properties ... 1037 for (PropertiesEntity props : query.getProperties(true, includeChildrenOfNodesAtMaxDepth)) { 1038 boolean compressed = props.isCompressed(); 1039 int propertyCount = props.getPropertyCount(); 1040 Collection<Property> properties = new ArrayList<Property>(propertyCount); 1041 Location nodeLocation = locationsByUuid.get(props.getId().getUuidString()); 1042 assert nodeLocation != null; 1043 // Record the UUID as a property, since it's not stored in the serialized properties... 1044 properties.add(actualLocation.getIdProperty(DnaLexicon.UUID)); 1045 // Deserialize all the properties (except the UUID)... 1046 byte[] data = props.getData(); 1047 if (data != null) { 1048 LargeValueSerializer largeValues = new LargeValueSerializer(props); 1049 ByteArrayInputStream bais = new ByteArrayInputStream(data); 1050 InputStream is = compressed ? new GZIPInputStream(bais) : bais; 1051 ObjectInputStream ois = new ObjectInputStream(is); 1052 try { 1053 serializer.deserializeAllProperties(ois, properties, largeValues); 1054 request.setProperties(nodeLocation, properties); 1055 } finally { 1056 ois.close(); 1057 } 1058 } 1059 } 1060 } finally { 1061 // Close and release the temporary data used for this operation ... 1062 query.close(); 1063 } 1064 1065 } catch (Throwable e) { // Includes PathNotFoundException 1066 request.setError(e); 1067 return; 1068 } 1069 request.setActualLocationOfNode(actualLocation); 1070 setCacheableInfo(request); 1071 } 1072 1073 /** 1074 * {@inheritDoc} 1075 * 1076 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CopyBranchRequest) 1077 */ 1078 @Override 1079 public void process( CopyBranchRequest request ) { 1080 logger.trace(request.toString()); 1081 Location actualFromLocation = null; 1082 Location actualToLocation = null; 1083 try { 1084 // Find the workspaces ... 1085 WorkspaceEntity fromWorkspace = getExistingWorkspace(request.fromWorkspace(), request); 1086 if (fromWorkspace == null) return; 1087 WorkspaceEntity intoWorkspace = getExistingWorkspace(request.intoWorkspace(), request); 1088 if (intoWorkspace == null) return; 1089 Long fromWorkspaceId = fromWorkspace.getId(); 1090 Long intoWorkspaceId = intoWorkspace.getId(); 1091 assert fromWorkspaceId != null; 1092 assert intoWorkspaceId != null; 1093 final boolean isSameWorkspace = fromWorkspaceId == intoWorkspaceId; 1094 1095 Location fromLocation = request.from(); 1096 ActualLocation actualFrom = getActualLocation(fromWorkspaceId, fromLocation); 1097 actualFromLocation = actualFrom.location; 1098 Path fromPath = actualFromLocation.getPath(); 1099 1100 Location newParentLocation = request.into(); 1101 ActualLocation actualNewParent = getActualLocation(intoWorkspaceId, newParentLocation); 1102 assert actualNewParent != null; 1103 1104 // Create a map that we'll use to record the new UUID for each of the original nodes ... 1105 Map<String, String> originalToNewUuid = new HashMap<String, String>(); 1106 1107 // Compute the subgraph, including the top node in the subgraph ... 1108 SubgraphQuery query = SubgraphQuery.create(getExecutionContext(), 1109 entities, 1110 fromWorkspaceId, 1111 actualFromLocation.getUuid(), 1112 fromPath, 1113 0); 1114 try { 1115 // Walk through the original nodes, creating new ChildEntity object (i.e., copy) for each original ... 1116 List<ChildEntity> originalNodes = query.getNodes(true, true); 1117 Iterator<ChildEntity> originalIter = originalNodes.iterator(); 1118 1119 // Start with the original (top-level) node first, since we need to add it to the list of children ... 1120 if (originalIter.hasNext()) { 1121 ChildEntity original = originalIter.next(); 1122 1123 // Create a new UUID for the copy ... 1124 String copyUuid = original.getId().getChildUuidString(); 1125 if (isSameWorkspace) { 1126 copyUuid = UUID.randomUUID().toString(); 1127 originalToNewUuid.put(original.getId().getChildUuidString(), copyUuid); 1128 } 1129 1130 // Now add the new copy of the original ... 1131 Name childName = request.desiredName(); 1132 if (childName == null) childName = fromPath.getLastSegment().getName(); 1133 boolean allowSnS = original.getAllowsSameNameChildren(); 1134 actualToLocation = addNewChild(intoWorkspaceId, actualNewParent, copyUuid, childName, allowSnS); 1135 } 1136 1137 // Now create copies of all children in the subgraph. 1138 // We assign new UUIDs to each new child only if the 'from' and 'into' workspaces are the same ... 1139 while (originalIter.hasNext()) { 1140 ChildEntity original = originalIter.next(); 1141 String newParentUuidOfCopy = original.getId().getParentUuidString(); 1142 if (isSameWorkspace) newParentUuidOfCopy = originalToNewUuid.get(newParentUuidOfCopy); 1143 assert newParentUuidOfCopy != null; 1144 1145 // Create a new UUID for the copy ... 1146 String copyUuid = original.getId().getChildUuidString(); 1147 if (isSameWorkspace) { 1148 copyUuid = UUID.randomUUID().toString(); 1149 originalToNewUuid.put(original.getId().getChildUuidString(), copyUuid); 1150 } 1151 1152 // Create the copy ... 1153 ChildEntity copy = new ChildEntity(new ChildId(intoWorkspaceId, newParentUuidOfCopy, copyUuid), 1154 original.getIndexInParent(), original.getChildNamespace(), 1155 original.getChildName(), original.getSameNameSiblingIndex()); 1156 entities.persist(copy); 1157 } 1158 entities.flush(); 1159 1160 Set<String> newNodesWithReferenceProperties = new HashSet<String>(); 1161 if (isSameWorkspace) { 1162 // Now create copies of all the intra-subgraph references, replacing the UUIDs on both ends ... 1163 for (ReferenceEntity reference : query.getInternalReferences()) { 1164 String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString()); 1165 assert newFromUuid != null; 1166 String newToUuid = originalToNewUuid.get(reference.getId().getToUuidString()); 1167 assert newToUuid != null; 1168 ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, newToUuid)); 1169 entities.persist(copy); 1170 newNodesWithReferenceProperties.add(newFromUuid); 1171 } 1172 1173 // Now create copies of all the references owned by the subgraph but pointing to non-subgraph nodes, 1174 // so we only replaced the 'from' UUID ... 1175 for (ReferenceEntity reference : query.getOutwardReferences()) { 1176 String oldToUuid = reference.getId().getToUuidString(); 1177 String newFromUuid = originalToNewUuid.get(reference.getId().getFromUuidString()); 1178 assert newFromUuid != null; 1179 ReferenceEntity copy = new ReferenceEntity(new ReferenceId(intoWorkspaceId, newFromUuid, oldToUuid)); 1180 entities.persist(copy); 1181 newNodesWithReferenceProperties.add(newFromUuid); 1182 } 1183 } 1184 1185 // Now process the properties, creating a copy (note references are not changed) ... 1186 for (PropertiesEntity original : query.getProperties(true, true)) { 1187 // Find the UUID of the copy ... 1188 String copyUuid = original.getId().getUuidString(); 1189 if (isSameWorkspace) copyUuid = originalToNewUuid.get(copyUuid); 1190 assert copyUuid != null; 1191 1192 // Create the copy ... 1193 boolean compressed = original.isCompressed(); 1194 byte[] originalData = original.getData(); 1195 PropertiesEntity copy = new PropertiesEntity(new NodeId(intoWorkspaceId, copyUuid)); 1196 copy.setCompressed(compressed); 1197 if (newNodesWithReferenceProperties.contains(copyUuid)) { 1198 1199 // This node has internal or outward references that must be adjusted ... 1200 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1201 OutputStream os = compressed ? new GZIPOutputStream(baos) : baos; 1202 ObjectOutputStream oos = new ObjectOutputStream(os); 1203 ByteArrayInputStream bais = new ByteArrayInputStream(originalData); 1204 InputStream is = compressed ? new GZIPInputStream(bais) : bais; 1205 ObjectInputStream ois = new ObjectInputStream(is); 1206 try { 1207 serializer.adjustReferenceProperties(ois, oos, originalToNewUuid); 1208 } finally { 1209 try { 1210 ois.close(); 1211 } finally { 1212 oos.close(); 1213 } 1214 } 1215 copy.setData(baos.toByteArray()); 1216 } else { 1217 // No references to adjust, so just copy the original data ... 1218 copy.setData(originalData); 1219 } 1220 copy.setPropertyCount(original.getPropertyCount()); 1221 copy.setReferentialIntegrityEnforced(original.isReferentialIntegrityEnforced()); 1222 entities.persist(copy); 1223 } 1224 entities.flush(); 1225 1226 } finally { 1227 // Close and release the temporary data used for this operation ... 1228 query.close(); 1229 } 1230 1231 } catch (Throwable e) { // Includes PathNotFoundException 1232 request.setError(e); 1233 return; 1234 } 1235 request.setActualLocations(actualFromLocation, actualToLocation); 1236 } 1237 1238 /** 1239 * {@inheritDoc} 1240 * 1241 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DeleteBranchRequest) 1242 */ 1243 @Override 1244 public void process( DeleteBranchRequest request ) { 1245 logger.trace(request.toString()); 1246 Location actualLocation = null; 1247 try { 1248 // Find the workspace ... 1249 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 1250 if (workspace == null) return; 1251 Long workspaceId = workspace.getId(); 1252 assert workspaceId != null; 1253 1254 Location location = request.at(); 1255 ActualLocation actual = getActualLocation(workspaceId, location); 1256 actualLocation = actual.location; 1257 Path path = actualLocation.getPath(); 1258 1259 // Compute the subgraph, including the top node in the subgraph ... 1260 SubgraphQuery query = SubgraphQuery.create(getExecutionContext(), 1261 entities, 1262 workspaceId, 1263 actualLocation.getUuid(), 1264 path, 1265 0); 1266 try { 1267 ChildEntity deleted = query.getNode(); 1268 String parentUuidString = deleted.getId().getParentUuidString(); 1269 String childName = deleted.getChildName(); 1270 long nsId = deleted.getChildNamespace().getId(); 1271 int indexInParent = deleted.getIndexInParent(); 1272 1273 // Get the locations of all deleted nodes, which will be required by events ... 1274 List<Location> deletedLocations = query.getNodeLocations(true, true); 1275 1276 // Now delete the subgraph ... 1277 query.deleteSubgraph(true); 1278 1279 // Verify referential integrity: that none of the deleted nodes are referenced by nodes not being deleted. 1280 List<ReferenceEntity> invalidReferences = query.getInwardReferences(); 1281 if (invalidReferences.size() > 0) { 1282 // Some of the references that remain will be invalid, since they point to nodes that 1283 // have just been deleted. Build up the information necessary to produce a useful exception ... 1284 ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory(); 1285 Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>(); 1286 for (ReferenceEntity entity : invalidReferences) { 1287 UUID fromUuid = UUID.fromString(entity.getId().getFromUuidString()); 1288 ActualLocation actualFromLocation = getActualLocation(workspaceId, Location.create(fromUuid)); 1289 Location fromLocation = actualFromLocation.location; 1290 List<Reference> refs = invalidRefs.get(fromLocation); 1291 if (refs == null) { 1292 refs = new ArrayList<Reference>(); 1293 invalidRefs.put(fromLocation, refs); 1294 } 1295 UUID toUuid = UUID.fromString(entity.getId().getToUuidString()); 1296 refs.add(refFactory.create(toUuid)); 1297 } 1298 String msg = JpaConnectorI18n.unableToDeleteBecauseOfReferences.text(); 1299 throw new ReferentialIntegrityException(invalidRefs, msg); 1300 } 1301 1302 // And adjust the SNS index and indexes ... 1303 ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities, 1304 workspaceId, 1305 parentUuidString, 1306 childName, 1307 nsId, 1308 indexInParent); 1309 entities.flush(); 1310 1311 // Remove from the cache of children locations all entries for deleted nodes ... 1312 cache.removeBranch(workspaceId, deletedLocations); 1313 } finally { 1314 // Close and release the temporary data used for this operation ... 1315 query.close(); 1316 } 1317 1318 } catch (Throwable e) { // Includes PathNotFoundException 1319 request.setError(e); 1320 return; 1321 } 1322 request.setActualLocationOfNode(actualLocation); 1323 } 1324 1325 /** 1326 * {@inheritDoc} 1327 * 1328 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.MoveBranchRequest) 1329 */ 1330 @Override 1331 public void process( MoveBranchRequest request ) { 1332 logger.trace(request.toString()); 1333 Location actualOldLocation = null; 1334 Location actualNewLocation = null; 1335 try { 1336 // Find the workspaces ... 1337 WorkspaceEntity workspace = getExistingWorkspace(request.inWorkspace(), request); 1338 if (workspace == null) return; 1339 Long workspaceId = workspace.getId(); 1340 assert workspaceId != null; 1341 1342 Location fromLocation = request.from(); 1343 ActualLocation actualLocation = getActualLocation(workspaceId, fromLocation); 1344 String fromUuidString = actualLocation.uuid; 1345 actualOldLocation = actualLocation.location; 1346 Path oldPath = actualOldLocation.getPath(); 1347 1348 // It's not possible to move the root node 1349 if (oldPath.isRoot()) { 1350 String msg = JpaConnectorI18n.unableToMoveRootNode.text(getSourceName()); 1351 throw new InvalidRequestException(msg); 1352 } 1353 1354 // Find the ChildEntity of the existing 'from' node ... 1355 ChildEntity fromEntity = actualLocation.childEntity; 1356 final String oldParentUuid = fromEntity.getId().getParentUuidString(); 1357 1358 // Find the actual new location ... 1359 Location toLocation = request.into(); 1360 String toUuidString = null; 1361 if (request.hasNoEffect()) { 1362 actualNewLocation = actualOldLocation; 1363 } else { 1364 // We have to proceed as normal ... 1365 ActualLocation actualIntoLocation = getActualLocation(workspaceId, toLocation); 1366 toUuidString = actualIntoLocation.uuid; 1367 if (!toUuidString.equals(oldParentUuid)) { 1368 // Now we know that the new parent is not the existing parent ... 1369 final int oldIndex = fromEntity.getIndexInParent(); 1370 1371 // Make sure the child name is set correctly ... 1372 String childOldLocalName = fromEntity.getChildName(); 1373 String childLocalName = null; 1374 NamespaceEntity ns = null; 1375 Name childName = request.desiredName(); 1376 if (childName != null) { 1377 childLocalName = request.desiredName().getLocalName(); 1378 String childNsUri = childName.getNamespaceUri(); 1379 ns = namespaces.get(childNsUri, true); 1380 } else { 1381 childName = oldPath.getLastSegment().getName(); 1382 childLocalName = fromEntity.getChildName(); 1383 ns = fromEntity.getChildNamespace(); 1384 } 1385 1386 // Find the largest SNS index in the existing ChildEntity objects with the same name ... 1387 Query query = entities.createNamedQuery("ChildEntity.findMaximumSnsIndex"); 1388 query.setParameter("workspaceId", workspaceId); 1389 query.setParameter("parentUuid", toUuidString); 1390 query.setParameter("ns", ns.getId()); 1391 query.setParameter("childName", childLocalName); 1392 int nextSnsIndex = 1; 1393 try { 1394 Integer index = (Integer)query.getSingleResult(); 1395 if (index != null) nextSnsIndex = index.intValue() + 1; 1396 } catch (NoResultException e) { 1397 } 1398 1399 // Find the largest child index in the existing ChildEntity objects ... 1400 query = entities.createNamedQuery("ChildEntity.findMaximumChildIndex"); 1401 query.setParameter("workspaceId", workspaceId); 1402 query.setParameter("parentUuid", toUuidString); 1403 int nextIndexInParent = 1; 1404 try { 1405 Integer index = (Integer)query.getSingleResult(); 1406 if (index != null) nextIndexInParent = index + 1; 1407 } catch (NoResultException e) { 1408 } 1409 1410 ChildId movedId = new ChildId(workspaceId, toUuidString, fromUuidString); 1411 if (fromEntity.getId().equals(movedId)) { 1412 // The node is being renamed, but not moved ... 1413 fromEntity.setChildName(childLocalName); 1414 fromEntity.setChildNamespace(ns); 1415 fromEntity.setIndexInParent(nextIndexInParent); 1416 fromEntity.setSameNameSiblingIndex(nextSnsIndex); 1417 } else { 1418 // We won't be able to move the entity to a different parent, because that would involve 1419 // changing the PK for the entity, which is not possible. Instead, we have to create a 1420 // new entity with the same identity information, then delete 'fromEntity' 1421 ChildEntity movedEntity = new ChildEntity(movedId, nextIndexInParent, ns, childLocalName, nextSnsIndex); 1422 movedEntity.setAllowsSameNameChildren(fromEntity.getAllowsSameNameChildren()); 1423 entities.persist(movedEntity); 1424 entities.remove(fromEntity); 1425 } 1426 1427 // Flush the entities to the database ... 1428 entities.flush(); 1429 1430 // Determine the new location ... 1431 Path newParentPath = actualIntoLocation.location.getPath(); 1432 Path newPath = pathFactory.create(newParentPath, childName, nextSnsIndex); 1433 actualNewLocation = actualOldLocation.with(newPath); 1434 1435 // And adjust the SNS index and indexes ... 1436 ChildEntity.adjustSnsIndexesAndIndexesAfterRemoving(entities, 1437 workspaceId, 1438 oldParentUuid, 1439 childOldLocalName, 1440 ns.getId(), 1441 oldIndex); 1442 1443 // Update the cache ... 1444 cache.moveNode(workspaceId, actualOldLocation, oldIndex, actualNewLocation); 1445 } 1446 1447 } 1448 1449 } catch (Throwable e) { // Includes PathNotFoundException 1450 request.setError(e); 1451 return; 1452 } 1453 request.setActualLocations(actualOldLocation, actualNewLocation); 1454 } 1455 1456 /** 1457 * {@inheritDoc} 1458 * 1459 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest) 1460 */ 1461 @Override 1462 public void process( VerifyWorkspaceRequest request ) { 1463 // Find the workspace ... 1464 String workspaceName = request.workspaceName(); 1465 if (workspaceName == null) workspaceName = nameOfDefaultWorkspace; 1466 WorkspaceEntity workspace = getExistingWorkspace(workspaceName, request); 1467 if (workspace != null) { 1468 Long workspaceId = workspace.getId(); 1469 assert workspaceId != null; 1470 ActualLocation actual = getActualLocation(workspaceId, Location.create(pathFactory.createRootPath())); 1471 request.setActualRootLocation(actual.location); 1472 request.setActualWorkspaceName(workspace.getName()); 1473 } 1474 } 1475 1476 /** 1477 * {@inheritDoc} 1478 * 1479 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest) 1480 */ 1481 @Override 1482 public void process( GetWorkspacesRequest request ) { 1483 // Return the set of available workspace names, even if new workspaces can be created ... 1484 Set<String> names = workspaces.getWorkspaceNames(); 1485 // Add in the names of the predefined workspaces (in case they weren't yet accessed) ... 1486 for (String name : this.predefinedWorkspaceNames) { 1487 names.add(name); 1488 } 1489 request.setAvailableWorkspaceNames(Collections.unmodifiableSet(names)); 1490 setCacheableInfo(request); 1491 } 1492 1493 /** 1494 * {@inheritDoc} 1495 * 1496 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest) 1497 */ 1498 @Override 1499 public void process( CreateWorkspaceRequest request ) { 1500 String name = request.desiredNameOfNewWorkspace(); 1501 if (!creatingWorkspacesAllowed) { 1502 String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName()); 1503 request.setError(new InvalidRequestException(msg)); 1504 } 1505 Set<String> existingNames = workspaces.getWorkspaceNames(); 1506 int counter = 0; 1507 while (existingNames.contains(name)) { 1508 switch (request.conflictBehavior()) { 1509 case CREATE_WITH_ADJUSTED_NAME: 1510 name = request.desiredNameOfNewWorkspace() + ++counter; 1511 break; 1512 case DO_NOT_CREATE: 1513 default: 1514 String msg = JpaConnectorI18n.workspaceAlreadyExists.text(getSourceName(), name); 1515 request.setError(new InvalidWorkspaceException(msg)); 1516 return; 1517 } 1518 } 1519 // Create the workspace ... 1520 WorkspaceEntity entity = workspaces.create(name); 1521 request.setActualWorkspaceName(entity.getName()); 1522 // Create the root node ... 1523 Location root = Location.create(pathFactory.createRootPath()); 1524 request.setActualRootLocation(getActualLocation(entity.getId(), root).location); 1525 } 1526 1527 /** 1528 * {@inheritDoc} 1529 * 1530 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest) 1531 */ 1532 @SuppressWarnings( "unchecked" ) 1533 @Override 1534 public void process( CloneWorkspaceRequest request ) { 1535 if (!creatingWorkspacesAllowed) { 1536 String msg = JpaConnectorI18n.unableToCreateWorkspaces.text(getSourceName()); 1537 request.setError(new InvalidRequestException(msg)); 1538 } 1539 Set<String> existingNames = workspaces.getWorkspaceNames(); 1540 String name = request.desiredNameOfTargetWorkspace(); 1541 int counter = 0; 1542 while (existingNames.contains(name)) { 1543 switch (request.targetConflictBehavior()) { 1544 case CREATE_WITH_ADJUSTED_NAME: 1545 name = request.desiredNameOfTargetWorkspace() + ++counter; 1546 break; 1547 case DO_NOT_CREATE: 1548 default: 1549 String msg = JpaConnectorI18n.workspaceAlreadyExists.text(getSourceName(), name); 1550 request.setError(new InvalidWorkspaceException(msg)); 1551 return; 1552 } 1553 } 1554 String fromWorkspaceName = request.nameOfWorkspaceToBeCloned(); 1555 WorkspaceEntity fromWorkspace = workspaces.get(fromWorkspaceName, false); 1556 if (fromWorkspace == null) { 1557 switch (request.cloneConflictBehavior()) { 1558 case SKIP_CLONE: 1559 break; 1560 case DO_NOT_CLONE: 1561 default: 1562 String msg = JpaConnectorI18n.workspaceDoesNotExist.text(getSourceName(), fromWorkspaceName); 1563 request.setError(new InvalidRequestException(msg)); 1564 return; 1565 } 1566 } 1567 1568 // Create the workspace ... 1569 WorkspaceEntity intoWorkspace = workspaces.create(name); 1570 String newWorkspaceName = intoWorkspace.getName(); 1571 request.setActualWorkspaceName(newWorkspaceName); 1572 1573 if (fromWorkspace != null) { 1574 // Copy the workspace into the new workspace, via bulk insert statements .. 1575 Long fromWorkspaceId = fromWorkspace.getId(); 1576 Long intoWorkspaceId = intoWorkspace.getId(); 1577 Query query = entities.createNamedQuery("ChildEntity.findInWorkspace"); 1578 query.setParameter("workspaceId", fromWorkspaceId); 1579 List<ChildEntity> childEntities = query.getResultList(); 1580 for (ChildEntity child : childEntities) { 1581 ChildId origId = child.getId(); 1582 ChildId copyId = new ChildId(intoWorkspaceId, origId.getParentUuidString(), origId.getChildUuidString()); 1583 ChildEntity copy = new ChildEntity(copyId, child.getIndexInParent(), child.getChildNamespace(), 1584 child.getChildName()); 1585 copy.setAllowsSameNameChildren(child.getAllowsSameNameChildren()); 1586 copy.setSameNameSiblingIndex(child.getSameNameSiblingIndex()); 1587 entities.persist(copy); 1588 } 1589 entities.flush(); 1590 1591 query = entities.createNamedQuery("PropertiesEntity.findInWorkspace"); 1592 query.setParameter("workspaceId", fromWorkspaceId); 1593 List<PropertiesEntity> properties = query.getResultList(); 1594 for (PropertiesEntity property : properties) { 1595 NodeId copyId = new NodeId(intoWorkspaceId, property.getId().getUuidString()); 1596 PropertiesEntity copy = new PropertiesEntity(copyId); 1597 copy.setCompressed(property.isCompressed()); 1598 copy.setData(property.getData()); 1599 copy.setPropertyCount(property.getPropertyCount()); 1600 copy.setReferentialIntegrityEnforced(property.isReferentialIntegrityEnforced()); 1601 Collection<LargeValueId> ids = property.getLargeValues(); 1602 if (ids.size() != 0) { 1603 copy.getLargeValues().addAll(ids); 1604 } 1605 entities.persist(copy); 1606 } 1607 entities.flush(); 1608 1609 query = entities.createNamedQuery("ReferenceEntity.findInWorkspace"); 1610 query.setParameter("workspaceId", fromWorkspaceId); 1611 List<ReferenceEntity> references = query.getResultList(); 1612 for (ReferenceEntity reference : references) { 1613 ReferenceId from = reference.getId(); 1614 ReferenceId copy = new ReferenceId(fromWorkspaceId, from.getFromUuidString(), from.getToUuidString()); 1615 entities.persist(new ReferenceEntity(copy)); 1616 } 1617 entities.flush(); 1618 } 1619 1620 // Finish up the request ... 1621 Location root = Location.create(pathFactory.createRootPath(), rootNodeUuid); 1622 request.setActualRootLocation(getActualLocation(intoWorkspace.getId(), root).location); 1623 } 1624 1625 /** 1626 * {@inheritDoc} 1627 * 1628 * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest) 1629 */ 1630 @Override 1631 public void process( DestroyWorkspaceRequest request ) { 1632 // Verify the workspace exists ... 1633 WorkspaceEntity workspace = getExistingWorkspace(request.workspaceName(), request); 1634 if (workspace == null) return; 1635 Long workspaceId = workspace.getId(); 1636 assert workspaceId != null; 1637 1638 // Delete the workspace ... 1639 workspaces.destroy(workspace.getName()); 1640 1641 // Delete all the entities from this workspace ... 1642 Query delete = entities.createQuery("delete PropertiesEntity entity where entity.id.workspaceId = :workspaceId"); 1643 delete.setParameter("workspaceId", workspaceId); 1644 delete.executeUpdate(); 1645 1646 delete = entities.createQuery("delete ChildEntity entity where entity.id.workspaceId = :workspaceId"); 1647 delete.setParameter("workspaceId", workspaceId); 1648 delete.executeUpdate(); 1649 1650 delete = entities.createQuery("delete ReferenceEntity entity where entity.id.workspaceId = :workspaceId"); 1651 delete.setParameter("workspaceId", workspaceId); 1652 delete.executeUpdate(); 1653 1654 // Delete unused large values ... 1655 LargeValueEntity.deleteUnused(entities); 1656 } 1657 1658 /** 1659 * {@inheritDoc} 1660 * 1661 * @see org.jboss.dna.graph.request.processor.RequestProcessor#close() 1662 */ 1663 @Override 1664 public void close() { 1665 // Verify that the references are valid so far ... 1666 verifyReferences(); 1667 1668 // Now commit the transaction ... 1669 EntityTransaction txn = entities.getTransaction(); 1670 if (txn != null) txn.commit(); 1671 super.close(); 1672 } 1673 1674 protected WorkspaceEntity getExistingWorkspace( String workspaceName, 1675 Request request ) { 1676 WorkspaceEntity workspace = workspaces.get(workspaceName, false); 1677 if (workspace == null) { 1678 // Is this a predefined workspace? 1679 for (String name : predefinedWorkspaceNames) { 1680 if (workspaceName.equals(name)) { 1681 // Create it anyway ... 1682 return workspaces.create(workspaceName); 1683 } 1684 } 1685 String msg = JpaConnectorI18n.workspaceDoesNotExist.text(getSourceName(), workspaceName); 1686 request.setError(new InvalidWorkspaceException(msg)); 1687 } 1688 return workspace; 1689 } 1690 1691 /** 1692 * {@link ReferenceEntity Reference entities} are added and removed in the appropriate <code>process(...)</code> methods. 1693 * However, this method is typically called in {@link BasicRequestProcessor#close()} and performs the following steps: 1694 * <ol> 1695 * <li>Remove all references that have a "from" node that is under the versions branch.</li> 1696 * <li>Verify that all remaining references have a valid and existing "to" node</li> 1697 * </ol> 1698 */ 1699 protected void verifyReferences() { 1700 if (!enforceReferentialIntegrity) return; 1701 if (!workspaceIdsWithChangedReferences.isEmpty()) { 1702 1703 Map<Location, List<Reference>> invalidRefs = new HashMap<Location, List<Reference>>(); 1704 for (Long workspaceId : workspaceIdsWithChangedReferences) { 1705 1706 // Remove all references that have a "from" node that doesn't support referential integrity ... 1707 ReferenceEntity.deleteUnenforcedReferences(workspaceId, entities); 1708 1709 // Verify that all references are resolved to existing nodes ... 1710 int numUnresolved = ReferenceEntity.countAllReferencesResolved(workspaceId, entities); 1711 if (numUnresolved != 0) { 1712 List<ReferenceEntity> references = ReferenceEntity.verifyAllReferencesResolved(workspaceId, entities); 1713 ValueFactory<Reference> refFactory = getExecutionContext().getValueFactories().getReferenceFactory(); 1714 for (ReferenceEntity entity : references) { 1715 ReferenceId id = entity.getId(); 1716 UUID fromUuid = UUID.fromString(id.getFromUuidString()); 1717 Location location = Location.create(fromUuid); 1718 location = getActualLocation(id.getWorkspaceId(), location).location; 1719 List<Reference> refs = invalidRefs.get(location); 1720 if (refs == null) { 1721 refs = new ArrayList<Reference>(); 1722 invalidRefs.put(location, refs); 1723 } 1724 UUID toUuid = UUID.fromString(id.getToUuidString()); 1725 refs.add(refFactory.create(toUuid)); 1726 } 1727 } 1728 } 1729 1730 workspaceIdsWithChangedReferences.clear(); 1731 1732 if (!invalidRefs.isEmpty()) { 1733 String msg = JpaConnectorI18n.invalidReferences.text(getSourceName()); 1734 throw new ReferentialIntegrityException(invalidRefs, msg); 1735 } 1736 } 1737 } 1738 1739 protected String createProperties( Long workspaceId, 1740 String uuidString, 1741 Collection<Property> properties ) throws IOException { 1742 assert uuidString != null; 1743 1744 // Create the PropertiesEntity ... 1745 NodeId nodeId = new NodeId(workspaceId, uuidString); 1746 PropertiesEntity props = new PropertiesEntity(nodeId); 1747 1748 // If there are properties ... 1749 boolean processProperties = true; 1750 if (properties.isEmpty()) processProperties = false; 1751 else if (properties.size() == 1 && properties.iterator().next().getName().equals(JcrLexicon.NAME)) processProperties = false; 1752 1753 if (processProperties) { 1754 References refs = enforceReferentialIntegrity ? new References() : null; 1755 LargeValueSerializer largeValues = new LargeValueSerializer(props); 1756 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1757 OutputStream os = compressData ? new GZIPOutputStream(baos) : baos; 1758 ObjectOutputStream oos = new ObjectOutputStream(os); 1759 int numProperties = properties.size(); 1760 try { 1761 Serializer.ReferenceValues refValues = refs != null ? refs : Serializer.NO_REFERENCES_VALUES; 1762 serializer.serializeProperties(oos, numProperties, properties, largeValues, refValues); 1763 } finally { 1764 oos.close(); 1765 } 1766 1767 props.setData(baos.toByteArray()); 1768 props.setPropertyCount(numProperties); 1769 1770 // Record the changes to the references ... 1771 if (refs != null && refs.hasWritten()) { 1772 for (Reference reference : refs.getWritten()) { 1773 String toUuid = resolveToUuid(workspaceId, reference); 1774 if (toUuid != null) { 1775 ReferenceId id = new ReferenceId(workspaceId, uuidString, toUuid); 1776 ReferenceEntity refEntity = new ReferenceEntity(id); 1777 entities.persist(refEntity); 1778 workspaceIdsWithChangedReferences.add(workspaceId); 1779 } 1780 } 1781 } 1782 } else { 1783 props.setData(null); 1784 props.setPropertyCount(0); 1785 } 1786 props.setCompressed(compressData); 1787 props.setReferentialIntegrityEnforced(true); 1788 1789 entities.persist(props); 1790 1791 // References will be persisted in the commit ... 1792 return uuidString; 1793 } 1794 1795 /** 1796 * Attempt to resolve the reference. 1797 * 1798 * @param workspaceId the ID of the workspace in which the reference occurs; may not be null 1799 * @param reference the reference 1800 * @return the UUID of the node to which the reference points, or null if the reference could not be resolved 1801 */ 1802 protected String resolveToUuid( Long workspaceId, 1803 Reference reference ) { 1804 // See if the reference is by UUID ... 1805 try { 1806 UUID uuid = uuidFactory.create(reference); 1807 ActualLocation actualLocation = getActualLocation(workspaceId, Location.create(uuid)); 1808 return actualLocation.uuid; 1809 } catch (ValueFormatException e) { 1810 // Unknown kind of reference, which we don't track 1811 } catch (PathNotFoundException e) { 1812 // Unable to resolve reference ... 1813 } 1814 // Unable to resolve reference ... 1815 return null; 1816 } 1817 1818 /** 1819 * Utility method to look up the actual information given a supplied location. This method verifies that the location actually 1820 * represents an existing node, or it throws a {@link PathNotFoundException}. In all cases, the resulting information contains 1821 * the correct path and the correct UUID. 1822 * <p> 1823 * Note that this method sometimes performs "unnecessary" work when the location contains both a path to a node and the node's 1824 * corresponding UUID. Strictly speaking, this method would need to do very little. However, in such cases, this method does 1825 * verify that the information is still correct (ensuring that calls to use the {@link ChildEntity} will be correct). So, 1826 * while this work <i>may</i> be unnecessary, it does ensure that the location is consistent and correct (something that is 1827 * not unnecessary). 1828 * </p> 1829 * <p> 1830 * There are cases when a request containing a Path and a UUID are no longer correct. The node may have been just moved by 1831 * another request (perhaps from a different client), or there may be an error in the component making the request. In these 1832 * cases, this method assumes that the path is incorrect (since paths may change) and finds the <i>correct path</i> given the 1833 * UUID. 1834 * </p> 1835 * <p> 1836 * This method will also find the path when the location contains just the UUID. 1837 * </p> 1838 * 1839 * @param workspaceId the ID of the workspace; may not be null 1840 * @param original the original location; may not be null 1841 * @return the actual location, which includes the verified location and additional information needed by this method that may 1842 * be usable after this method is called 1843 * @throws PathNotFoundException if the location does not represent a location that could be found 1844 */ 1845 protected ActualLocation getActualLocation( Long workspaceId, 1846 Location original ) throws PathNotFoundException { 1847 assert original != null; 1848 1849 // Look for the UUID in the original ... 1850 Property uuidProperty = original.getIdProperty(DnaLexicon.UUID); 1851 String uuidString = uuidProperty != null && !uuidProperty.isEmpty() ? stringFactory.create(uuidProperty.getFirstValue()) : null; 1852 1853 Path path = original.getPath(); 1854 if (path != null) { 1855 // See if the location is already in the cache ... 1856 Location cached = cache.getLocationFor(workspaceId, path); 1857 if (cached != null) { 1858 return new ActualLocation(cached, cached.getUuid().toString(), null); 1859 } 1860 } 1861 1862 // If the original location has a UUID, then use that to find the child entity that represents the location ... 1863 if (uuidString != null) { 1864 // The original has a UUID, so use that to find the child entity. 1865 // Then walk up the ancestors and build the path. 1866 String nodeUuidString = uuidString; 1867 LinkedList<Path.Segment> segments = new LinkedList<Path.Segment>(); 1868 ChildEntity entity = null; 1869 ChildEntity originalEntity = null; 1870 while (uuidString != null && !uuidString.equals(this.rootNodeUuidString)) { 1871 Query query = entities.createNamedQuery("ChildEntity.findByChildUuid"); 1872 query.setParameter("workspaceId", workspaceId); 1873 query.setParameter("childUuidString", uuidString); 1874 try { 1875 // Find the parent of the UUID ... 1876 entity = (ChildEntity)query.getSingleResult(); 1877 if (originalEntity == null) originalEntity = entity; 1878 String localName = entity.getChildName(); 1879 String uri = entity.getChildNamespace().getUri(); 1880 int sns = entity.getSameNameSiblingIndex(); 1881 Name name = nameFactory.create(uri, localName); 1882 segments.addFirst(pathFactory.createSegment(name, sns)); 1883 uuidString = entity.getId().getParentUuidString(); 1884 } catch (NoResultException e) { 1885 uuidString = null; 1886 } 1887 } 1888 Path fullPath = pathFactory.createAbsolutePath(segments); 1889 Location newLocation = Location.create(fullPath, uuidProperty); 1890 cache.addNewNode(workspaceId, newLocation); 1891 return new ActualLocation(newLocation, nodeUuidString, originalEntity); 1892 } 1893 1894 // There is no UUID, so look for a path ... 1895 if (path == null) { 1896 String propName = DnaLexicon.UUID.getString(getExecutionContext().getNamespaceRegistry()); 1897 String msg = JpaConnectorI18n.locationShouldHavePathAndOrProperty.text(getSourceName(), propName); 1898 throw new PathNotFoundException(original, pathFactory.createRootPath(), msg); 1899 } 1900 1901 // Walk the child entities, starting at the root, down the to the path ... 1902 if (path.isRoot()) { 1903 Location newLocation = original.with(rootNodeUuid); 1904 cache.addNewNode(workspaceId, newLocation); 1905 return new ActualLocation(newLocation, rootNodeUuidString, null); 1906 } 1907 // See if the parent location is known in the cache ... 1908 Location cachedParent = cache.getLocationFor(workspaceId, path.getParent()); 1909 if (cachedParent != null) { 1910 // We know the UUID of the parent, so we can find the child a little faster ... 1911 ChildEntity child = findByPathSegment(workspaceId, cachedParent.getUuid().toString(), path.getLastSegment()); 1912 uuidString = child.getId().getChildUuidString(); 1913 Location newLocation = original.with(UUID.fromString(uuidString)); 1914 cache.addNewNode(workspaceId, newLocation); 1915 return new ActualLocation(newLocation, uuidString, child); 1916 } 1917 1918 // We couldn't find the parent, so we need to search by path ... 1919 String parentUuid = this.rootNodeUuidString; 1920 ChildEntity child = null; 1921 for (Path.Segment segment : path) { 1922 child = findByPathSegment(workspaceId, parentUuid, segment); 1923 if (child == null) { 1924 // Unable to complete the path, so prepare the exception by determining the lowest path that exists ... 1925 Path lowest = path; 1926 while (lowest.getLastSegment() != segment) { 1927 lowest = lowest.getParent(); 1928 } 1929 lowest = lowest.getParent(); 1930 throw new PathNotFoundException(original, lowest); 1931 } 1932 parentUuid = child.getId().getChildUuidString(); 1933 } 1934 assert child != null; 1935 uuidString = child.getId().getChildUuidString(); 1936 Location newLocation = original.with(UUID.fromString(uuidString)); 1937 cache.addNewNode(workspaceId, newLocation); 1938 return new ActualLocation(newLocation, uuidString, child); 1939 } 1940 1941 /** 1942 * Find the node with the supplied path segment that is a child of the supplied parent. 1943 * 1944 * @param workspaceId the ID of the workspace 1945 * @param parentUuid the UUID of the parent node, in string form 1946 * @param pathSegment the path segment of the child 1947 * @return the existing namespace, or null if one does not exist 1948 * @throws IllegalArgumentException if the manager or URI are null 1949 */ 1950 protected ChildEntity findByPathSegment( Long workspaceId, 1951 String parentUuid, 1952 Path.Segment pathSegment ) { 1953 assert namespaces != null; 1954 assert parentUuid != null; 1955 assert pathSegment != null; 1956 assert workspaceId != null; 1957 Name name = pathSegment.getName(); 1958 String localName = name.getLocalName(); 1959 String nsUri = name.getNamespaceUri(); 1960 NamespaceEntity ns = namespaces.get(nsUri, false); 1961 if (ns == null) { 1962 // The namespace can't be found, then certainly the node won't be found ... 1963 return null; 1964 } 1965 int snsIndex = pathSegment.hasIndex() ? pathSegment.getIndex() : 1; 1966 Query query = entities.createNamedQuery("ChildEntity.findByPathSegment"); 1967 query.setParameter("workspaceId", workspaceId); 1968 query.setParameter("parentUuidString", parentUuid); 1969 query.setParameter("ns", ns.getId()); 1970 query.setParameter("childName", localName); 1971 query.setParameter("sns", snsIndex); 1972 try { 1973 return (ChildEntity)query.getSingleResult(); 1974 } catch (NoResultException e) { 1975 return null; 1976 } 1977 } 1978 1979 protected String createHexValuesString( Collection<String> hexValues ) { 1980 if (hexValues == null || hexValues.isEmpty()) return null; 1981 StringBuilder sb = new StringBuilder(); 1982 boolean first = true; 1983 for (String hexValue : hexValues) { 1984 if (first) { 1985 first = false; 1986 } else { 1987 sb.append(','); 1988 } 1989 sb.append(hexValue); 1990 } 1991 return sb.toString(); 1992 } 1993 1994 protected Collection<String> createHexValues( String hexValuesString ) { 1995 return Arrays.asList(hexValuesString.split(",")); 1996 } 1997 1998 protected class LargeValueSerializer implements LargeValues { 1999 private final PropertiesEntity properties; 2000 private Set<String> written; 2001 2002 public LargeValueSerializer( PropertiesEntity entity ) { 2003 this.properties = entity; 2004 this.written = null; 2005 } 2006 2007 public LargeValueSerializer( PropertiesEntity entity, 2008 Set<String> written ) { 2009 this.properties = entity; 2010 this.written = written; 2011 } 2012 2013 /** 2014 * {@inheritDoc} 2015 * 2016 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize() 2017 */ 2018 public long getMinimumSize() { 2019 return largeValueMinimumSizeInBytes; 2020 } 2021 2022 /** 2023 * {@inheritDoc} 2024 * 2025 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories, 2026 * byte[], long) 2027 */ 2028 public Object read( ValueFactories valueFactories, 2029 byte[] hash, 2030 long length ) throws IOException { 2031 String hashStr = StringUtil.getHexString(hash); 2032 // Find the large value ... 2033 LargeValueId largeValueId = new LargeValueId(hashStr); 2034 LargeValueEntity entity = entities.find(LargeValueEntity.class, largeValueId); 2035 if (entity != null) { 2036 // Find the large value from the existing property entity ... 2037 byte[] data = entity.getData(); 2038 if (entity.isCompressed()) { 2039 InputStream stream = new GZIPInputStream(new ByteArrayInputStream(data)); 2040 try { 2041 data = IoUtil.readBytes(stream); 2042 } finally { 2043 stream.close(); 2044 } 2045 } 2046 return valueFactories.getValueFactory(entity.getType()).create(data); 2047 } 2048 throw new IOException(JpaConnectorI18n.unableToReadLargeValue.text(getSourceName(), hashStr)); 2049 } 2050 2051 /** 2052 * {@inheritDoc} 2053 * 2054 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#write(byte[], long, 2055 * org.jboss.dna.graph.property.PropertyType, java.lang.Object) 2056 */ 2057 public void write( byte[] hash, 2058 long length, 2059 PropertyType type, 2060 Object value ) throws IOException { 2061 if (value == null) return; 2062 String hashStr = StringUtil.getHexString(hash); 2063 if (written != null) written.add(hashStr); 2064 2065 // Look for an existing value in the collection ... 2066 final LargeValueId id = new LargeValueId(hashStr); 2067 for (LargeValueId existing : properties.getLargeValues()) { 2068 if (existing.equals(id)) { 2069 // Already associated with this properties entity 2070 return; 2071 } 2072 } 2073 LargeValueEntity entity = entities.find(LargeValueEntity.class, id); 2074 if (entity == null) { 2075 // We have to create the large value entity ... 2076 entity = new LargeValueEntity(); 2077 entity.setCompressed(true); 2078 entity.setId(id); 2079 entity.setLength(length); 2080 entity.setType(type); 2081 ValueFactories factories = getExecutionContext().getValueFactories(); 2082 byte[] bytes = null; 2083 switch (type) { 2084 case BINARY: 2085 Binary binary = factories.getBinaryFactory().create(value); 2086 InputStream stream = null; 2087 try { 2088 binary.acquire(); 2089 stream = binary.getStream(); 2090 if (compressData) stream = new GZIPInputStream(stream); 2091 bytes = IoUtil.readBytes(stream); 2092 } finally { 2093 try { 2094 if (stream != null) stream.close(); 2095 } finally { 2096 binary.release(); 2097 } 2098 } 2099 break; 2100 case URI: 2101 // This will be treated as a string ... 2102 default: 2103 String str = factories.getStringFactory().create(value); 2104 if (compressData) { 2105 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 2106 OutputStream strStream = new GZIPOutputStream(bs); 2107 try { 2108 IoUtil.write(str, strStream); 2109 } finally { 2110 strStream.close(); 2111 } 2112 bytes = bs.toByteArray(); 2113 } else { 2114 bytes = str.getBytes(); 2115 } 2116 break; 2117 } 2118 entity.setData(bytes); 2119 entities.persist(entity); 2120 } 2121 // Now associate the large value with the properties entity ... 2122 assert id.getHash() != null; 2123 properties.getLargeValues().add(id); 2124 } 2125 2126 } 2127 2128 protected class RecordingLargeValues implements LargeValues { 2129 protected final Collection<String> readKeys = new HashSet<String>(); 2130 protected final Collection<String> writtenKeys = new HashSet<String>(); 2131 protected final LargeValues delegate; 2132 2133 RecordingLargeValues( LargeValues delegate ) { 2134 assert delegate != null; 2135 this.delegate = delegate; 2136 } 2137 2138 /** 2139 * {@inheritDoc} 2140 * 2141 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize() 2142 */ 2143 public long getMinimumSize() { 2144 return delegate.getMinimumSize(); 2145 } 2146 2147 /** 2148 * {@inheritDoc} 2149 * 2150 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories, 2151 * byte[], long) 2152 */ 2153 public Object read( ValueFactories valueFactories, 2154 byte[] hash, 2155 long length ) throws IOException { 2156 String key = StringUtil.getHexString(hash); 2157 readKeys.add(key); 2158 return delegate.read(valueFactories, hash, length); 2159 } 2160 2161 public void write( byte[] hash, 2162 long length, 2163 PropertyType type, 2164 Object value ) throws IOException { 2165 String key = StringUtil.getHexString(hash); 2166 writtenKeys.add(key); 2167 delegate.write(hash, length, type, value); 2168 } 2169 } 2170 2171 protected class SkippedLargeValues implements LargeValues { 2172 protected Collection<String> skippedKeys = new HashSet<String>(); 2173 protected final LargeValues delegate; 2174 2175 SkippedLargeValues( LargeValues delegate ) { 2176 assert delegate != null; 2177 this.delegate = delegate; 2178 } 2179 2180 /** 2181 * {@inheritDoc} 2182 * 2183 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#getMinimumSize() 2184 */ 2185 public long getMinimumSize() { 2186 return delegate.getMinimumSize(); 2187 } 2188 2189 /** 2190 * {@inheritDoc} 2191 * 2192 * @see org.jboss.dna.connector.store.jpa.util.Serializer.LargeValues#read(org.jboss.dna.graph.property.ValueFactories, 2193 * byte[], long) 2194 */ 2195 public Object read( ValueFactories valueFactories, 2196 byte[] hash, 2197 long length ) throws IOException { 2198 String key = StringUtil.getHexString(hash); 2199 skippedKeys.add(key); 2200 return null; 2201 } 2202 2203 public void write( byte[] hash, 2204 long length, 2205 PropertyType type, 2206 Object value ) { 2207 throw new UnsupportedOperationException(); 2208 } 2209 } 2210 2211 @Immutable 2212 protected static class ActualLocation { 2213 /** The actual location */ 2214 protected final Location location; 2215 /** The string-form of the UUID, supplied as a convenience. */ 2216 protected final String uuid; 2217 /** The ChildEntity that represents the location, which may be null if the location represents the root node */ 2218 protected final ChildEntity childEntity; 2219 2220 protected ActualLocation( Location location, 2221 String uuid, 2222 ChildEntity childEntity ) { 2223 assert location != null; 2224 assert uuid != null; 2225 this.location = location; 2226 this.uuid = uuid; 2227 this.childEntity = childEntity; 2228 } 2229 2230 /** 2231 * {@inheritDoc} 2232 * 2233 * @see java.lang.Object#toString() 2234 */ 2235 @Override 2236 public String toString() { 2237 return this.location.toString() + " (uuid=" + uuid + ") " + childEntity; 2238 } 2239 } 2240 2241 protected class References implements Serializer.ReferenceValues { 2242 private Set<Reference> read; 2243 private Set<Reference> removed; 2244 private Set<Reference> written; 2245 2246 protected References() { 2247 } 2248 2249 /** 2250 * {@inheritDoc} 2251 * 2252 * @see org.jboss.dna.connector.store.jpa.util.Serializer.ReferenceValues#read(org.jboss.dna.graph.property.Reference) 2253 */ 2254 public void read( Reference reference ) { 2255 if (read == null) read = new HashSet<Reference>(); 2256 read.add(reference); 2257 } 2258 2259 /** 2260 * {@inheritDoc} 2261 * 2262 * @see org.jboss.dna.connector.store.jpa.util.Serializer.ReferenceValues#remove(org.jboss.dna.graph.property.Reference) 2263 */ 2264 public void remove( Reference reference ) { 2265 if (removed == null) removed = new HashSet<Reference>(); 2266 removed.add(reference); 2267 } 2268 2269 /** 2270 * {@inheritDoc} 2271 * 2272 * @see org.jboss.dna.connector.store.jpa.util.Serializer.ReferenceValues#write(org.jboss.dna.graph.property.Reference) 2273 */ 2274 public void write( Reference reference ) { 2275 if (written == null) written = new HashSet<Reference>(); 2276 written.add(reference); 2277 } 2278 2279 public boolean hasRead() { 2280 return read != null; 2281 } 2282 2283 public boolean hasRemoved() { 2284 return removed != null; 2285 } 2286 2287 public boolean hasWritten() { 2288 return written != null; 2289 } 2290 2291 /** 2292 * @return read 2293 */ 2294 public Set<Reference> getRead() { 2295 if (read != null) return read; 2296 return Collections.emptySet(); 2297 } 2298 2299 /** 2300 * @return removed 2301 */ 2302 public Set<Reference> getRemoved() { 2303 if (removed != null) return removed; 2304 return Collections.emptySet(); 2305 } 2306 2307 /** 2308 * @return written 2309 */ 2310 public Set<Reference> getWritten() { 2311 if (written != null) return written; 2312 return Collections.emptySet(); 2313 } 2314 } 2315 }