/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.ext.repository;

import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
import org.exoplatform.services.jcr.config.RepositoryEntry;
import org.exoplatform.services.jcr.config.RepositoryServiceConfiguration;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.core.WorkspaceContainerFacade;
import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
import org.exoplatform.services.jcr.impl.core.SessionRegistry;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.security.RolesAllowed;
import javax.jcr.RepositoryException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

/**
 * Created by The eXo Platform SAS.
 *
 * RestRepositoryService is a RESTfull service allowing to manage the repositories.
 *
 * @author <a href="mailto:alex.reshetnyak@exoplatform.com.ua">Alex Reshetnyak</a>
 * @version $Id: NamesList.java 111 2008-11-11 11:11:11Z rainf0x $
 */

@Path("/jcr-service")
public class RestRepositoryService implements ResourceContainer
{

   /**
    * Definition the constants.
    */
   public static final class Constants
   {

      /**
       * The base path to this service.
       */
      public static final String BASE_URL = "/jcr-service";

      /**
       * Definition the operation types.
       */
      public static final class OperationType
      {
         /**
          * Repository service configuration operation.
          */
         public static final String REPOSITORY_SERVICE_CONFIGURATION = "/repository-service-configuration";

         /**
          * Default workspace configuration operations.
          */
         public static final String DEFAULT_WS_CONFIG = "/default-ws-config";

         /**
          * Create new repository operations.
          */
         public static final String CREATE_REPOSITORY = "/create-repository";

         /**
          * Create new workspace.
          */
         public static final String CREATE_WORKSPACE = "/create-workspace";

         /**
          * Remove repository operation.
          */
         public static final String REMOVE_REPOSITORY = "/remove-repository";

         /**
          * Remove workspace operation.
          */
         public static final String REMOVE_WORKSPACE = "/remove-workspace";

         /**
          * The list of repositories name operations.
          */
         public static final String REPOSITORIES_LIST = "/repositories";

         /**
          * The list of workspaces name operation.
          */
         public static final String WORKSPACES_LIST = "/workspaces";

         /**
          * Update workspace configuration operation.
          */
         public static final String UPDATE_WORKSPACE_CONFIG = "/update-workspace-config";

         /**
          * OperationType private constructor.
          */
         private OperationType()
         {
         }
      }

      /**
       * Constants private constructor.
       */
      private Constants()
      {
      }
   }

   /**
    * Class logger.
    */
   private final Log log = ExoLogger.getLogger("exo.jcr.component.ext.RestRepositoryService");

   /**
    * 
    */
   private final RepositoryService repositoryService;

   /**
    * To disable cache control.
    */
   private static final CacheControl NO_CACHE = new CacheControl();

   static
   {
      // noCache = new CacheControl();
      NO_CACHE.setNoCache(true);
      NO_CACHE.setNoStore(true);
   }

   /**
    * @param repositoryService
    */
   public RestRepositoryService(RepositoryService repositoryService)
   {
      this.repositoryService = repositoryService;
   }

   /**
    * Gives the repository service configuration which is composed
    * of the configuration of all the repositories and workspaces
    * @return Response
    *           return the Response with RepositoryServiceConf
    * @response
    * {code:json}
    *{"repositoryEntry": the configuration of the repository }
    * {code}
    * @LevelAPI Experimental
    */
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/repository-service-configuration")
   public Response getRepositoryServiceConfiguration()
   {
      RepositoryServiceConfiguration configuration = repositoryService.getConfig();
      RepositoryServiceConf conf =
         new RepositoryServiceConf(configuration.getRepositoryConfigurations(), configuration
            .getDefaultRepositoryName());
      return Response.ok(conf, MediaType.APPLICATION_JSON_TYPE).cacheControl(NO_CACHE).build();
   }

   /**
    * Gives the configuration of the default workspace of the given repository
    * @param repositoryName the name of the repository
    * @return Response
    *           return the Response with WorkspaceEntry
    * @response
    * {code:json}
    *  {"workspaceEntry" : the configuration of the default workspace }
    * {code}
    * @LevelAPI Experimental
    */
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/default-ws-config/{repositoryName}")
   public Response getDefaultWorkspaceConfig(@PathParam("repositoryName") String repositoryName)
   {
      String errorMessage = new String();
      Status status;

      try
      {
         String defaultWorkspaceName =
            repositoryService.getRepository(repositoryName).getConfiguration().getDefaultWorkspaceName();

         for (WorkspaceEntry wEntry : repositoryService.getRepository(repositoryName).getConfiguration()
            .getWorkspaceEntries())
         {
            if (defaultWorkspaceName.equals(wEntry.getName()))
            {
               return Response.ok(wEntry).cacheControl(NO_CACHE).build();
            }
         }

         return Response.status(Response.Status.NOT_FOUND).entity("Can not get default workspace configuration.").type(
            MediaType.TEXT_PLAIN).cacheControl(NO_CACHE).build();
      }
      catch (RepositoryException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.NOT_FOUND;
      }
      catch (Throwable e) //NOSONAR
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.INTERNAL_SERVER_ERROR;
      }

