content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.ambari.server.controller;
  
  import java.io.IOException;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import jakarta.inject.Inject;
  import jakarta.inject.Provider;
  import jakarta.inject.Singleton;
  import jakarta.servlet.ServletException;
  import jakarta.servlet.http.HttpServletRequest;
  import jakarta.servlet.http.HttpServletResponse;
  
  import org.apache.ambari.server.api.AmbariErrorHandler;
  import org.apache.ambari.server.api.AmbariPersistFilter;
  import org.apache.ambari.server.api.AmbariViewErrorHandlerProxy;
  import org.apache.ambari.server.configuration.Configuration;
  import org.apache.ambari.server.orm.entities.ViewEntity;
  import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
  import org.apache.ambari.server.security.AmbariViewsSecurityHeaderFilter;
  import org.apache.ambari.server.view.ViewContextImpl;
  import org.apache.ambari.server.view.ViewInstanceHandlerList;
  import org.apache.ambari.server.view.ViewRegistry;
  import org.apache.ambari.view.SystemException;
  import org.apache.ambari.view.ViewContext;
  import org.eclipse.jetty.server.Handler;
  import org.eclipse.jetty.server.Request;
  import org.eclipse.jetty.server.handler.ErrorHandler;
  import org.eclipse.jetty.server.handler.HandlerCollection;
  import org.eclipse.jetty.server.session.SessionCache;
  import org.eclipse.jetty.server.session.SessionHandler;
  import org.eclipse.jetty.servlet.FilterHolder;
  import org.eclipse.jetty.webapp.WebAppContext;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.web.filter.DelegatingFilterProxy;
  
  import javassist.util.proxy.ProxyFactory;
  
  /**
   * An Ambari specific extension of the FailsafeHandlerList that allows for the addition
   * of view instances as handlers.
   */
  @Singleton
  public class AmbariHandlerList extends HandlerCollection implements ViewInstanceHandlerList {
  
    /**
     * The target pattern for a view resource request.
     */
    private static final Pattern VIEW_RESOURCE_TARGET_PATTERN =
      Pattern.compile("/api/(\\S+)/views/(\\S+)/versions/(\\S+)/instances/(\\S+)/resources/(\\S+)");
  
    /**
     * The view registry.
     */
    @Inject
    ViewRegistry viewRegistry;
  
    /**
     * Session manager.
     */
    @Inject
    SessionHandler sessionHandler;
  
    /**
     * The web app context provider.
     */
    @Inject
    Provider<WebAppContext> webAppContextProvider;
  
    /**
     * The persistence filter.
     */
    @Inject
    AmbariPersistFilter persistFilter;
  
    /**
     * The security filter.
     */
    @Inject
    DelegatingFilterProxy springSecurityFilter;
  
    /**
     * The security header filter - conditionally adds security-related headers to the HTTP response for Ambari Views requests.
     */
    @Inject
    AmbariViewsSecurityHeaderFilter ambariViewsSecurityHeaderFilter;
  
    @Inject
    SessionHandlerConfigurer sessionHandlerConfigurer;
  
    @Inject
    Configuration configuration;
  
    @Inject
    AmbariErrorHandler ambariErrorHandler;
  
  
    /**
     * Mapping of view instance entities to handlers.
     */
    private final Map<ViewInstanceEntity, WebAppContext> viewHandlerMap = new HashMap<>();
  
    /**
     * The non-view handlers.
     */
    private final Collection<Handler> nonViewHandlers = new HashSet<>();
  
    private static final Logger LOG = LoggerFactory.getLogger(AmbariHandlerList.class);
  
  
    // ----- Constructors ------------------------------------------------------
  
    /**
     * Construct an AmbariHandlerList.
     */
    public AmbariHandlerList() {
      super(true);
    }
  
  
    // ----- HandlerCollection -------------------------------------------------
  
    @Override
    public void handle(String target, Request baseRequest,
                       HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
  
      ViewEntity viewEntity = getTargetView(target);
  
      if (viewEntity == null) {
        processHandlers(target, baseRequest, request, response);
      } else {
        // if there is a view target (as in a view resource request) then set the view class loader
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        try {
          ClassLoader viewClassLoader = viewEntity.getClassLoader();
          if (viewClassLoader == null) {
            LOG.debug("No class loader associated with view {}.", viewEntity.getName());
          } else {
            Thread.currentThread().setContextClassLoader(viewClassLoader);
          }
          processHandlers(target, baseRequest, request, response);
        } finally {
          Thread.currentThread().setContextClassLoader(contextClassLoader);
        }
      }
    }
  
    @Override
    public void addHandler(Handler handler) {
      nonViewHandlers.add(handler);
      super.addHandler(handler);
    }
  
  // ----- ViewInstanceHandler -----------------------------------------------
  
    @Override
    public void addViewInstance(ViewInstanceEntity viewInstanceDefinition) throws SystemException {
      WebAppContext handler = getHandler(viewInstanceDefinition);
      viewHandlerMap.put(viewInstanceDefinition, handler);
      super.addHandler(handler);
      // if this is running then start the handler being added...
      if (!isStopped() && !isStopping()) {
        try {
          handler.start();
        } catch (Exception e) {
          throw new SystemException("Caught exception adding a view instance.", e);
        }
      }
      handler.getSessionHandler().setSessionCache(sessionHandler.getSessionCache());
    }
  
    @Override
    public void shareSessionCacheToViews(SessionCache serverSessionCache) {
      for (WebAppContext webAppContext : viewHandlerMap.values()) {
        webAppContext.getSessionHandler().setSessionCache(serverSessionCache);
      }
    }
  
    @Override
    public void removeViewInstance(ViewInstanceEntity viewInstanceDefinition) {
      Handler handler = viewHandlerMap.get(viewInstanceDefinition);
      if (handler != null) {
        viewHandlerMap.remove(viewInstanceDefinition);
        removeHandler(handler);
      }
    }
  
  
    // ----- helper methods ----------------------------------------------------
  
    // call the handlers until the request is handled
    private void processHandlers(String target, Request baseRequest,
                                 HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
  
      final Handler[] handlers = getHandlers();
  
      if (handlers != null && isStarted()) {
        if (!processHandlers(viewHandlerMap.values(), target, baseRequest, request, response)) {
          processHandlers(nonViewHandlers, target, baseRequest, request, response);
        }
      }
    }
  
    // call the given handlers until the request is handled; return true if the request is handled
    private boolean processHandlers(Collection<? extends Handler> handlers, String target, Request baseRequest,
                                    HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {
  
      for (Handler handler : handlers) {
        handler.handle(target, baseRequest, request, response);
        if (baseRequest.isHandled()) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * Get a Handler for the given view instance.
     *
     * @param viewInstanceDefinition the view instance definition
     * @return a handler
     * @throws org.apache.ambari.view.SystemException if an handler can not be obtained for the given view instance
     */
    private WebAppContext getHandler(ViewInstanceEntity viewInstanceDefinition)
      throws SystemException {
  
      ViewEntity viewDefinition = viewInstanceDefinition.getViewEntity();
      WebAppContext webAppContext = webAppContextProvider.get();
  
      webAppContext.setWar(viewDefinition.getArchive());
      webAppContext.setContextPath(viewInstanceDefinition.getContextPath());
      webAppContext.setClassLoader(viewInstanceDefinition.getViewEntity().getClassLoader());
      webAppContext.setAttribute(ViewContext.CONTEXT_ATTRIBUTE, new ViewContextImpl(viewInstanceDefinition, viewRegistry));
      webAppContext.setSessionHandler(new SharedSessionHandler(sessionHandler));
      webAppContext.addFilter(new FilterHolder(ambariViewsSecurityHeaderFilter), "/*", AmbariServer.DISPATCHER_TYPES);
      webAppContext.addFilter(new FilterHolder(persistFilter), "/*", AmbariServer.DISPATCHER_TYPES);
      webAppContext.addFilter(new FilterHolder(springSecurityFilter), "/*", AmbariServer.DISPATCHER_TYPES);
      webAppContext.setAllowNullPathInfo(true);
  
      if (webAppContext.getErrorHandler() != null) {
        ErrorHandler errorHandlerProxy = createAmbariViewErrorHandlerProxy(webAppContext.getErrorHandler());
        if (errorHandlerProxy != null) {
          webAppContext.setErrorHandler(errorHandlerProxy);
        }
        webAppContext.getErrorHandler().setShowStacks(configuration.isServerShowErrorStacks());
      }
  
      return webAppContext;
    }
  
    private ErrorHandler createAmbariViewErrorHandlerProxy(ErrorHandler errorHandler) {
      ErrorHandler proxy = null;
      try {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(ErrorHandler.class);
        proxy = (ErrorHandler) proxyFactory.create(new Class[0],
          new Object[0],
          new AmbariViewErrorHandlerProxy(errorHandler, ambariErrorHandler));
      } catch (Exception e) {
        LOG.error("An error occurred while instantiating the error handler proxy instance", e);
      }
      return proxy;
    }
  
    /**
     * Get the view that is the target of the request; null if not a view request.
     *
     * @param target the target of the request
     * @return the view target; null if none
     */
    private ViewEntity getTargetView(String target) {
      Matcher matcher = VIEW_RESOURCE_TARGET_PATTERN.matcher(target);
  
      return matcher.matches() ? viewRegistry.getDefinition(matcher.group(2), matcher.group(3)) : null;
    }
  
  
    // ----- inner class : SharedSessionHandler --------------------------------
  
    /**
     * A session handler that shares its session manager with another app.
     * This handler DOES NOT attempt stop the shared session manager.
     */
    private class SharedSessionHandler extends SessionHandler {
  
      // ----- Constructors ----------------------------------------------------
  
      /**
       * Construct a SharedSessionHandler.
       *
       * @param sessionHandler the shared session manager.
       */
      public SharedSessionHandler(SessionHandler sessionHandler) {
        setSessionIdManager(sessionHandler.getSessionIdManager());
        sessionHandlerConfigurer.configureSessionHandler(this);
      }
  
  
      // ----- SessionHandler --------------------------------------------------
  
      @Override
      protected void doStop() throws Exception {
        // do nothing...
      }
    }
  }
  