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.rest.server.processor;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.io.*;
023
024import org.apache.juneau.commons.http.*;
025import org.apache.juneau.http.header.*;
026import org.apache.juneau.http.response.*;
027import org.apache.juneau.marshall.html.*;
028import org.apache.juneau.marshall.httppart.*;
029import org.apache.juneau.marshall.marshaller.*;
030import org.apache.juneau.marshall.serializer.*;
031import org.apache.juneau.rest.server.*;
032import org.apache.juneau.rest.server.util.*;
033
034/**
035 * Response handler for plain-old Java objects.
036 *
037 * <h5 class='section'>See Also:</h5><ul>
038 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
039 * </ul>
040 */
041@SuppressWarnings({
042   "resource" // SerializedPojoProcessor manages Closeable resources
043})
044public class SerializedPojoProcessor implements CatchAllResponseProcessor {
045
046   @Override /* Overridden from ResponseProcessor */
047   @SuppressWarnings({
048      "java:S3776" // Cognitive complexity acceptable for POJO serialization logic
049   })
050   public int process(RestOpSession opSession) throws IOException, NotAcceptable, BasicHttpException {
051      RestRequest req = opSession.getRequest();
052      RestResponse res = opSession.getResponse();
053      SerializerMatch sm = res.getSerializerMatch().orElse(null);
054      HttpPartSchema schema = res.getContentSchema().orElse(null);
055
056      Object o = res.getContent(Object.class);
057
058      if (nn(sm)) {
059         try {
060            Serializer s = sm.getSerializer();
061            MediaType mediaType = res.getMediaType();
062            if (mediaType == null)
063               mediaType = sm.getMediaType();
064
065            MediaType responseType = s.getResponseContentType();
066            if (responseType == null)
067               responseType = mediaType;
068
069            if (req.isPlainText())
070               res.setHeader(ContentType.TEXT_PLAIN);
071            else
072               res.setHeader(ContentType.of(responseType.toString()));
073
074            // @formatter:off
075            SerializerSession session = s
076               .createSession()
077               .properties(req.getSerializerSessionPropertyMap())
078               .javaMethod(req.getOpContext().getJavaMethod())
079               .locale(req.getLocale())
080               .timeZone(req.getTimeZone().orElse(null))
081               .mediaType(mediaType)
082               .apply(WriterSerializerSession.Builder.class, x -> x.streamCharset(res.getCharset()).useWhitespace(req.isPlainText() ? true : null))
083               .schema(schema)
084               .debug(req.isDebugMarshalling() ? true : null)
085               .uriContext(req.getUriContext())
086               .resolver(req.getVarResolverSession())
087               .apply(HtmlDocSerializerSession.Builder.class, x -> req.getAttribute(RestRequest.CSP_NONCE_ATTR).as(String.class).ifPresent(x::nonce))
088               .build();
089            // @formatter:on
090
091            for (var h : session.getResponseHeaders().entrySet())
092               res.addHeader(h.getKey(), h.getValue());
093
094            if (! session.isWriterSerializer()) {
095               if (req.isPlainText()) {
096                  FinishablePrintWriter w = res.getNegotiatedWriter();
097                  var baos = new ByteArrayOutputStream();
098                  session.write(o, baos);
099                  w.write(toSpacedHex(baos.toByteArray()));
100                  w.flush();
101                  w.finish();
102               } else {
103                  FinishableServletOutputStream os = res.getNegotiatedOutputStream();
104                  session.write(o, os);
105                  os.flush();
106                  os.finish();
107               }
108            } else {
109               FinishablePrintWriter w = res.getNegotiatedWriter();
110               session.write(o, w);
111               w.flush();
112               w.finish();
113            }
114         } catch (SerializeException e) {
115            throw new InternalServerError(e);
116         }
117         return FINISHED;
118      }
119
120      if (o == null)
121         return FINISHED;
122
123      throw new NotAcceptable("Unsupported media-type in request header 'Accept': '%s'\n\tSupported media-types: %s", req.getHeaderParam("Accept").orElse(""),
124         Json5.of(res.getOpContext().getSerializers().getSupportedMediaTypes()));
125   }
126}