001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.bean.atom; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021 022import java.util.*; 023 024import org.apache.juneau.commons.time.*; 025import org.apache.juneau.marshall.*; 026 027/** 028 * Represents an individual entry within an Atom feed or as a standalone Atom document. 029 * 030 * <p> 031 * An Atom entry is a discrete item of content within a feed, such as a blog post, news article, 032 * podcast episode, or other individual piece of content. Entries can exist within a feed or be 033 * published as standalone Atom documents. 034 * 035 * <p> 036 * Each entry contains metadata about the content (title, authors, timestamps) and optionally the 037 * content itself or links to it. Entries are designed to be independently meaningful and may be 038 * consumed separately from their containing feed. 039 * 040 * <h5 class='figure'>Schema</h5> 041 * <p class='bschema'> 042 * atomEntry = 043 * element atom:entry { 044 * atomCommonAttributes, 045 * (atomAuthor* 046 * & atomCategory* 047 * & atomContent? 048 * & atomContributor* 049 * & atomId 050 * & atomLink* 051 * & atomPublished? 052 * & atomRights? 053 * & atomSource? 054 * & atomSummary? 055 * & atomTitle 056 * & atomUpdated 057 * & extensionElement*) 058 * } 059 * </p> 060 * 061 * <h5 class='section'>Required Elements:</h5> 062 * <p> 063 * Per RFC 4287, the following elements are required in an entry: 064 * <ul class='spaced-list'> 065 * <li><c>atom:id</c> - A permanent, universally unique identifier for the entry. 066 * <li><c>atom:title</c> - A human-readable title for the entry. 067 * <li><c>atom:updated</c> - The most recent instant in time when the entry was modified. 068 * </ul> 069 * 070 * <h5 class='section'>Recommended Elements:</h5> 071 * <p> 072 * The following elements are recommended: 073 * <ul class='spaced-list'> 074 * <li><c>atom:author</c> - Authors of the entry (required if feed doesn't have authors). 075 * <li><c>atom:content</c> or <c>atom:link[@rel="alternate"]</c> - Either content or alternate link. 076 * <li><c>atom:summary</c> - Brief summary of the entry (required if content is not inline). 077 * </ul> 078 * 079 * <h5 class='section'>Example:</h5> 080 * <p class='bjava'> 081 * <jc>// Create an entry</jc> 082 * Entry <jv>entry</jv> = <jk>new</jk> Entry( 083 * <js>"tag:example.org,2024:entry1"</js>, 084 * <js>"My First Blog Post"</js>, 085 * <js>"2024-01-15T12:00:00Z"</js> 086 * ) 087 * .setAuthors( 088 * <jk>new</jk> Person(<js>"Jane Doe"</js>) 089 * .setEmail(<js>"jane@example.org"</js>) 090 * ) 091 * .setContent( 092 * <jk>new</jk> Content(<js>"html"</js>) 093 * .setText(<js>"<p>This is my first blog post!</p>"</js>) 094 * ) 095 * .setSummary(<js>"An introduction to my new blog"</js>) 096 * .setPublished(<js>"2024-01-15T10:00:00Z"</js>) 097 * .setLinks( 098 * <jk>new</jk> Link(<js>"alternate"</js>, <js>"text/html"</js>, <js>"http://example.org/posts/1"</js>) 099 * ); 100 * </p> 101 * 102 * <h5 class='section'>Specification:</h5> 103 * <p> 104 * Represents an <c>atomEntry</c> construct in the 105 * <a class="doclink" href="https://tools.ietf.org/html/rfc4287#section-4.1.2">RFC 4287 - Section 4.1.2</a> specification. 106 * 107 * <h5 class='section'>See Also:</h5><ul> 108 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanAtom">juneau-bean-atom</a> 109 * <li class='extlink'><a class="doclink" href="https://tools.ietf.org/html/rfc4287">RFC 4287 - The Atom Syndication Format</a> 110 * </ul> 111 */ 112@Marshalled(typeName = "entry") 113public class Entry extends CommonEntry<Entry> { 114 115 private Content content; 116 private Calendar published; 117 private Source source; 118 private Text summary; 119 120 /** Bean constructor. */ 121 public Entry() {} 122 123 /** 124 * Normal constructor. 125 * 126 * @param id The ID of this entry. Can be <jk>null</jk> to leave the property unset. 127 * @param title The title of this entry. Can be <jk>null</jk> to leave the property unset. 128 * @param updated The updated timestamp of this entry. Can be <jk>null</jk> to leave the property unset. 129 */ 130 public Entry(Id id, Text title, Calendar updated) { 131 super(id, title, updated); 132 } 133 134 /** 135 * Normal constructor. 136 * 137 * @param id The ID of this entry. Can be <jk>null</jk>, in which case the property is set to an {@link Id} with no text (not left unset). 138 * @param title The title of this entry. Can be <jk>null</jk>, in which case the property is set to a {@link Text} with no text (not left unset). 139 * @param updated The updated timestamp of this entry. Can be <jk>null</jk> or blank to leave the property unset. 140 */ 141 public Entry(String id, String title, String updated) { 142 super(id, title, updated); 143 } 144 145 /** 146 * Bean property getter: <property>content</property>. 147 * 148 * <p> 149 * Returns the content of this entry, or a link to it. 150 * 151 * <p> 152 * The content element contains or links to the complete content of the entry. It can contain 153 * text, HTML, XHTML, or other media types. When not present, the entry must have an alternate 154 * link pointing to the content. 155 * 156 * @return The property value, or <jk>null</jk> if it is not set. 157 */ 158 public Content getContent() { return content; } 159 160 /** 161 * Bean property getter: <property>published</property>. 162 * 163 * <p> 164 * Returns the time when this entry was first published or made available. 165 * 166 * <p> 167 * This differs from the updated time in that it represents the original publication date, 168 * which typically doesn't change even when the entry is modified. The updated timestamp 169 * reflects the last modification time. 170 * 171 * @return The property value, or <jk>null</jk> if it is not set. 172 */ 173 public Calendar getPublished() { return published; } 174 175 /** 176 * Bean property getter: <property>source</property>. 177 * 178 * <p> 179 * Returns metadata about the source feed if this entry was copied from another feed. 180 * 181 * <p> 182 * When an entry is copied or aggregated from another feed, the source element preserves 183 * metadata from the original feed. This is useful for attribution and tracking the origin 184 * of syndicated content. 185 * 186 * @return The property value, or <jk>null</jk> if it is not set. 187 */ 188 public Source getSource() { return source; } 189 190 /** 191 * Bean property getter: <property>summary</property>. 192 * 193 * <p> 194 * Returns a short summary, abstract, or excerpt of the entry. 195 * 196 * <p> 197 * The summary is typically used in feed readers to give users a preview of the entry's 198 * content without loading the full content. It's especially useful when content is not 199 * inline or is very long. 200 * 201 * @return The property value, or <jk>null</jk> if it is not set. 202 */ 203 public Text getSummary() { return summary; } 204 205 /** 206 * Bean property setter: <property>content</property>. 207 * 208 * <p> 209 * Sets the content of this entry. 210 * 211 * <h5 class='section'>Examples:</h5> 212 * <p class='bjava'> 213 * <jc>// Plain text content</jc> 214 * Entry <jv>entry</jv> = <jk>new</jk> Entry(...) 215 * .setContent( 216 * <jk>new</jk> Content(<js>"text"</js>) 217 * .setText(<js>"This is plain text content"</js>) 218 * ); 219 * 220 * <jc>// HTML content</jc> 221 * Entry <jv>entry2</jv> = <jk>new</jk> Entry(...) 222 * .setContent( 223 * <jk>new</jk> Content(<js>"html"</js>) 224 * .setText(<js>"<p>This is <strong>HTML</strong> content</p>"</js>) 225 * ); 226 * 227 * <jc>// Link to external content</jc> 228 * Entry <jv>entry3</jv> = <jk>new</jk> Entry(...) 229 * .setContent( 230 * <jk>new</jk> Content() 231 * .setType(<js>"video/mp4"</js>) 232 * .setSrc(<js>"http://example.org/video.mp4"</js>) 233 * ); 234 * </p> 235 * 236 * @param value 237 * The new value for this property. 238 * <br>Can be <jk>null</jk> to unset the property. 239 * @return This object. 240 */ 241 public Entry setContent(Content value) { 242 content = value; 243 return this; 244 } 245 246 /** 247 * Bean property setter: <property>published</property>. 248 * 249 * <p> 250 * Sets the time when this entry was first published or made available. 251 * 252 * <h5 class='section'>Example:</h5> 253 * <p class='bjava'> 254 * Entry <jv>entry</jv> = <jk>new</jk> Entry(...) 255 * .setPublished(Calendar.<jsm>getInstance</jsm>()); 256 * </p> 257 * 258 * @param value 259 * The new value for this property. 260 * <br>Can be <jk>null</jk> to unset the property. 261 * @return This object. 262 */ 263 public Entry setPublished(Calendar value) { 264 published = value; 265 return this; 266 } 267 268 /** 269 * Bean property fluent setter: <property>published</property>. 270 * 271 * <p> 272 * Sets the time when this entry was first published using an ISO-8601 date string. 273 * 274 * <h5 class='section'>Example:</h5> 275 * <p class='bjava'> 276 * Entry <jv>entry</jv> = <jk>new</jk> Entry(...) 277 * .setPublished(<js>"2024-01-15T10:00:00Z"</js>); 278 * </p> 279 * 280 * @param value 281 * The new value for this property in ISO-8601 format. 282 * <br>Can be <jk>null</jk> to unset the property. 283 * @return This object. 284 */ 285 public Entry setPublished(String value) { 286 setPublished(o(value).filter(x1 -> ! isBlank(x1)).map(x -> GranularZonedDateTime.of(value).getZonedDateTime()).map(GregorianCalendar::from).orElse(null)); 287 return this; 288 } 289 290 /** 291 * Bean property setter: <property>source</property>. 292 * 293 * <p> 294 * Sets metadata about the source feed if this entry was copied from another feed. 295 * 296 * <h5 class='section'>Example:</h5> 297 * <p class='bjava'> 298 * Entry <jv>entry</jv> = <jk>new</jk> Entry(...) 299 * .setSource( 300 * <jk>new</jk> Source() 301 * .setId(<js>"tag:originalblog.example.com,2024:feed"</js>) 302 * .setTitle(<js>"Original Blog"</js>) 303 * .setUpdated(<js>"2024-01-15T12:00:00Z"</js>) 304 * ); 305 * </p> 306 * 307 * @param value 308 * The new value for this property. 309 * <br>Can be <jk>null</jk> to unset the property. 310 * @return This object. 311 */ 312 public Entry setSource(Source value) { 313 source = value; 314 return this; 315 } 316 317 /** 318 * Bean property fluent setter: <property>summary</property>. 319 * 320 * <p> 321 * Sets a short summary, abstract, or excerpt of the entry as plain text. 322 * 323 * @param value 324 * The new value for this property. 325 * <br>Can be <jk>null</jk> to unset the property. 326 * @return This object. 327 */ 328 public Entry setSummary(String value) { 329 setSummary(new Text(value)); 330 return this; 331 } 332 333 /** 334 * Bean property setter: <property>summary</property>. 335 * 336 * <p> 337 * Sets a short summary, abstract, or excerpt of the entry. 338 * 339 * <h5 class='section'>Example:</h5> 340 * <p class='bjava'> 341 * Entry <jv>entry</jv> = <jk>new</jk> Entry(...) 342 * .setSummary( 343 * <jk>new</jk> Text(<js>"text"</js>) 344 * .setText(<js>"This entry discusses the benefits of Atom feeds..."</js>) 345 * ); 346 * </p> 347 * 348 * @param value 349 * The new value for this property. 350 * <br>Can be <jk>null</jk> to unset the property. 351 * @return This object. 352 */ 353 public Entry setSummary(Text value) { 354 summary = value; 355 return this; 356 } 357}