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.*;
020
021import java.io.*;
022import java.util.*;
023
024import org.apache.juneau.http.header.*;
025import org.apache.juneau.http.response.*;
026import org.apache.juneau.marshall.marshaller.*;
027import org.apache.juneau.rest.server.*;
028import org.apache.juneau.rest.server.util.*;
029
030/**
031 * Response handler for plain-old Java objects when a serializer match is not found and they're asking for plain/text or anything.
032 *
033 * <h5 class='section'>See Also:</h5><ul>
034 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
035 * </ul>
036 */
037@SuppressWarnings({
038   "resource" // PlainTextPojoProcessor manages Closeable resources
039})
040public class PlainTextPojoProcessor implements ResponseProcessor {
041
042   @Override /* Overridden from ResponseProcessor */
043   public int process(RestOpSession opSession) throws IOException, NotAcceptable, BasicHttpException {
044      RestRequest req = opSession.getRequest();
045      RestResponse res = opSession.getResponse();
046      String accept = req.getHeaderParam("Accept").orElse("*/*");
047
048      if (res.getSerializerMatch().isPresent() || ! (ie(accept) || accept.startsWith("text/plain") || accept.contains("*/*")))
049         return NEXT;
050
051      Object o = res.getContent(Object.class);
052
053      if (ie(res.getContentType()))
054         res.setHeader(ContentType.TEXT_PLAIN);
055
056      FinishablePrintWriter w = res.getNegotiatedWriter();
057      if (o == null) {
058         w.append("null");
059      } else if (o instanceof Map || o instanceof Collection || o.getClass().isArray()) {
060         w.append(Json5.of(o));
061      } else if (o instanceof Throwable && ! opSession.getRestContext().isRenderResponseStackTraces()) {
062         // A thrown exception reached the plain-text renderer as the response content.  Emitting its
063         // toString() (class name + message) would leak internal detail to the client, so in production
064         // we write only the generic HTTP status text.  Structured detail remains in the 'Thrown'
065         // response header and server-side logs; enable renderResponseStackTraces for full in-response
066         // detail during development.
067         var statusText = RestUtils.getHttpResponseText(res.getStatus());
068         w.append(statusText == null ? "" : statusText);
069      } else {
070         w.append(req.getMarshallingSession().getClassMetaForObject(o).toString(o));
071      }
072      w.flush();
073      w.finish();
074
075      return FINISHED;
076   }
077}