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

com.helger.photon.api.InvokableAPIDescriptor 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.api;

import java.io.Serializable;
import java.util.Locale;

import javax.annotation.Nonnull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.impl.ICommonsOrderedMap;
import com.helger.commons.http.CHttp;
import com.helger.commons.mime.MimeType;
import com.helger.commons.mime.MimeTypeParser;
import com.helger.commons.mutable.MutableInt;
import com.helger.commons.string.ToStringGenerator;
import com.helger.servlet.response.UnifiedResponse;
import com.helger.web.scope.IRequestWebScopeWithoutResponse;

/**
 * An {@link InvokableAPIDescriptor} contains an {@link IAPIDescriptor} as well
 * the original path by which it was called as well as any resolved path
 * variables.
 *
 * @author Philip Helger
 */
public class InvokableAPIDescriptor implements Serializable
{
  private static final Logger LOGGER = LoggerFactory.getLogger (InvokableAPIDescriptor.class);

  private final IAPIDescriptor m_aDescriptor;
  private final String m_sPath;
  private final ICommonsOrderedMap  m_aPathVariables;

  /**
   * Constructor
   *
   * @param aDescriptor
   *        The matching {@link IAPIDescriptor}. Never null.
   * @param sPath
   *        The URL path requested by the user, relative to the servlet.
   * @param aPathVariables
   *        All resolved path variables, if the path descriptor contains
   *        variable elements.
   */
  public InvokableAPIDescriptor (@Nonnull final IAPIDescriptor aDescriptor,
                                 @Nonnull @Nonempty final String sPath,
                                 @Nonnull final ICommonsOrderedMap  aPathVariables)
  {
    ValueEnforcer.notNull (aDescriptor, "Descriptor");
    ValueEnforcer.notEmpty (sPath, "Path");
    ValueEnforcer.notNull (aPathVariables, "PathVariables");

    m_aDescriptor = aDescriptor;
    m_sPath = sPath;
    m_aPathVariables = aPathVariables.getClone ();
  }

  /**
   * @return The original API descriptor. Never null.
   */
  @Nonnull
  public final IAPIDescriptor getAPIDescriptor ()
  {
    return m_aDescriptor;
  }

  /**
   * @return The URL path requested by the user, relative to the servlet. Never
   *         null nor empty.
   */
  @Nonnull
  @Nonempty
  public final String getPath ()
  {
    return m_sPath;
  }

  /**
   * @return All resolved path variables, if the path descriptor contains
   *         variable elements. Never null but maybe empty.
   */
  @Nonnull
  @ReturnsMutableCopy
  public final ICommonsOrderedMap  getAllPathVariables ()
  {
    return m_aPathVariables.getClone ();
  }

  /**
   * Check if all pre-requisites are handled correctly. This checks if all
   * required headers and params are present.
   *
   * @param aRequestScope
   *        The request scope to validate.
   * @param aStatusCode
   *        The mutable int to hold the status code to be returned in case of
   *        error. Default is HTTP 400, bad request.
   * @return true if all prerequisites are fulfilled.
   */
  public boolean canExecute (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope,
                             @Nonnull final MutableInt aStatusCode)
  {
    if (aRequestScope == null)
      return false;

    // Note: HTTP method was already checked in APIDescriptorList

    // Check required headers
    for (final String sRequiredHeader : m_aDescriptor.requiredHeaders ())
      if (aRequestScope.headers ().getFirstHeaderValue (sRequiredHeader) == null)
      {
        LOGGER.warn ("Request '" + m_sPath + "' is missing required HTTP header '" + sRequiredHeader + "'");
        return false;
      }

    // Check required parameters
    for (final String sRequiredParam : m_aDescriptor.requiredParams ())
      if (!aRequestScope.params ().containsKey (sRequiredParam))
      {
        LOGGER.warn ("Request '" + m_sPath + "' is missing required HTTP parameter '" + sRequiredParam + "'");
        return false;
      }

    // Check explicit filter
    if (m_aDescriptor.hasExecutionFilter ())
      if (!m_aDescriptor.getExecutionFilter ().canExecute (aRequestScope))
      {
        LOGGER.warn ("Request '" + m_sPath + "' cannot be executed because of ExecutionFilter");
        return false;
      }

    if (m_aDescriptor.allowedMimeTypes ().isNotEmpty ())
    {
      final String sContentType = aRequestScope.getContentType ();
      // Parse to extract any parameters etc.
      final MimeType aMT = MimeTypeParser.safeParseMimeType (sContentType);
      // If parsing fails, use the provided String
      final String sMimeTypeToCheck = aMT == null ? sContentType : aMT.getAsStringWithoutParameters ();

      if (!m_aDescriptor.allowedMimeTypes ().contains (sMimeTypeToCheck) &&
          !m_aDescriptor.allowedMimeTypes ().contains (sMimeTypeToCheck.toLowerCase (Locale.US)))
      {
        LOGGER.warn ("Request '" +
                     m_sPath +
                     "' contains the Content-Type '" +
                     sContentType +
                     "' which is not in the allowed list of " +
                     m_aDescriptor.allowedMimeTypes ());
        // Special error code HTTP 415
        aStatusCode.set (CHttp.HTTP_UNSUPPORTED_MEDIA_TYPE);
        return false;
      }
    }

    return true;
  }

  /**
   * Invoke the Java callback underlying this API descriptor. Note: this method
   * may only be invoked after
   * {@link #canExecute(IRequestWebScopeWithoutResponse,MutableInt)} returned
   * true!
   *
   * @param aRequestScope
   *        Current request scope. Never null.
   * @param aUnifiedResponse
   *        Current response. Never null.
   * @throws Exception
   *         In case the Java callback throws one
   * @throws IllegalStateException
   *         In case the executor factory creates a null executor.
   */
  public void invokeAPI (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope,
                         @Nonnull final UnifiedResponse aUnifiedResponse) throws Exception
  {
    final IAPIExecutor aExecutor = m_aDescriptor.getExecutorFactory ().get ();
    if (aExecutor == null)
      throw new IllegalStateException ("Failed to created API executor for: " + toString ());

    // Go go go
    aExecutor.invokeAPI (m_aDescriptor, m_sPath, m_aPathVariables, aRequestScope, aUnifiedResponse);
  }

  @Override
  public String toString ()
  {
    return new ToStringGenerator (this).append ("APIDescriptor", m_aDescriptor)
                                       .append ("Path", m_sPath)
                                       .append ("PathVariables", m_aPathVariables)
                                       .getToString ();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy