All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.photon.app.PhotonUnifiedResponse Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.photon.app;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.w3c.dom.Node;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.http.EHttpMethod;
import com.helger.commons.io.stream.NonBlockingByteArrayOutputStream;
import com.helger.commons.mime.CMimeType;
import com.helger.commons.mime.IMimeType;
import com.helger.commons.mime.MimeType;
import com.helger.commons.serialize.SerializationHelper;
import com.helger.commons.string.StringHelper;
import com.helger.commons.url.ISimpleURL;
import com.helger.css.media.ICSSMediaList;
import com.helger.html.hc.IHCConversionSettings;
import com.helger.html.hc.IHCHasChildrenMutable;
import com.helger.html.hc.IHCNode;
import com.helger.html.hc.IHCNodeList;
import com.helger.html.hc.config.HCSettings;
import com.helger.html.hc.config.IHCOnDocumentReadyProvider;
import com.helger.html.hc.html.root.HCHtml;
import com.helger.html.hc.impl.HCNodeList;
import com.helger.html.hc.render.HCRenderer;
import com.helger.html.hc.special.HCSpecialNodeHandler;
import com.helger.html.hc.special.HCSpecialNodes;
import com.helger.html.hc.special.IHCSpecialNodes;
import com.helger.html.resource.css.ICSSCodeProvider;
import com.helger.html.resource.css.ICSSPathProvider;
import com.helger.html.resource.js.IJSPathProvider;
import com.helger.http.EHttpVersion;
import com.helger.json.IJson;
import com.helger.json.IJsonArray;
import com.helger.json.IJsonObject;
import com.helger.json.JsonArray;
import com.helger.json.JsonObject;
import com.helger.json.serialize.IJsonWriterSettings;
import com.helger.json.serialize.JsonWriterSettings;
import com.helger.photon.app.html.PhotonCSS;
import com.helger.photon.app.html.PhotonHTMLHelper;
import com.helger.photon.app.html.PhotonJS;
import com.helger.servlet.mock.MockHttpServletRequest;
import com.helger.servlet.request.RequestHelper;
import com.helger.servlet.response.EContentDispositionType;
import com.helger.servlet.response.ERedirectMode;
import com.helger.servlet.response.UnifiedResponse;
import com.helger.web.scope.IRequestWebScopeWithoutResponse;
import com.helger.xml.microdom.IMicroNode;
import com.helger.xml.microdom.serialize.MicroWriter;
import com.helger.xml.serialize.write.IXMLWriterSettings;
import com.helger.xml.serialize.write.XMLWriter;
import com.helger.xml.serialize.write.XMLWriterSettings;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * Extends the {@link UnifiedResponse} with additional sanity methods for easier
 * pratical use.
 *
 * @author Philip Helger
 */
public class PhotonUnifiedResponse extends UnifiedResponse
{
  private final IRequestWebScopeWithoutResponse m_aRequestScope;
  private IXMLWriterSettings m_aXWS = XMLWriterSettings.DEFAULT_XML_SETTINGS;
  private IJsonWriterSettings m_aJWS = JsonWriterSettings.DEFAULT_SETTINGS;

  public PhotonUnifiedResponse (@Nonnull final EHttpVersion eHttpVersion,
                                @Nonnull final EHttpMethod eHttpMethod,
                                @Nonnull final HttpServletRequest aHttpRequest,
                                @Nonnull final IRequestWebScopeWithoutResponse aRequestScope)
  {
    super (eHttpVersion, eHttpMethod, aHttpRequest);
    m_aRequestScope = aRequestScope;
  }

  protected final IRequestWebScopeWithoutResponse getRequestScope ()
  {
    return m_aRequestScope;
  }

  @Nonnull
  public final IXMLWriterSettings getXMLWriterSettings ()
  {
    return m_aXWS;
  }

  public final void setXMLWriterSettings (@Nonnull final IXMLWriterSettings aXWS)
  {
    ValueEnforcer.notNull (aXWS, "XWS");
    m_aXWS = aXWS;
  }

  @Nonnull
  public final IJsonWriterSettings getJsonWriterSettings ()
  {
    return m_aJWS;
  }

  public final void setJsonWriterSettings (@Nonnull final IJsonWriterSettings aJWS)
  {
    ValueEnforcer.notNull (aJWS, "JWS");
    m_aJWS = aJWS;
  }

