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.marshall.sse;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022
023import org.apache.juneau.commons.collections.*;
024import org.apache.juneau.marshall.parser.*;
025import org.apache.juneau.marshall.stream.*;
026
027/**
028 * Parses <c>text/event-stream</c> input (WHATWG Server-Sent Events) into Java objects.
029 *
030 * <h5 class='topic'>Media types</h5>
031 * <p>
032 * Handles <c>Content-Type</c> types: <bc>text/event-stream</bc>.
033 *
034 * <h5 class='topic'>Description</h5>
035 * <p>
036 * Eager parse path — drains the stream into a {@link java.util.List List&lt;SseEvent&gt;}.
037 * For true line-driven streaming over long-lived connections, use {@link SseEventReader} directly.
038 *
039 * <h5 class='section'>Notes:</h5><ul>
040 *    <li class='note'>This class is thread safe and reusable.
041 * </ul>
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Sse">SSE</a>
045 * </ul>
046 */
047@SuppressWarnings({
048   "java:S110", // Inheritance depth acceptable for parser hierarchy
049   "java:S9149", // Per-format static factories intentionally shadow the parent's.
050   "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design.
051})
052public class SseParser extends ReaderParser implements RecordReadable {
053
054   /** Default parser, all default settings. */
055   public static final SseParser DEFAULT = new SseParser(create());
056
057   /**
058    * Creates a new builder for this object.
059    *
060    * @return A new builder.
061    */
062   public static Builder create() {
063      return new Builder();
064   }
065
066   /**
067    * Builder class.
068    */
069   public static class Builder extends ReaderParser.Builder<Builder> {
070
071      private static final Cache<HashKey,SseParser> CACHE = Cache.of(HashKey.class, SseParser.class).build();
072
073      /**
074       * Constructor, default settings.
075       */
076      protected Builder() {
077         consumes(SseSerializer.MEDIA_TYPE);
078         type(SseParser.class);
079      }
080
081      /**
082       * Copy constructor.
083       *
084       * @param copyFrom The builder to copy from. Must not be <jk>null</jk>.
085       */
086      protected Builder(Builder copyFrom) {
087         super(reqnn("copyFrom", copyFrom));
088      }
089
090      /**
091       * Copy constructor.
092       *
093       * @param copyFrom The parser to copy from. Must not be <jk>null</jk>.
094       */
095      protected Builder(SseParser copyFrom) {
096         super(reqnn("copyFrom", copyFrom));
097      }
098
099      @Override /* Overridden from Context.Builder<?> */
100      public HashKey hashKey() {
101         return HashKey.of(super.hashKey());
102      }
103
104      @Override /* Overridden from Context.Builder<?> */
105      public SseParser build() {
106         return cache(CACHE).build(SseParser.class);
107      }
108
109      @Override /* Overridden from Context.Builder<?> */
110      public Builder copy() {
111         return new Builder(this);
112      }
113   }
114
115   /**
116    * Constructor.
117    *
118    * @param builder The builder for this object.
119    */
120   public SseParser(Builder builder) {
121      super(builder);
122   }
123
124   @Override /* Overridden from Context */
125   public Builder copy() {
126      return new Builder(this);
127   }
128
129   @Override /* Overridden from Context */
130   public SseParserSession.Builder createSession() {
131      return SseParserSession.create(this);
132   }
133
134   @Override /* Overridden from Context */
135   public SseParserSession getSession() {
136      return createSession().build();
137   }
138
139   /**
140    * Convenience delegator that opens a {@link RecordReader} over the input using
141    * <b>default session arguments</b> (mirrors {@link #read(Object, Class)}).
142    *
143    * <p>
144    * The real implementation lives on {@link SseParserSession#readRecords(Object)}.  Callers
145    * that need request-derived configuration (locale, timezone, schema, swaps) should call
146    * {@link #createSession()} and invoke {@link SseParserSession#readRecords(Object)} on the
147    * built session instead.
148    *
149    * @param input The input.
150    * @return A new {@link RecordReader} cursor.
151    * @throws IOException If a problem occurred opening the underlying input.
152    */
153   @Override /* RecordReadable */
154   public RecordReader readRecords(Object input) throws IOException {
155      return getSession().readRecords(input);
156   }
157
158   @Override /* RecordReadable */
159   public boolean isRecordStreaming() {
160      return false;
161   }
162}