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 *          &amp; atomCategory*
047 *          &amp; atomContent?
048 *          &amp; atomContributor*
049 *          &amp; atomId
050 *          &amp; atomLink*
051 *          &amp; atomPublished?
052 *          &amp; atomRights?
053 *          &amp; atomSource?
054 *          &amp; atomSummary?
055 *          &amp; atomTitle
056 *          &amp; atomUpdated
057 *          &amp; 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>"&lt;p&gt;This is my first blog post!&lt;/p&gt;"</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>"&lt;p&gt;This is &lt;strong&gt;HTML&lt;/strong&gt; content&lt;/p&gt;"</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}