  @Nonnull
  public PhotonUnifiedResponse jsonEmpty ()
  {
    return json (null);
  }

  @Nonnull
  public PhotonUnifiedResponse json (@Nullable final IJson aValue)
  {
    // Ensure it is valid JSON
    final String sResponse = aValue != null ? aValue.getAsJsonString (m_aJWS) : "{}";
    final Charset aCharset = StandardCharsets.UTF_8;
    setContentAndCharset (sResponse, aCharset);
    setMimeType (new MimeType (CMimeType.APPLICATION_JSON).addParameter (CMimeType.PARAMETER_NAME_CHARSET,
                                                                         aCharset.name ()));
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse xml (@Nullable final String sXML, @Nonnull final Charset aCharset)
  {
    setContentAndCharset (StringHelper.getNotNull (sXML), aCharset);
    setMimeType (new MimeType (CMimeType.APPLICATION_XML).addParameter (CMimeType.PARAMETER_NAME_CHARSET,
                                                                        aCharset.name ()));
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse xml (@Nonnull final byte [] aXML, @Nonnull final Charset aCharset)
  {
    setContent (aXML);
    setCharset (aCharset);
    setMimeType (new MimeType (CMimeType.APPLICATION_XML).addParameter (CMimeType.PARAMETER_NAME_CHARSET,
                                                                        aCharset.name ()));
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse xml (@Nullable final IMicroNode aNode)
  {
    return xml (aNode, m_aXWS);
  }

  @Nonnull
  public PhotonUnifiedResponse xml (@Nullable final IMicroNode aNode, @Nonnull final IXMLWriterSettings aSettings)
  {
    return xml (aNode == null ? null : MicroWriter.getNodeAsString (aNode, aSettings), aSettings.getCharset ());
  }

  @Nonnull
  public PhotonUnifiedResponse xml (@Nullable final Node aNode)
  {
    return xml (aNode, m_aXWS);
  }

  @Nonnull
  public PhotonUnifiedResponse xml (@Nullable final Node aNode, @Nonnull final IXMLWriterSettings aSettings)
  {
    return xml (aNode == null ? null : XMLWriter.getNodeAsString (aNode, aSettings), aSettings.getCharset ());
  }

  @Nonnull
  public PhotonUnifiedResponse text (@Nullable final String sValue)
  {
    setContentAndCharset (StringHelper.getNotNull (sValue), StandardCharsets.UTF_8);
    setMimeType (CMimeType.TEXT_PLAIN);
    return this;
  }

  public static final class HtmlHelper
  {
    /**
     * Response value property - only in case of success - contains the response
     * data as object
     */
    public static final String PROPERTY_VALUE = "value";
    /**
     * Additional CSS files - only in case of success - contains a list of
     * strings
     */
    public static final String PROPERTY_EXTERNAL_CSS = "externalcss";
    /** Additional inline CSS - only in case of success - contains a string */
    public static final String PROPERTY_INLINE_CSS_BEFORE_EXTERNAL = "inlinecssBeforeExternal";
    /** Additional inline CSS - only in case of success - contains a string */
    public static final String PROPERTY_INLINE_CSS_AFTER_EXTERNAL = "inlinecssAfterExternal";
    /** The sub key for CSS elements specifying the media list */
    public static final String SUBPROPERTY_CSS_MEDIA = "media";
    /** The sub key for CSS elements specifying the external CSS href */
    public static final String SUBPROPERTY_CSS_HREF = "href";
    /** The sub key for CSS elements specifying the inline CSS content */
    public static final String SUBPROPERTY_CSS_CONTENT = "content";
    /**
     * Additional JS files - only in case of success - contains a list of
     * strings
     */
    public static final String PROPERTY_EXTERNAL_JS = "externaljs";
    /** Additional inline JS - only in case of success - contains a string */
    public static final String PROPERTY_INLINE_JS_BEFORE_EXTERNAL = "inlinejsBeforeExternal";
    /** Additional inline JS - only in case of success - contains a string */
    public static final String PROPERTY_INLINE_JS_AFTER_EXTERNAL = "inlinejsAfterExternal";
    /** Default property for HTML content */
    public static final String PROPERTY_HTML = "html";

    private HtmlHelper ()
    {}

    @Nonnull
    public static String getHTMLString (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope,
                                        @Nullable final IHCHasChildrenMutable  aNode,
                                        @Nonnull final HCSpecialNodes aSpecialNodes,
                                        @Nullable final IHCOnDocumentReadyProvider aOnDocumentReadyProvider)
    {
      ValueEnforcer.notNull (aRequestScope, "RequestScope");
      ValueEnforcer.notNull (aSpecialNodes, "SpecialNodes");
      if (aNode == null)
        return "";

      final IHCConversionSettings aConversionSettings = HCSettings.getConversionSettingsWithoutNamespaces ();

      IHCNode aTargetNode = aNode;

      // Special handling for complete HCHtml objects needed
      if (aNode instanceof IHCNodeList  && aNode.getChildCount () == 1 && aNode.getFirstChild () instanceof HCHtml)
      {
        final HCHtml aHtml = (HCHtml) aNode.getFirstChild ();
        aTargetNode = aHtml;

        // customize, finalize and extract resources
        // This must be done before the CSS and JS are included because
        // per-request
        // resource registration happens inside
        HCRenderer.prepareForConversion (aHtml, aHtml.body (), aConversionSettings);

        // Extract and merge all inline out-of-band nodes
        if (aConversionSettings.isExtractOutOfBandNodes ())
        {
          final ICommonsList  aOOBNodes = aHtml.getAllOutOfBandNodesWithMergedInlineNodes ();
          aHtml.addAllOutOfBandNodesToHead (aOOBNodes);
        }

        PhotonHTMLHelper.mergeExternalCSSAndJSNodes (aRequestScope,
                                                     aHtml.head (),
                                                     PhotonAppSettings.isMergeCSSResources (),
                                                     PhotonAppSettings.isMergeJSResources (),
                                                     PhotonAppManager.getWebSiteResourceBundleMgr ());

        // Move scripts to body? If so, after aggregation!
        if (HCSettings.isScriptsInBody ())
          aHtml.moveScriptElementsToBody ();
      }
      else
      {
        // customize, finalize and extract resources
        // Non-HTML node
        HCRenderer.prepareForConversion (aNode, aNode, aConversionSettings);

        if (aConversionSettings.isExtractOutOfBandNodes ())
        {
          HCSpecialNodeHandler.extractSpecialContent (aNode, aSpecialNodes, aOnDocumentReadyProvider);
        }
      }

      // Serialize remaining node to HTML
      final IMicroNode aMicroNode = aTargetNode.convertToMicroNode (aConversionSettings);
      return aMicroNode == null ? "" : MicroWriter.getNodeAsString (aMicroNode,
                                                                    aConversionSettings.getXMLWriterSettings ());
    }

    public static void addCSSAndJS (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope,
                                    @Nonnull final HCSpecialNodes aSpecialNodes)
    {
      ValueEnforcer.notNull (aRequestScope, "RequestScope");

      // Grab per-request CSS/JS only in success case!
      // Grab all CSS/JS independent of conditional comment :(
      final boolean bRegular = HCSettings.isUseRegularResources ();

      for (final ICSSPathProvider aCSS : PhotonCSS.getAllRegisteredCSSIncludesForThisRequest ())
        aSpecialNodes.addExternalCSS (aCSS.getMediaList (),
                                      PhotonAppSettings.getCSSPath (aRequestScope, aCSS, bRegular)
                                                       .getAsStringWithEncodedParameters ());

      for (final IJSPathProvider aJS : PhotonJS.getAllRegisteredJSIncludesForThisRequest ())
        aSpecialNodes.addExternalJS (PhotonAppSettings.getJSPath (aRequestScope, aJS, bRegular)
                                                      .getAsStringWithEncodedParameters ());
    }

    @Nonnull
    public static IJsonObject getResponseAsJSON (@Nullable final IJsonObject aSuccessValue,
                                                 @Nonnull final IHCSpecialNodes aSpecialNodes)
    {
      final IJsonObject aAssocArray = new JsonObject ();
      if (aSuccessValue != null)
        aAssocArray.addJson (PROPERTY_VALUE, aSuccessValue);

      // Apply special nodes
      if (aSpecialNodes.hasExternalCSSs ())
      {
        final IJsonArray aList = new JsonArray ();
        for (final Map.Entry > aEntry : aSpecialNodes.getAllExternalCSSs ()
                                                                                          .entrySet ())
          for (final String sCSSFile : aEntry.getValue ())
            aList.add (new JsonObject ().add (SUBPROPERTY_CSS_MEDIA, aEntry.getKey ().getMediaString ())
                                        .add (SUBPROPERTY_CSS_HREF, sCSSFile));
        aAssocArray.addJson (PROPERTY_EXTERNAL_CSS, aList);
      }
      if (aSpecialNodes.hasInlineCSSBeforeExternal ())
      {
        final IJsonArray aList = new JsonArray ();
        for (final ICSSCodeProvider aEntry : aSpecialNodes.getAllInlineCSSBeforeExternal ())
          aList.add (new JsonObject ().add (SUBPROPERTY_CSS_MEDIA, aEntry.getMediaList ().getMediaString ())
                                      .add (SUBPROPERTY_CSS_CONTENT, aEntry.getCSSCode ()));
        aAssocArray.addJson (PROPERTY_INLINE_CSS_BEFORE_EXTERNAL, aList);
      }
      if (aSpecialNodes.hasInlineCSSAfterExternal ())
      {
        final IJsonArray aList = new JsonArray ();
        for (final ICSSCodeProvider aEntry : aSpecialNodes.getAllInlineCSSAfterExternal ())
          aList.add (new JsonObject ().add (SUBPROPERTY_CSS_MEDIA, aEntry.getMediaList ().getMediaString ())
                                      .add (SUBPROPERTY_CSS_CONTENT, aEntry.getCSSCode ()));
        aAssocArray.addJson (PROPERTY_INLINE_CSS_AFTER_EXTERNAL, aList);
      }
      if (aSpecialNodes.hasExternalJSs ())
        aAssocArray.add (PROPERTY_EXTERNAL_JS, aSpecialNodes.getAllExternalJSs ());
      if (aSpecialNodes.hasInlineJSBeforeExternal ())
        aAssocArray.add (PROPERTY_INLINE_JS_BEFORE_EXTERNAL, aSpecialNodes.getInlineJSBeforeExternal ().getJSCode ());
      if (aSpecialNodes.hasInlineJSAfterExternal ())
        aAssocArray.add (PROPERTY_INLINE_JS_AFTER_EXTERNAL, aSpecialNodes.getInlineJSAfterExternal ().getJSCode ());
      return aAssocArray;
    }
  }

  @SuppressWarnings ("unchecked")
  @Nonnull
  public PhotonUnifiedResponse html (@Nullable final IHCNode aNode)
  {
    return html (aNode == null ? null : aNode instanceof IHCHasChildrenMutable 
                                                                                      ? (IHCHasChildrenMutable ) aNode
                                                                                      : new HCNodeList ().addChild (aNode),
                 null,
                 null);
  }

  @Nonnull
  public PhotonUnifiedResponse html (@Nullable final IHCHasChildrenMutable  aNode,
                                     @Nullable final IHCOnDocumentReadyProvider aOnDocumentReadyProvider,
                                     @Nullable final IJsonObject aCustomJson)
  {
    final HCSpecialNodes aSpecialNodes = new HCSpecialNodes ();

    // Now decompose the HCNode itself and set it in "html" property
    final IJsonObject aObj = new JsonObject ();
    // First extract the HTML
    aObj.add (HtmlHelper.PROPERTY_HTML,
              HtmlHelper.getHTMLString (m_aRequestScope, aNode, aSpecialNodes, aOnDocumentReadyProvider));
    // Do it after all nodes were finalized etc
    HtmlHelper.addCSSAndJS (m_aRequestScope, aSpecialNodes);

    // Add custom json
    aObj.addAll (aCustomJson);

    final IJsonObject ret = HtmlHelper.getResponseAsJSON (aObj, aSpecialNodes);
    return json (ret);
  }

  /**
   * Create a simple HTML response without JSON structuring
   *
   * @param aNode
   *        The node to be rendered. May be null.
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse htmlSimple (@Nullable final IHCNode aNode)
  {
    if (aNode == null)
      setContentAndCharset ("", HCSettings.getHTMLCharset ());
    else
      setContentAndCharset (HCRenderer.getAsHTMLStringWithoutNamespaces (aNode), HCSettings.getHTMLCharset ());
    setMimeType (PhotonHTMLHelper.getMimeType (m_aRequestScope));
    return this;
  }

  /**
   * HTTP 200 OK
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createOk ()
  {
    setStatus (HttpServletResponse.SC_OK);
    return this;
  }

  /**
   * HTTP 202 Accepted
   *
   * @return this for chaining
   * @since 8.1.3
   */
  @Nonnull
  public PhotonUnifiedResponse createAccepted ()
  {
    setStatus (HttpServletResponse.SC_ACCEPTED);
    return this;
  }

  /**
   * HTTP 204 No Content
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createNoContent ()
  {
    setStatus (HttpServletResponse.SC_NO_CONTENT);
    return this;
  }

  /**
   * HTTP 303 See Other
   *
   * @param aRedirectTargetURL
   *        The redirect URL. May not be null.
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createSeeOther (@Nonnull final ISimpleURL aRedirectTargetURL)
  {
    ValueEnforcer.notNull (aRedirectTargetURL, "Location");

    setRedirect (aRedirectTargetURL, ERedirectMode.POST_REDIRECT_GET);
    return this;
  }

  /**
   * HTTP 400 Bad Request
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createBadRequest ()
  {
    setStatus (HttpServletResponse.SC_BAD_REQUEST);
    return this;
  }

  /**
   * HTTP 404 Not Found
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createNotFound ()
  {
    setStatus (HttpServletResponse.SC_NOT_FOUND);
    return this;
  }

  /**
   * HTTP 409 Conflict
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createConflict ()
  {
    setStatus (HttpServletResponse.SC_CONFLICT);
    return this;
  }

  /**
   * HTTP 412 Precondition Failed
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createPreconditionFailed ()
  {
    setStatus (HttpServletResponse.SC_PRECONDITION_FAILED);
    return this;
  }

  /**
   * HTTP 500 Internal Server Error
   *
   * @return this for chaining
   */
  @Nonnull
  public PhotonUnifiedResponse createInternalServerError ()
  {
    setStatus (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse setContent (@Nonnull final NonBlockingByteArrayOutputStream aBAOS)
  {
    setContent (aBAOS.directGetBuffer (), 0, aBAOS.size ());
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse serialized (@Nonnull final Serializable aObj)
  {
    setContent (SerializationHelper.getSerializedByteArray (aObj));
    setMimeType (CMimeType.APPLICATION_OCTET_STREAM);
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse pdf (@Nonnull final NonBlockingByteArrayOutputStream aBAOS)
  {
    setContent (aBAOS);
    setMimeType (CMimeType.APPLICATION_PDF);
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse pdf (@Nonnull final byte [] aData, @Nonnull @Nonempty final String sFilename)
  {
    setContent (aData);
    setMimeType (CMimeType.APPLICATION_PDF);
    setContentDispositionFilename (sFilename);
    setContentDispositionType (EContentDispositionType.INLINE);
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse pdf (@Nonnull final NonBlockingByteArrayOutputStream aBAOS,
                                    @Nonnull @Nonempty final String sFilename)
  {
    pdf (aBAOS);
    setContentDispositionFilename (sFilename);
    setContentDispositionType (EContentDispositionType.INLINE);
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse attachment (@Nonnull @Nonempty final String sFilename)
  {
    setContentDispositionFilename (sFilename);
    setContentDispositionType (EContentDispositionType.ATTACHMENT);
    return this;
  }

  @Nonnull
  public PhotonUnifiedResponse binary (@Nonnull final NonBlockingByteArrayOutputStream aBAOS,
                                       @Nonnull final IMimeType aMimeType,
                                       @Nonnull @Nonempty final String sFilename)
  {
    setContent (aBAOS);
    setMimeType (aMimeType);
    return attachment (sFilename);
  }

  /**
   * Factory method
   *
   * @param aRequestScope
   *        The current request scope. May not be null.
   * @return New {@link PhotonUnifiedResponse}. Never null.
   */
  @Nonnull
  public static PhotonUnifiedResponse createSimple (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope)
  {
    final HttpServletRequest aHttpRequest = aRequestScope.getRequest ();
    if (aHttpRequest instanceof MockHttpServletRequest)
    {
      // No version and no method present
      return new PhotonUnifiedResponse (EHttpVersion.HTTP_11, EHttpMethod.GET, aHttpRequest, aRequestScope);
    }
    return new PhotonUnifiedResponse (RequestHelper.getHttpVersion (aHttpRequest),
                                      RequestHelper.getHttpMethod (aHttpRequest),
                                      aHttpRequest,
                                      aRequestScope);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy