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.plaintext; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.io.*; 022import java.util.*; 023import java.util.concurrent.*; 024 025import org.apache.juneau.commons.bean.*; 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.marshall.*; 028import org.apache.juneau.marshall.parser.*; 029import org.apache.juneau.marshall.stream.*; 030import org.apache.juneau.marshall.swap.spi.*; 031 032/** 033 * Parsers HTTP plain text request bodies into Group 5 POJOs. 034 * 035 * <p> 036 * See <a class="doclink" href="https://juneau.apache.org/docs/topics/PojoCategories">POJO Categories</a>. 037 * 038 * <h5 class='topic'>Media types</h5> 039 * 040 * Handles <c>Accept</c> types: <bc>text/plain</bc> 041 * <p> 042 * Produces <c>Content-Type</c> types: <bc>text/plain</bc> 043 * 044 * <h5 class='topic'>Description</h5> 045 * 046 * Essentially just converts plain text to POJOs via static <c>fromString()</c> or <c>valueOf()</c>, or 047 * through constructors that take a single string argument. 048 * 049 * <p> 050 * Also parses objects using a transform if the object class has an {@link ObjectSwap ObjectSwap<?,String>} transform 051 * defined on it. 052 * 053 * <h5 class='section'>Notes:</h5><ul> 054 * <li class='note'>This class is thread safe and reusable. 055 * </ul> 056 * 057 */ 058@SuppressWarnings({ 059 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 060 "java:S9149", // Per-format static factories intentionally shadow the parent's. 061 "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design. 062}) 063public class PlainTextParser extends ReaderParser implements PlainTextMetaProvider, RecordReadable { 064 065 /** 066 * Builder class. 067 */ 068 public static class Builder extends ReaderParser.Builder<Builder> { 069 070 private static final Cache<HashKey,PlainTextParser> CACHE = Cache.of(HashKey.class, PlainTextParser.class).build(); 071 072 /** 073 * Constructor, default settings. 074 */ 075 protected Builder() { 076 consumes("text/plain"); 077 } 078 079 /** 080 * Copy constructor. 081 * 082 * @param copyFrom The builder to copy from. 083 * <br>Cannot be <jk>null</jk>. 084 */ 085 protected Builder(Builder copyFrom) { 086 super(reqnn("copyFrom", copyFrom)); 087 } 088 089 /** 090 * Copy constructor. 091 * 092 * @param copyFrom The bean to copy from. 093 * <br>Cannot be <jk>null</jk>. 094 */ 095 protected Builder(PlainTextParser copyFrom) { 096 super(reqnn("copyFrom", copyFrom)); 097 } 098 099 @Override /* Overridden from Context.Builder<?> */ 100 public PlainTextParser build() { 101 return cache(CACHE).build(PlainTextParser.class); 102 } 103 104 @Override /* Overridden from Context.Builder<?> */ 105 public Builder copy() { 106 return new Builder(this); 107 } 108 109 110 } 111 112 /** Default parser, all default settings.*/ 113 public static final PlainTextParser DEFAULT = new PlainTextParser(create()); 114 115 /** 116 * Creates a new builder for this object. 117 * 118 * @return A new builder. 119 */ 120 public static Builder create() { 121 return new Builder(); 122 } 123 124 private final Map<ClassMeta<?>,PlainTextClassMeta> plainTextClassMetas = new ConcurrentHashMap<>(); 125 private final Map<BeanPropertyMeta,PlainTextBeanPropertyMeta> plainTextBeanPropertyMetas = new ConcurrentHashMap<>(); 126 127 /** 128 * Constructor. 129 * 130 * @param builder The builder for this object. 131 */ 132 public PlainTextParser(Builder builder) { 133 super(builder); 134 } 135 136 @Override /* Overridden from Context */ 137 public Builder copy() { 138 return new Builder(this); 139 } 140 141 @Override /* Overridden from Context */ 142 public PlainTextParserSession.Builder createSession() { 143 return PlainTextParserSession.create(this); 144 } 145 146 @Override /* Overridden from PlainTextMetaProvider */ 147 public PlainTextBeanPropertyMeta getPlainTextBeanPropertyMeta(BeanPropertyMeta bpm) { 148 if (bpm == null) 149 return PlainTextBeanPropertyMeta.DEFAULT; 150 return plainTextBeanPropertyMetas.computeIfAbsent(bpm, k -> new PlainTextBeanPropertyMeta(k.getDelegateFor(), this)); 151 } 152 153 @Override /* Overridden from PlainTextMetaProvider */ 154 public PlainTextClassMeta getPlainTextClassMeta(ClassMeta<?> cm) { 155 return plainTextClassMetas.computeIfAbsent(cm, k -> new PlainTextClassMeta(k, this)); 156 } 157 158 @Override /* Overridden from Context */ 159 public PlainTextParserSession getSession() { return createSession().build(); } 160 161 /** 162 * Convenience delegator that opens a {@link RecordReader} over the input using 163 * <b>default session arguments</b> (mirrors {@link #read(Object, Class)}). 164 * 165 * <p> 166 * The real implementation lives on {@link PlainTextParserSession#readRecords(Object)}. Callers 167 * that need request-derived configuration (locale, timezone, schema, swaps) should call 168 * {@link #createSession()} and invoke {@link PlainTextParserSession#readRecords(Object)} on the 169 * built session instead. 170 * 171 * @param input The input. 172 * @return A new {@link RecordReader} cursor. 173 * @throws IOException If a problem occurred opening the underlying input. 174 */ 175 @Override /* RecordReadable */ 176 public RecordReader readRecords(Object input) throws IOException { 177 return getSession().readRecords(input); 178 } 179 180 @Override /* RecordReadable */ 181 public boolean isRecordStreaming() { 182 return false; 183 } 184}