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.federation.contribution; 025 026 import java.io.Serializable; 027 import java.util.Collection; 028 import java.util.Collections; 029 import java.util.Iterator; 030 import java.util.List; 031 import java.util.NoSuchElementException; 032 import net.jcip.annotations.Immutable; 033 import org.jboss.dna.graph.Location; 034 import org.jboss.dna.graph.property.DateTime; 035 import org.jboss.dna.graph.property.Name; 036 import org.jboss.dna.graph.property.NamespaceRegistry; 037 import org.jboss.dna.graph.property.Property; 038 import org.jboss.dna.graph.property.basic.JodaDateTime; 039 040 /** 041 * The contribution of a source to the information for a single federated node. Users of this interface should treat contributions 042 * as generally being immutable, since some implementation will be immutable and will return immutable {@link #getProperties() 043 * properties} and {@link #getChildren() children} containers. Thus, rather than make changes to an existing contribution, a new 044 * contribution is created to replace the previous contribution. 045 * 046 * @author Randall Hauch 047 */ 048 @Immutable 049 public abstract class Contribution implements Serializable { 050 051 /** 052 * Create an empty contribution from the named source. 053 * 054 * @param sourceName the name of the source, which may not be null or blank 055 * @param workspaceName the name of the workspace, which may not be null or blank 056 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 057 * expiration time 058 * @return the contribution 059 */ 060 public static Contribution create( String sourceName, 061 String workspaceName, 062 DateTime expirationTime ) { 063 return new EmptyContribution(sourceName, workspaceName, expirationTime); 064 } 065 066 /** 067 * Create a contribution of a single property from the named source. 068 * 069 * @param sourceName the name of the source, which may not be null or blank 070 * @param workspaceName the name of the workspace, which may not be null or blank 071 * @param locationInSource the location in the source for this contributed information; may not be null 072 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 073 * expiration time 074 * @param property the property from the source; may not be null 075 * @return the contribution 076 */ 077 public static Contribution create( String sourceName, 078 String workspaceName, 079 Location locationInSource, 080 DateTime expirationTime, 081 Property property ) { 082 if (property == null) { 083 return new EmptyContribution(sourceName, workspaceName, expirationTime); 084 } 085 return new OnePropertyContribution(sourceName, workspaceName, locationInSource, expirationTime, property); 086 } 087 088 /** 089 * Create a contribution of a single child from the named source. 090 * 091 * @param sourceName the name of the source, which may not be null or blank 092 * @param workspaceName the name of the workspace, which may not be null or blank 093 * @param locationInSource the path in the source for this contributed information; may not be null 094 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 095 * expiration time 096 * @param child the child from the source; may not be null or empty 097 * @return the contribution 098 */ 099 public static Contribution create( String sourceName, 100 String workspaceName, 101 Location locationInSource, 102 DateTime expirationTime, 103 Location child ) { 104 if (child == null) { 105 return new EmptyContribution(sourceName, workspaceName, expirationTime); 106 } 107 return new OneChildContribution(sourceName, workspaceName, locationInSource, expirationTime, child); 108 } 109 110 /** 111 * Create a contribution of a single child from the named source. 112 * 113 * @param sourceName the name of the source, which may not be null or blank 114 * @param workspaceName the name of the workspace, which may not be null or blank 115 * @param locationInSource the path in the source for this contributed information; may not be null 116 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 117 * expiration time 118 * @param child1 the first child from the source; may not be null or empty 119 * @param child2 the second child from the source; may not be null or empty 120 * @return the contribution 121 */ 122 public static Contribution create( String sourceName, 123 String workspaceName, 124 Location locationInSource, 125 DateTime expirationTime, 126 Location child1, 127 Location child2 ) { 128 if (child1 != null) { 129 if (child2 != null) { 130 return new TwoChildContribution(sourceName, workspaceName, locationInSource, expirationTime, child1, child2); 131 } 132 return new OneChildContribution(sourceName, workspaceName, locationInSource, expirationTime, child1); 133 } 134 if (child2 != null) { 135 return new OneChildContribution(sourceName, workspaceName, locationInSource, expirationTime, child2); 136 } 137 return new EmptyContribution(sourceName, workspaceName, expirationTime); 138 } 139 140 /** 141 * Create a contribution of the supplied properties and children from the named source. 142 * 143 * @param sourceName the name of the source, which may not be null or blank 144 * @param workspaceName the name of the workspace, which may not be null or blank 145 * @param locationInSource the path in the source for this contributed information; may not be null 146 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 147 * expiration time 148 * @param properties the properties from the source; may not be null 149 * @param children the children from the source; may not be null or empty 150 * @return the contribution 151 */ 152 public static Contribution create( String sourceName, 153 String workspaceName, 154 Location locationInSource, 155 DateTime expirationTime, 156 Collection<Property> properties, 157 List<Location> children ) { 158 if (properties == null || properties.isEmpty()) { 159 // There are no properties ... 160 if (children == null || children.isEmpty()) { 161 return new EmptyContribution(sourceName, workspaceName, expirationTime); 162 } 163 if (children.size() == 1) { 164 return new OneChildContribution(sourceName, workspaceName, locationInSource, expirationTime, 165 children.iterator().next()); 166 } 167 if (children.size() == 2) { 168 Iterator<Location> iter = children.iterator(); 169 return new TwoChildContribution(sourceName, workspaceName, locationInSource, expirationTime, iter.next(), 170 iter.next()); 171 } 172 return new MultiChildContribution(sourceName, workspaceName, locationInSource, expirationTime, children); 173 } 174 // There are some properties ... 175 if (children == null || children.isEmpty()) { 176 // There are no children ... 177 if (properties.size() == 1) { 178 return new OnePropertyContribution(sourceName, workspaceName, locationInSource, expirationTime, 179 properties.iterator().next()); 180 } 181 if (properties.size() == 2) { 182 Iterator<Property> iter = properties.iterator(); 183 return new TwoPropertyContribution(sourceName, workspaceName, locationInSource, expirationTime, iter.next(), 184 iter.next()); 185 } 186 if (properties.size() == 3) { 187 Iterator<Property> iter = properties.iterator(); 188 return new ThreePropertyContribution(sourceName, workspaceName, locationInSource, expirationTime, iter.next(), 189 iter.next(), iter.next()); 190 } 191 return new MultiPropertyContribution(sourceName, workspaceName, locationInSource, expirationTime, properties); 192 } 193 // There are some properties AND some children ... 194 return new NodeContribution(sourceName, workspaceName, locationInSource, expirationTime, properties, children); 195 } 196 197 /** 198 * Create a placeholder contribution of a single child from the named source. 199 * 200 * @param sourceName the name of the source, which may not be null or blank 201 * @param workspaceName the name of the workspace, which may not be null or blank 202 * @param locationInSource the path in the source for this contributed information; may not be null 203 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 204 * expiration time 205 * @param child the child from the source; may not be null or empty 206 * @return the contribution 207 */ 208 public static Contribution createPlaceholder( String sourceName, 209 String workspaceName, 210 Location locationInSource, 211 DateTime expirationTime, 212 Location child ) { 213 if (child == null) { 214 return new EmptyContribution(sourceName, workspaceName, expirationTime); 215 } 216 return new PlaceholderContribution(sourceName, workspaceName, locationInSource, expirationTime, 217 Collections.singletonList(child)); 218 } 219 220 /** 221 * Create a placeholder contribution of the supplied properties and children from the named source. 222 * 223 * @param sourceName the name of the source, which may not be null or blank 224 * @param workspaceName the name of the workspace, which may not be null or blank 225 * @param locationInSource the path in the source for this contributed information; may not be null 226 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 227 * expiration time 228 * @param children the children from the source; may not be null or empty 229 * @return the contribution 230 */ 231 public static Contribution createPlaceholder( String sourceName, 232 String workspaceName, 233 Location locationInSource, 234 DateTime expirationTime, 235 List<Location> children ) { 236 if (children == null || children.isEmpty()) { 237 return new EmptyContribution(sourceName, workspaceName, expirationTime); 238 } 239 return new PlaceholderContribution(sourceName, workspaceName, locationInSource, expirationTime, children); 240 } 241 242 /** 243 * This is the first version of this class. See the documentation of BasicMergePlan.serialVersionUID. 244 */ 245 private static final long serialVersionUID = 1L; 246 247 protected static final Iterator<Property> EMPTY_PROPERTY_ITERATOR = new EmptyIterator<Property>(); 248 protected static final Iterator<Location> EMPTY_CHILDREN_ITERATOR = new EmptyIterator<Location>(); 249 250 private final String sourceName; 251 private final String workspaceName; 252 private DateTime expirationTimeInUtc; 253 254 /** 255 * Create a contribution for the source with the supplied name and path. 256 * 257 * @param sourceName the name of the source, which may not be null or blank 258 * @param workspaceName the name of the workspace, which may not be null or blank 259 * @param expirationTime the time (in UTC) after which this contribution should be considered expired, or null if there is no 260 * expiration time 261 */ 262 protected Contribution( String sourceName, 263 String workspaceName, 264 DateTime expirationTime ) { 265 assert sourceName != null && sourceName.trim().length() != 0; 266 assert workspaceName != null && workspaceName.trim().length() != 0; 267 assert expirationTime == null || expirationTime.equals(expirationTime.toUtcTimeZone()); 268 this.sourceName = sourceName; 269 this.workspaceName = workspaceName; 270 this.expirationTimeInUtc = expirationTime; 271 } 272 273 /** 274 * Get the name of the source that made this contribution. 275 * 276 * @return the name of the contributing source 277 */ 278 public String getSourceName() { 279 return this.sourceName; 280 } 281 282 /** 283 * Get the name of the workspace in the {@link #getSourceName() source} from which this contribution came. 284 * 285 * @return the name of the workspace 286 */ 287 public String getWorkspaceName() { 288 return this.workspaceName; 289 } 290 291 /** 292 * Get the source-specific location of this information. 293 * 294 * @return the location as known to the source, or null for {@link EmptyContribution} 295 */ 296 public abstract Location getLocationInSource(); 297 298 /** 299 * Determine whether this contribution has expired given the supplied current time. 300 * 301 * @param utcTime the current time expressed in UTC; may not be null 302 * @return true if at least one contribution has expired, or false otherwise 303 */ 304 public boolean isExpired( DateTime utcTime ) { 305 assert utcTime != null; 306 assert utcTime.toUtcTimeZone().equals(utcTime); // check that it is passed UTC time 307 if (expirationTimeInUtc == null) return false; 308 return !expirationTimeInUtc.isAfter(utcTime); 309 } 310 311 /** 312 * Get the expiration time, already in UTC. 313 * 314 * @return the expiration time in UTC 315 */ 316 public DateTime getExpirationTimeInUtc() { 317 return this.expirationTimeInUtc; 318 } 319 320 /** 321 * Get the properties that are in this contribution. This resulting iterator does not support {@link Iterator#remove() 322 * removal}. 323 * 324 * @return the properties; never null 325 */ 326 public Iterator<Property> getProperties() { 327 return EMPTY_PROPERTY_ITERATOR; 328 } 329 330 /** 331 * Get the number of properties that are in this contribution. 332 * 333 * @return the number of properties 334 */ 335 public int getPropertyCount() { 336 return 0; 337 } 338 339 /** 340 * Get the contributed property with the supplied name. 341 * 342 * @param name the name of the property 343 * @return the contributed property that matches the name, or null if no such property is in the contribution 344 */ 345 public Property getProperty( Name name ) { 346 return null; 347 } 348 349 /** 350 * Get the children that make up this contribution. This resulting iterator does not support {@link Iterator#remove() removal} 351 * . 352 * 353 * @return the children; never null 354 */ 355 public Iterator<Location> getChildren() { 356 return EMPTY_CHILDREN_ITERATOR; 357 } 358 359 /** 360 * Get the number of children that make up this contribution. 361 * 362 * @return the number of children 363 */ 364 public int getChildrenCount() { 365 return 0; 366 } 367 368 /** 369 * Return whether this contribution is an empty contribution. 370 * 371 * @return true if this contribution is empty, or false otherwise 372 */ 373 public boolean isEmpty() { 374 return false; 375 } 376 377 /** 378 * Determine whether this contribution is considered a placeholder necessary solely because the same source has contributions 379 * at or below the children. 380 * 381 * @return true if a placeholder contribution, or false otherwise 382 */ 383 public boolean isPlaceholder() { 384 return false; 385 } 386 387 /** 388 * {@inheritDoc} 389 * <p> 390 * This implementation returns the hash code of the {@link #getSourceName() source name}, and is compatible with the 391 * implementation of {@link #equals(Object)}. 392 * </p> 393 */ 394 @Override 395 public int hashCode() { 396 return this.sourceName.hashCode(); 397 } 398 399 /** 400 * {@inheritDoc} 401 * 402 * @see java.lang.Object#toString() 403 */ 404 @Override 405 public String toString() { 406 return getString(null); 407 } 408 409 public String getString( NamespaceRegistry registry ) { 410 StringBuffer sb = new StringBuffer(); 411 sb.append("Contribution from \""); 412 sb.append(getSourceName()); 413 sb.append("\": "); 414 DateTime expiration = getExpirationTimeInUtc(); 415 if (expiration != null) { 416 if (isExpired(new JodaDateTime().toUtcTimeZone())) { 417 sb.append("expired "); 418 } else { 419 sb.append("expires "); 420 } 421 sb.append(expiration.getString()); 422 } 423 if (getPropertyCount() != 0) { 424 sb.append(" { "); 425 boolean first = true; 426 Iterator<Property> propIter = getProperties(); 427 while (propIter.hasNext()) { 428 if (!first) sb.append(", "); 429 else first = false; 430 sb.append(propIter.next().getString(registry)); 431 } 432 sb.append(" }"); 433 } 434 if (getChildrenCount() != 0) { 435 sb.append("< "); 436 boolean first = true; 437 Iterator<Location> childIter = getChildren(); 438 while (childIter.hasNext()) { 439 if (!first) sb.append(", "); 440 else first = false; 441 sb.append(childIter.next().getString(registry)); 442 } 443 sb.append(" >"); 444 } 445 return sb.toString(); 446 } 447 448 /** 449 * {@inheritDoc} 450 * <p> 451 * This implementation only compares the {@link #getSourceName() source name}. 452 * </p> 453 */ 454 @Override 455 public boolean equals( Object obj ) { 456 if (obj == this) return true; 457 if (obj instanceof Contribution) { 458 Contribution that = (Contribution)obj; 459 if (!this.getSourceName().equals(that.getSourceName())) return false; 460 return true; 461 } 462 return false; 463 } 464 465 protected static class ImmutableIterator<T> implements Iterator<T> { 466 private final Iterator<T> iter; 467 468 protected ImmutableIterator( Iterator<T> iter ) { 469 this.iter = iter; 470 } 471 472 /** 473 * {@inheritDoc} 474 * 475 * @see java.util.Iterator#hasNext() 476 */ 477 public boolean hasNext() { 478 return iter.hasNext(); 479 } 480 481 /** 482 * {@inheritDoc} 483 * 484 * @see java.util.Iterator#next() 485 */ 486 public T next() { 487 return iter.next(); 488 } 489 490 /** 491 * {@inheritDoc} 492 * 493 * @see java.util.Iterator#remove() 494 */ 495 public void remove() { 496 throw new UnsupportedOperationException(); 497 } 498 } 499 500 protected static class EmptyIterator<T> implements Iterator<T> { 501 502 /** 503 * {@inheritDoc} 504 * 505 * @see java.util.Iterator#hasNext() 506 */ 507 public boolean hasNext() { 508 return false; 509 } 510 511 /** 512 * {@inheritDoc} 513 * 514 * @see java.util.Iterator#next() 515 */ 516 public T next() { 517 throw new NoSuchElementException(); 518 } 519 520 /** 521 * {@inheritDoc} 522 * 523 * @see java.util.Iterator#remove() 524 */ 525 public void remove() { 526 throw new UnsupportedOperationException(); 527 } 528 529 } 530 531 protected static class OneValueIterator<T> implements Iterator<T> { 532 533 private final T value; 534 private boolean next = true; 535 536 protected OneValueIterator( T value ) { 537 assert value != null; 538 this.value = value; 539 } 540 541 /** 542 * {@inheritDoc} 543 * 544 * @see java.util.Iterator#hasNext() 545 */ 546 public boolean hasNext() { 547 return next; 548 } 549 550 /** 551 * {@inheritDoc} 552 * 553 * @see java.util.Iterator#next() 554 */ 555 public T next() { 556 if (next) { 557 next = false; 558 return value; 559 } 560 throw new NoSuchElementException(); 561 } 562 563 /** 564 * {@inheritDoc} 565 * 566 * @see java.util.Iterator#remove() 567 */ 568 public void remove() { 569 throw new UnsupportedOperationException(); 570 } 571 572 } 573 574 protected static class TwoValueIterator<T> implements Iterator<T> { 575 576 private final T value1; 577 private final T value2; 578 private int next = 2; 579 580 protected TwoValueIterator( T value1, 581 T value2 ) { 582 this.value1 = value1; 583 this.value2 = value2; 584 } 585 586 /** 587 * {@inheritDoc} 588 * 589 * @see java.util.Iterator#hasNext() 590 */ 591 public boolean hasNext() { 592 return next > 0; 593 } 594 595 /** 596 * {@inheritDoc} 597 * 598 * @see java.util.Iterator#next() 599 */ 600 public T next() { 601 if (next == 2) { 602 next = 1; 603 return value1; 604 } 605 if (next == 1) { 606 next = 0; 607 return value2; 608 } 609 throw new NoSuchElementException(); 610 } 611 612 /** 613 * {@inheritDoc} 614 * 615 * @see java.util.Iterator#remove() 616 */ 617 public void remove() { 618 throw new UnsupportedOperationException(); 619 } 620 } 621 622 protected static class ThreeValueIterator<T> implements Iterator<T> { 623 624 private final T value1; 625 private final T value2; 626 private final T value3; 627 private int next = 3; 628 629 protected ThreeValueIterator( T value1, 630 T value2, 631 T value3 ) { 632 this.value1 = value1; 633 this.value2 = value2; 634 this.value3 = value3; 635 } 636 637 /** 638 * {@inheritDoc} 639 * 640 * @see java.util.Iterator#hasNext() 641 */ 642 public boolean hasNext() { 643 return next > 0; 644 } 645 646 /** 647 * {@inheritDoc} 648 * 649 * @see java.util.Iterator#next() 650 */ 651 public T next() { 652 if (next == 3) { 653 next = 2; 654 return value1; 655 } 656 if (next == 2) { 657 next = 1; 658 return value2; 659 } 660 if (next == 1) { 661 next = 0; 662 return value3; 663 } 664 throw new NoSuchElementException(); 665 } 666 667 /** 668 * {@inheritDoc} 669 * 670 * @see java.util.Iterator#remove() 671 */ 672 public void remove() { 673 throw new UnsupportedOperationException(); 674 } 675 676 } 677 }