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