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