      return Response.status(status).entity(errorMessage).type(MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE)
         .build();
   }

   /**
    * Creates a repository based on the given repository configuration
    * 
    * @param uriInfo the uri location 
    * @param newRepository the configuration of the new repository
    * @return Response return the Response
    * @throws URISyntaxException will be generated the URISyntaxException
    * @request
    * {code:json}
    *   {"newRepository": the configuration of the new repository }
    * {code}
    * @LevelAPI Experimental
    */
   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/create-repository")
   public Response createRepository(@Context UriInfo uriInfo, RepositoryEntry newRepository) throws URISyntaxException
   {
      String errorMessage = new String();
      Status status;
      try
      {
         repositoryService.createRepository(newRepository);
         repositoryService.getConfig().retain(); // save configuration to persistence (file or persister)
         return Response.ok().cacheControl(NO_CACHE).build();
      }
      catch (RepositoryException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.BAD_REQUEST;
      }
      catch (RepositoryConfigurationException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.BAD_REQUEST;
      }
      catch (Throwable e) //NOSONAR
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.INTERNAL_SERVER_ERROR;
      }

      return Response.status(status).entity(errorMessage).type(MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE)
         .build();
   }

   /**
    * Adds a new workspace based on the given configuration to the given repository.
    * 
    * @param uriInfo the uri location
    * @param repositoryName the name of the repository
    * @param newWorkspace the configuration of the new workspace
    * @return Response return the Response
    * @throws URISyntaxException will be generated the URISyntaxException
    * @request
    * {code:json}
    *  {"newWorkspace" : the configuration of the new workspace}
    * {code}
    * @LevelAPI Experimental
    */
   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/create-workspace/{repositoryName}")
   public Response createWorkspace(@Context UriInfo uriInfo, @PathParam("repositoryName") String repositoryName,
      WorkspaceEntry newWorkspace) throws URISyntaxException
   {
      String errorMessage = new String();
      Status status;
      try
      {
         RepositoryImpl repository = (RepositoryImpl)repositoryService.getRepository(repositoryName);
         repository.configWorkspace(newWorkspace);
         repository.createWorkspace(newWorkspace.getName());
         repositoryService.getConfig().retain(); // save configuration to persistence (file or persister)
         return Response.ok().cacheControl(NO_CACHE).build();
      }
      catch (RepositoryException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.NOT_FOUND;
      }
      catch (RepositoryConfigurationException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.BAD_REQUEST;
      }
      catch (Throwable e) //NOSONAR
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.INTERNAL_SERVER_ERROR;
      }
      return Response.status(status).entity(errorMessage).type(MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE)
         .build();
   }

   /**
    * Removes an existing repository
    * 
    * @param uriInfo the uri location
    * @param repositoryName the name of the repository
    * @param forseSessionClose a flag indicating whether or not the current sessions must be closed
    * @return Response return the Response
    * @LevelAPI Experimental
    */
   @GET
   @RolesAllowed("administrators")
   @Path("/remove-repository/{repositoryName}/{forseSessionClose}")
   public Response removeRepository(@Context UriInfo uriInfo, @PathParam("repositoryName") String repositoryName,
      @PathParam("forseSessionClose") Boolean forseSessionClose)
   {
      String errorMessage = new String();
      Status status;

      try
      {
         if (forseSessionClose)
         {
            for (WorkspaceEntry wsEntry : repositoryService.getConfig().getRepositoryConfiguration(repositoryName)
               .getWorkspaceEntries())
            {
               forceCloseSession(repositoryName, wsEntry.getName());
            }
         }

         if (repositoryService.canRemoveRepository(repositoryName))
         {
            repositoryService.removeRepository(repositoryName);
            repositoryService.getConfig().retain(); // save configuration to persistence (file or persister) 
            return Response.ok().build();
         }
         return Response.status(Status.CONFLICT).entity("Can't remove repository " + repositoryName)
            .cacheControl(NO_CACHE).build();
      }
      catch (RepositoryException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.NOT_FOUND;
      }
      catch (Throwable e) //NOSONAR
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.INTERNAL_SERVER_ERROR;
      }

      return Response.status(status).entity(errorMessage).type(MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE)
         .build();
   }

   /**
    * Removes an existing workspace from a given repository. 
    * 
    * @param uriInfo the uri location
    * @param repositoryName the name of the repository
    * @param workspaceName the name of the workspace
    * @param forseSessionClose a flag indicating whether or not the current sessions must be closed
    * @return Response return the Response
    * @LevelAPI Experimental
    */
   @POST
   @RolesAllowed("administrators")
   @Path("/remove-workspace/{repositoryName}/{workspaceName}/{forseSessionClose}/")
   public Response removeWorkspace(@Context UriInfo uriInfo, @PathParam("repositoryName") String repositoryName,
      @PathParam("workspaceName") String workspaceName, @PathParam("forseSessionClose") Boolean forseSessionClose)
   {
      String errorMessage = new String();
      Status status;

      try
      {
         ManageableRepository repository = repositoryService.getRepository(repositoryName);

         if (forseSessionClose)
         {
            forceCloseSession(repositoryName, workspaceName);
         }

         if (repository.canRemoveWorkspace(workspaceName))
         {
            repository.removeWorkspace(workspaceName);
            repositoryService.getConfig().retain(); // save configuration to persistence (file or persister)
            return Response.ok().build();
         }
         return Response.status(Status.CONFLICT).entity(
            "Can't remove workspace " + workspaceName + " in repository " + repositoryName).cacheControl(NO_CACHE)
            .build();
      }
      catch (RepositoryException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.NOT_FOUND;
      }
      catch (RepositoryConfigurationException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.NOT_FOUND;
      }
      catch (Throwable e) //NOSONAR
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.INTERNAL_SERVER_ERROR;
      }

      return Response.status(status).entity(errorMessage).type(MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE)
         .build();
   }

   /**
    * Gives the name of all the existing repositories.
    * 
    * @return Response return the Response with list of repository names
    * @response
    * {code:json}
    *  {"names" : the name of all the existing repositories }
    * {code}
    * @LevelAPI Experimental
    */
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/repositories")
   public Response getRepositoryNames()
   {
      List<String> repositories = new ArrayList<String>();

      for (RepositoryEntry rEntry : repositoryService.getConfig().getRepositoryConfigurations())
      {
         repositories.add(rEntry.getName());
      }

      return Response.ok(new NamesList(repositories)).cacheControl(NO_CACHE).build();
   }

   /**
    * Gives the name of all the existing workspaces for a given repository.
    * 
    * @param repositoryName the name of the repository
    * @return Response return the Response with list of workspace names
    * @response
    * {code:json}
    *  {"names" : the name of all the existing workspaces for a given repository }
    * {code}
    * @LevelAPI Experimental
    */
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/workspaces/{repositoryName}")
   public Response getWorkspaceNames(@PathParam("repositoryName") String repositoryName)
   {
      String errorMessage = new String();
      Status status;

      try
      {
         List<String> workspaces = new ArrayList<String>();

         for (WorkspaceEntry wEntry : repositoryService.getRepository(repositoryName).getConfiguration()
            .getWorkspaceEntries())
         {
            workspaces.add(wEntry.getName());
         }

         return Response.ok(new NamesList(workspaces)).cacheControl(NO_CACHE).build();
      }
      catch (RepositoryException e)
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.NOT_FOUND;
      }
      catch (Throwable e) //NOSONAR
      {
         if (log.isDebugEnabled())
         {
            log.error(e.getMessage(), e);
         }
         errorMessage = e.getMessage();
         status = Status.INTERNAL_SERVER_ERROR;
      }

      return Response.status(status).entity(errorMessage).type(MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE)
         .build();
   }

   /**
    * Updates the configuration of a given workspace.
    * 
    * @param repositoryName the name of the repository
    * @param workspaceName the name of the workspace
    * @param workspaceEntry the configuration of the workspace
    * @request
    * {code:json}
    *  {"workspaceEntry" : the configuration of the workspace}
    * {code}
    * @return Response return the Response
    * @LevelAPI Unsupported
    */
   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @RolesAllowed("administrators")
   @Path("/update-workspace-config/{repositoryName}/{workspaceName}")
   public Response updateWorkspaceConfiguration(@PathParam("repositoryName") String repositoryName,
      @PathParam("workspaceName") String workspaceName, WorkspaceEntry workspaceEntry)
   {
      return Response.status(Status.OK).entity("The method /update-workspace-config not implemented.").type(
         MediaType.TEXT_PLAIN_TYPE).cacheControl(NO_CACHE).build();
   }

   /**
    * forceCloseSession. Close sessions on specific workspace.
    * 
    * @param repositoryName
    *          repository name
    * @param workspaceName
    *          workspace name
    * @return int return the how many sessions was closed
    * @throws RepositoryConfigurationException
    *           will be generate RepositoryConfigurationException
    * @throws RepositoryException
    *           will be generate RepositoryException
    */
   private int forceCloseSession(String repositoryName, String workspaceName) throws RepositoryException,
      RepositoryConfigurationException
   {
      ManageableRepository mr = repositoryService.getRepository(repositoryName);
      WorkspaceContainerFacade wc = mr.getWorkspaceContainer(workspaceName);

      SessionRegistry sessionRegistry = (SessionRegistry)wc.getComponent(SessionRegistry.class);

      return sessionRegistry.closeSessions(workspaceName);
   }
}
