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