/*******************************************************************************
 * 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.olingo.odata2.core.rest;

import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Encoded;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;

import org.apache.olingo.odata2.api.ODataServiceFactory;
import org.apache.olingo.odata2.api.exception.ODataBadRequestException;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
import org.apache.olingo.odata2.core.rest.app.AbstractODataApplication;

/**
 * Default OData root locator responsible to handle the whole path and delegate all calls to a sub locator:<p>
 * <code>/{odata path} e.g. http://host:port/webapp/odata.svc/$metadata</code><br>
 * All path segments defined by a servlet mapping belong to the odata uri.
 * </p>
 * This behavior can be changed:<p>
 * <code>/{custom path}{odata path} e.g. http://host:port/webapp/bmw/odata.svc/$metadata</code><br>
 * The first segment defined by a servlet mapping belong to customer context and the following segments are OData
 * specific.
 * </p>
 *
 */
@Path("/")
public class ODataRootLocator {

  @Context
  private HttpHeaders httpHeaders;
  @Context
  private UriInfo uriInfo;
  @Context
  private Request request;
  @Context
  private ServletConfig servletConfig;
  @Context
  private HttpServletRequest servletRequest;

  @Context
  private Application app;

  /**
   * Default root behavior which will delegate all paths to a ODataLocator.
   * @param pathSegments URI path segments - all segments have to be OData
   * @param xHttpMethod HTTP Header X-HTTP-Method for tunneling through POST
   * @param xHttpMethodOverride HTTP Header X-HTTP-Method-Override for tunneling through POST
   * @return a locator handling OData protocol
   * @throws ODataException
   * @throws ClassNotFoundException
   * @throws IllegalAccessException
   * @throws InstantiationException
   */
  @Path("/{pathSegments: .*}")
  public Object handleRequest(
      @Encoded @PathParam("pathSegments") final List<PathSegment> pathSegments,
      @HeaderParam("X-HTTP-Method") final String xHttpMethod,
      @HeaderParam("X-HTTP-Method-Override") final String xHttpMethodOverride)
      throws ODataException, ClassNotFoundException, InstantiationException, IllegalAccessException {

    if (xHttpMethod != null && xHttpMethodOverride != null) {

      /*
       * X-HTTP-Method-Override : implemented by CXF
       * X-HTTP-Method : implemented in ODataSubLocator:handlePost
       */

      if (!xHttpMethod.equalsIgnoreCase(xHttpMethodOverride)) {
        throw new ODataBadRequestException(ODataBadRequestException.AMBIGUOUS_XMETHOD);
      }
    }

    if (servletRequest.getPathInfo() == null) {
      return handleRedirect();
    }

    ODataServiceFactory serviceFactory = getServiceFactory();

    int pathSplit = getPathSplit();

    final SubLocatorParameter param = new SubLocatorParameter();
    param.setServiceFactory(serviceFactory);
    param.setPathSegments(pathSegments);
    param.setHttpHeaders(httpHeaders);
    param.setUriInfo(uriInfo);
    param.setRequest(request);
    param.setServletRequest(servletRequest);
    param.setPathSplit(pathSplit);

    return ODataSubLocator.create(param);
  }

  public ODataServiceFactory getServiceFactory() {
    return createServiceFactoryFromContext(app, servletRequest, servletConfig);
  }

  public int getPathSplit() {
    int pathSplit = 0;
    final String pathSplitAsString = servletConfig.getInitParameter(ODataServiceFactory.PATH_SPLIT_LABEL);
    if (pathSplitAsString != null) {
      pathSplit = Integer.parseInt(pathSplitAsString);
    }
    return pathSplit;
  }

  public static ODataServiceFactory createServiceFactoryFromContext(final Application app,
      final HttpServletRequest servletRequest,
      final ServletConfig servletConfig) {
    try {
      Class<?> factoryClass;
      if (app instanceof AbstractODataApplication) {
        factoryClass = ((AbstractODataApplication) app).getServiceFactoryClass();
      } else {
        final String factoryClassName = servletConfig.getInitParameter(ODataServiceFactory.FACTORY_LABEL);
        if (factoryClassName == null) {
          throw new ODataRuntimeException("Servlet config missing: " + ODataServiceFactory.FACTORY_LABEL);
        }

        ClassLoader cl = (ClassLoader) servletRequest.getAttribute(ODataServiceFactory.FACTORY_CLASSLOADER_LABEL);
        if (cl == null) {
          factoryClass = Class.forName(factoryClassName);
        } else {
          factoryClass = Class.forName(factoryClassName, true, cl);
        }
      }
      return (ODataServiceFactory) factoryClass.newInstance();
    } catch (Exception e) {
      throw new ODataRuntimeException("Exception during ODataServiceFactory creation occured.", e);
    }
  }

  private Object handleRedirect() {
    return new ODataRedirectLocator();
  }
}
