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