1 /* 2 * Copyright 2004 Sun Microsystems, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 package com.sun.syndication.feed.synd; 18 19 import com.sun.syndication.feed.impl.ObjectBean; 20 import com.sun.syndication.feed.impl.CopyFromHelper; 21 import com.sun.syndication.feed.WireFeed; 22 import com.sun.syndication.feed.module.*; 23 import com.sun.syndication.feed.module.impl.ModuleUtils; 24 import com.sun.syndication.feed.synd.impl.Converters; 25 import com.sun.syndication.feed.synd.impl.URINormalizer; 26 27 import java.util.*; 28 import java.io.Serializable; 29 30 /** 31 * Bean for all types of feeds. 32 * <p> 33 * It handles all RSS versions and Atom 0.3, it normalizes all info, it may lose information. 34 * <p> 35 * @author Alejandro Abdelnur 36 * 37 */ 38 public class SyndFeedImpl implements Serializable, SyndFeed { 39 40 private ObjectBean _objBean; 41 42 private String _encoding; 43 private String _uri; 44 private SyndContent _title; 45 private SyndContent _description; 46 private String _feedType; 47 private String _link; 48 private List _links; 49 private SyndImage _image; 50 private List _entries; 51 private List _modules; 52 private List _authors; 53 private List _contributors; 54 private List _foreignMarkup; 55 56 private static final Converters CONVERTERS = new Converters(); 57 58 private static final Set IGNORE_PROPERTIES = new HashSet(); 59 60 /** 61 * Unmodifiable Set containing the convenience properties of this class. 62 * <p> 63 * Convenience properties are mapped to Modules, for cloning the convenience properties 64 * can be ignored as the will be copied as part of the module cloning. 65 */ 66 67 public static final Set CONVENIENCE_PROPERTIES = Collections.unmodifiableSet(IGNORE_PROPERTIES); 68 69 static { 70 IGNORE_PROPERTIES.add("publishedDate"); 71 IGNORE_PROPERTIES.add("author"); 72 IGNORE_PROPERTIES.add("copyright"); 73 IGNORE_PROPERTIES.add("categories"); 74 IGNORE_PROPERTIES.add("language"); 75 } 76 77 /** 78 * Returns the real feed types the SyndFeedImpl supports when converting from and to. 79 * <p> 80 * @return the real feed type supported. 81 */ 82 public List getSupportedFeedTypes() { 83 return CONVERTERS.getSupportedFeedTypes(); 84 } 85 86 /** 87 * For implementations extending SyndFeedImpl to be able to use the ObjectBean functionality 88 * with extended interfaces. 89 * <p> 90 * @param beanClass 91 * @param convenienceProperties set containing the convenience properties of the SyndEntryImpl 92 * (the are ignored during cloning, check CloneableBean for details). 93 * 94 */ 95 protected SyndFeedImpl(Class beanClass,Set convenienceProperties) { 96 _objBean = new ObjectBean(beanClass,this,convenienceProperties); 97 } 98 99 /** 100 * Default constructor. All properties are set to <b>null</b>. 101 * <p> 102 * 103 */ 104 public SyndFeedImpl() { 105 this(null); 106 } 107 108 /** 109 * Creates a SyndFeedImpl and populates all its properties out of the 110 * given RSS Channel or Atom Feed properties. 111 * <p> 112 * @param feed the RSS Channel or the Atom Feed to populate the properties from. 113 * 114 */ 115 public SyndFeedImpl(WireFeed feed) { 116 this(SyndFeed.class,IGNORE_PROPERTIES); 117 if (feed!=null) { 118 _feedType = feed.getFeedType(); 119 Converter converter = CONVERTERS.getConverter(_feedType); 120 if (converter==null) { 121 throw new IllegalArgumentException("Invalid feed type ["+_feedType+"]"); 122 } 123 converter.copyInto(feed,this); 124 } 125 } 126 127 /** 128 * Creates a deep 'bean' clone of the object. 129 * <p> 130 * @return a clone of the object. 131 * @throws CloneNotSupportedException thrown if an element of the object cannot be cloned. 132 * 133 */ 134 public Object clone() throws CloneNotSupportedException { 135 return _objBean.clone(); 136 } 137 138 /** 139 * Indicates whether some other object is "equal to" this one as defined by the Object equals() method. 140 * <p> 141 * @param other he reference object with which to compare. 142 * @return <b>true</b> if 'this' object is equal to the 'other' object. 143 * 144 */ 145 public boolean equals(Object other) { 146 // can't use foreign markup in equals, due to JDOM equals impl 147 Object fm = getForeignMarkup(); 148 setForeignMarkup(((SyndFeedImpl)other).getForeignMarkup()); 149 boolean ret = _objBean.equals(other); 150 // restore foreign markup 151 setForeignMarkup(fm); 152 return ret; 153 } 154 155 /** 156 * Returns a hashcode value for the object. 157 * <p> 158 * It follows the contract defined by the Object hashCode() method. 159 * <p> 160 * @return the hashcode of the bean object. 161 * 162 */ 163 public int hashCode() { 164 return _objBean.hashCode(); 165 } 166 167 /** 168 * Returns the String representation for the object. 169 * <p> 170 * @return String representation for the object. 171 * 172 */ 173 public String toString() { 174 return _objBean.toString(); 175 } 176 177 /** 178 * Creates a real feed containing the information of the SyndFeedImpl. 179 * <p> 180 * The feed type of the created WireFeed is taken from the SyndFeedImpl feedType property. 181 * <p> 182 * @return the real feed. 183 * 184 */ 185 public WireFeed createWireFeed() { 186 return createWireFeed(_feedType); 187 } 188 189 /** 190 * Creates a real feed containing the information of the SyndFeedImpl. 191 * <p> 192 * @param feedType the feed type for the WireFeed to be created. 193 * @return the real feed. 194 * 195 */ 196 public WireFeed createWireFeed(String feedType) { 197 if (feedType==null) { 198 throw new IllegalArgumentException("Feed type cannot be null"); 199 } 200 Converter converter = CONVERTERS.getConverter(feedType); 201 if (converter==null) { 202 throw new IllegalArgumentException("Invalid feed type ["+feedType+"]"); 203 } 204 return converter.createRealFeed(this); 205 } 206 207 /** 208 * Returns the wire feed type the feed had/will-have when coverted from/to a WireFeed. 209 * <p> 210 * @return the feed type, <b>null</b> if none. 211 * 212 */ 213 public String getFeedType() { 214 return _feedType; 215 } 216 217 /** 218 * Sets the wire feed type the feed will-have when coverted to a WireFeed. 219 * <p> 220 * @param feedType the feed type to set, <b>null</b> if none. 221 * 222 */ 223 public void setFeedType(String feedType) { 224 _feedType = feedType; 225 } 226 227 /** 228 * Returns the charset encoding of a the feed. This is not set by Rome parsers. 229 * <p> 230 * @return the charset encoding of the feed. 231 * 232 */ 233 public String getEncoding() { 234 return _encoding; 235 } 236 237 /** 238 * Sets the charset encoding of a the feed. This is not set by Rome parsers. 239 * <p> 240 * @param encoding the charset encoding of the feed. 241 * 242 */ 243 public void setEncoding(String encoding) { 244 _encoding = encoding; 245 } 246 247 /** 248 * Returns the feed URI. 249 * <p> 250 * How the feed URI maps to a concrete feed type (RSS or Atom) depends on 251 * the concrete feed type. This is explained in detail in Rome documentation, 252 * <a href="http://wiki.java.net/bin/edit/Javawsxml/Rome04URIMapping">Feed and entry URI mapping</a>. 253 * <p> 254 * The returned URI is a normalized URI as specified in RFC 2396bis. 255 * <p> 256 * Note: The URI is the unique identifier, in the RSS 2.0/atom case this is 257 * the GUID, for RSS 1.0 this is the URI attribute of the item. The Link 258 * is the URL that the item is accessible under, the URI is the 259 * permanent identifier which the aggregator should use to reference 260 * this item. Often the URI will use some standardized identifier scheme 261 * such as DOI's so that items can be identified even if they appear in 262 * multiple feeds with different "links" (they might be on different 263 * hosting platforms but be the same item). Also, though rare, there 264 * could be multiple items with the same link but a different URI and 265 * associated metadata which need to be treated as distinct entities. 266 * In the RSS 1.0 case the URI must be a valid RDF URI reference. 267 * <p> 268 * @return the feed URI, <b>null</b> if none. 269 * 270 */ 271 public String getUri() { 272 return _uri; 273 } 274 275 /** 276 * Sets the feed URI. 277 * <p> 278 * How the feed URI maps to a concrete feed type (RSS or Atom) depends on 279 * the concrete feed type. This is explained in detail in Rome documentation, 280 * <a href="http://wiki.java.net/bin/edit/Javawsxml/Rome04URIMapping">Feed and entry URI mapping</a>. 281 * <p> 282 * Note: The URI is the unique identifier, in the RSS 2.0/atom case this is 283 * the GUID, for RSS 1.0 this is the URI attribute of the item. The Link 284 * is the URL that the item is accessible under, the URI is the 285 * permanent identifier which the aggregator should use to reference 286 * this item. Often the URI will use some standardized identifier scheme 287 * such as DOI's so that items can be identified even if they appear in 288 * multiple feeds with different "links" (they might be on different 289 * hosting platforms but be the same item). Also, though rare, there 290 * could be multiple items with the same link but a different URI and 291 * associated metadata which need to be treated as distinct entities. 292 * In the RSS 1.0 case the URI must be a valid RDF URI reference. 293 * <p> 294 * @param uri the feed URI to set, <b>null</b> if none. 295 * 296 */ 297 public void setUri(String uri) { 298 _uri = URINormalizer.normalize(uri); 299 } 300 301 /** 302 * Returns the feed title. 303 * <p> 304 * @return the feed title, <b>null</b> if none. 305 * 306 */ 307 public String getTitle() { 308 if (_title != null) return _title.getValue(); 309 return null; 310 } 311 312 /** 313 * Sets the feed title. 314 * <p> 315 * @param title the feed title to set, <b>null</b> if none. 316 * 317 */ 318 public void setTitle(String title) { 319 if (_title == null) _title = new SyndContentImpl(); 320 _title.setValue(title); 321 } 322 323 /** 324 * Returns the feed title as a text construct. 325 * <p> 326 * @return the feed title, <b>null</b> if none. 327 * 328 */ 329 public SyndContent getTitleEx() { 330 return _title; 331 } 332 333 /** 334 * Sets the feed title as a text construct. 335 * <p> 336 * @param title the feed title to set, <b>null</b> if none. 337 * 338 */ 339 public void setTitleEx(SyndContent title) { 340 _title = title; 341 } 342 343 /** 344 * Returns the feed link. 345 * <p> 346 * Note: The URI is the unique identifier, in the RSS 2.0/atom case this is 347 * the GUID, for RSS 1.0 this is the URI attribute of the item. The Link 348 * is the URL that the item is accessible under, the URI is the 349 * permanent identifier which the aggregator should use to reference 350 * this item. Often the URI will use some standardized identifier scheme 351 * such as DOI's so that items can be identified even if they appear in 352 * multiple feeds with different "links" (they might be on different 353 * hosting platforms but be the same item). Also, though rare, there 354 * could be multiple items with the same link but a different URI and 355 * associated metadata which need to be treated as distinct entities. 356 * In the RSS 1.0 case the URI must be a valid RDF URI reference. 357 * <p> 358 * @return the feed link, <b>null</b> if none. 359 * 360 */ 361 public String getLink() { 362 return _link; 363 } 364 365 /** 366 * Sets the feed link. 367 * <p> 368 * Note: The URI is the unique identifier, in the RSS 2.0/atom case this is 369 * the GUID, for RSS 1.0 this is the URI attribute of the item. The Link 370 * is the URL that the item is accessible under, the URI is the 371 * permanent identifier which the aggregator should use to reference 372 * this item. Often the URI will use some standardized identifier scheme 373 * such as DOI's so that items can be identified even if they appear in 374 * multiple feeds with different "links" (they might be on different 375 * hosting platforms but be the same item). Also, though rare, there 376 * could be multiple items with the same link but a different URI and 377 * associated metadata which need to be treated as distinct entities. 378 * In the RSS 1.0 case the URI must be a valid RDF URI reference. 379 * <p> 380 * @param link the feed link to set, <b>null</b> if none. 381 * 382 */ 383 public void setLink(String link) { 384 _link = link; 385 } 386 387 /** 388 * Returns the feed description. 389 * <p> 390 * @return the feed description, <b>null</b> if none. 391 * 392 */ 393 public String getDescription() { 394 if (_description != null) return _description.getValue(); 395 return null; 396 } 397 398 /** 399 * Sets the feed description. 400 * <p> 401 * @param description the feed description to set, <b>null</b> if none. 402 * 403 */ 404 public void setDescription(String description) { 405 if (_description == null) _description = new SyndContentImpl(); 406 _description.setValue(description); 407 } 408 409 /** 410 * Returns the feed description as a text construct. 411 * <p> 412 * @return the feed description, <b>null</b> if none. 413 * 414 */ 415 public SyndContent getDescriptionEx() { 416 return _description; 417 } 418 419 /** 420 * Sets the feed description as a text construct. 421 * <p> 422 * @param description the feed description to set, <b>null</b> if none. 423 * 424 */ 425 public void setDescriptionEx(SyndContent description) { 426 _description = description; 427 } 428 429 /** 430 * Returns the feed published date. 431 * <p> 432 * This method is a convenience method, it maps to the Dublin Core module date. 433 * <p> 434 * @return the feed published date, <b>null</b> if none. 435 * 436 */ 437 public Date getPublishedDate() { 438 return getDCModule().getDate(); 439 } 440 441 /** 442 * Sets the feed published date. 443 * <p> 444 * This method is a convenience method, it maps to the Dublin Core module date. 445 * <p> 446 * @param publishedDate the feed published date to set, <b>null</b> if none. 447 * 448 */ 449 public void setPublishedDate(Date publishedDate) { 450 getDCModule().setDate(publishedDate); 451 } 452 453 /** 454 * Returns the feed copyright. 455 * <p> 456 * This method is a convenience method, it maps to the Dublin Core module rights. 457 * <p> 458 * @return the feed copyright, <b>null</b> if none. 459 * 460 */ 461 public String getCopyright() { 462 return getDCModule().getRights(); 463 } 464 465 /** 466 * Sets the feed copyright. 467 * <p> 468 * This method is a convenience method, it maps to the Dublin Core module rights. 469 * <p> 470 * @param copyright the feed copyright to set, <b>null</b> if none. 471 * 472 */ 473 public void setCopyright(String copyright) { 474 getDCModule().setRights(copyright); 475 } 476 477 /** 478 * Returns the feed image. 479 * <p> 480 * @return the feed image, <b>null</b> if none. 481 * 482 */ 483 public SyndImage getImage() { 484 return _image; 485 } 486 487 /** 488 * Sets the feed image. 489 * <p> 490 * @param image the feed image to set, <b>null</b> if none. 491 * 492 */ 493 public void setImage(SyndImage image) { 494 _image = image; 495 } 496 497 /** 498 * Returns the feed categories. 499 * <p> 500 * This method is a convenience method, it maps to the Dublin Core module subjects. 501 * <p> 502 * @return a list of SyndCategoryImpl elements with the feed categories, 503 * an empty list if none. 504 * 505 */ 506 public List getCategories() { 507 return new SyndCategoryListFacade(getDCModule().getSubjects()); 508 } 509 510 /** 511 * Sets the feed categories. 512 * <p> 513 * This method is a convenience method, it maps to the Dublin Core module subjects. 514 * <p> 515 * @param categories the list of SyndCategoryImpl elements with the feed categories to set, 516 * an empty list or <b>null</b> if none. 517 * 518 */ 519 public void setCategories(List categories) { 520 getDCModule().setSubjects(SyndCategoryListFacade.convertElementsSyndCategoryToSubject(categories)); 521 } 522 523 /** 524 * Returns the feed entries. 525 * <p> 526 * @return a list of SyndEntryImpl elements with the feed entries, 527 * an empty list if none. 528 * 529 */ 530 public List getEntries() { 531 return (_entries==null) ? (_entries=new ArrayList()) : _entries; 532 } 533 534 /** 535 * Sets the feed entries. 536 * <p> 537 * @param entries the list of SyndEntryImpl elements with the feed entries to set, 538 * an empty list or <b>null</b> if none. 539 * 540 */ 541 public void setEntries(List entries) { 542 _entries = entries; 543 } 544 545 /** 546 * Returns the feed language. 547 * <p> 548 * This method is a convenience method, it maps to the Dublin Core module language. 549 * <p> 550 * @return the feed language, <b>null</b> if none. 551 * 552 */ 553 public String getLanguage() { 554 return getDCModule().getLanguage(); 555 } 556 557 /** 558 * Sets the feed language. 559 * <p> 560 * This method is a convenience method, it maps to the Dublin Core module language. 561 * <p> 562 * @param language the feed language to set, <b>null</b> if none. 563 * 564 */ 565 public void setLanguage(String language) { 566 getDCModule().setLanguage(language); 567 } 568 569 /** 570 * Returns the feed modules. 571 * <p> 572 * @return a list of ModuleImpl elements with the feed modules, 573 * an empty list if none. 574 * 575 */ 576 public List getModules() { 577 if (_modules==null) { 578 _modules=new ArrayList(); 579 } 580 if (ModuleUtils.getModule(_modules,DCModule.URI)==null) { 581 _modules.add(new DCModuleImpl()); 582 } 583 return _modules; 584 } 585 586 587 /** 588 * Sets the feed modules. 589 * <p> 590 * @param modules the list of ModuleImpl elements with the feed modules to set, 591 * an empty list or <b>null</b> if none. 592 * 593 */ 594 public void setModules(List modules) { 595 _modules = modules; 596 } 597 598 /** 599 * Returns the module identified by a given URI. 600 * <p> 601 * @param uri the URI of the ModuleImpl. 602 * @return The module with the given URI, <b>null</b> if none. 603 */ 604 public Module getModule(String uri) { 605 return ModuleUtils.getModule(getModules(),uri); 606 } 607 608 /** 609 * Returns the Dublin Core module of the feed. 610 * @return the DC module, it's never <b>null</b> 611 * 612 */ 613 private DCModule getDCModule() { 614 return (DCModule) getModule(DCModule.URI); 615 } 616 617 public Class getInterface() { 618 return SyndFeed.class; 619 } 620 621 public void copyFrom(Object obj) { 622 COPY_FROM_HELPER.copy(this,obj); 623 } 624 625 626 // TODO We need to find out how to refactor this one in a nice reusable way. 627 628 private static final CopyFromHelper COPY_FROM_HELPER; 629 630 static { 631 Map basePropInterfaceMap = new HashMap(); 632 basePropInterfaceMap.put("feedType",String.class); 633 basePropInterfaceMap.put("encoding",String.class); 634 basePropInterfaceMap.put("uri",String.class); 635 basePropInterfaceMap.put("title",String.class); 636 basePropInterfaceMap.put("link",String.class); 637 basePropInterfaceMap.put("description",String.class); 638 basePropInterfaceMap.put("image",SyndImage.class); 639 basePropInterfaceMap.put("entries",SyndEntry.class); 640 basePropInterfaceMap.put("modules",Module.class); 641 642 Map basePropClassImplMap = new HashMap(); 643 basePropClassImplMap.put(SyndEntry.class,SyndEntryImpl.class); 644 basePropClassImplMap.put(SyndImage.class,SyndImageImpl.class); 645 basePropClassImplMap.put(DCModule.class,DCModuleImpl.class); 646 basePropClassImplMap.put(SyModule.class,SyModuleImpl.class); 647 648 COPY_FROM_HELPER = new CopyFromHelper(SyndFeed.class,basePropInterfaceMap,basePropClassImplMap); 649 } 650 651 /** 652 * Returns the links 653 * <p> 654 * @return Returns the links. 655 */ 656 public List getLinks() { 657 return _links; 658 } 659 660 /** 661 * Set the links 662 * <p> 663 * @param links The links to set. 664 */ 665 public void setLinks(List links) { 666 _links = links; 667 } 668 669 public List getAuthors() { 670 return _authors; 671 } 672 673 public void setAuthors(List authors) { 674 this._authors = authors; 675 } 676 677 /** 678 * Returns the feed author. 679 * <p> 680 * This method is a convenience method, it maps to the Dublin Core module creator. 681 * <p> 682 * @return the feed author, <b>null</b> if none. 683 * 684 */ 685 public String getAuthor() { 686 return getDCModule().getCreator(); 687 } 688 689 /** 690 * Sets the feed author. 691 * <p> 692 * This method is a convenience method, it maps to the Dublin Core module creator. 693 * <p> 694 * @param author the feed author to set, <b>null</b> if none. 695 * 696 */ 697 public void setAuthor(String author) { 698 getDCModule().setCreator(author); 699 } 700 701 public List getContributors() { 702 return _contributors; 703 } 704 705 public void setContributors(List contributors) { 706 this._contributors = contributors; 707 } 708 709 /** 710 * Returns foreign markup found at channel level. 711 * <p> 712 * @return Opaque object to discourage use 713 * 714 */ 715 public Object getForeignMarkup() { 716 return (_foreignMarkup==null) ? (_foreignMarkup=new ArrayList()) : _foreignMarkup; 717 } 718 719 /** 720 * Sets foreign markup found at channel level. 721 * <p> 722 * @param foreignMarkup Opaque object to discourage use 723 * 724 */ 725 public void setForeignMarkup(Object foreignMarkup) { 726 _foreignMarkup = (List)foreignMarkup; 727 } 728 }