/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache Jetspeed" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache Jetspeed", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.jetspeed.services.cms.manager;
/**
 * Slide implementation for the CmsManager
 *
 * @author <a href="mailto:christophe.lombart@skynet.be">Christophe Lombart</a>
 *
 */

// Java 2 SDK
import java.util.*;
import java.io.FileWriter;
import java.io.FileInputStream;
import java.util.Vector;

// Turbine
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.resources.TurbineResources;
import org.apache.turbine.util.upload.FileItem;
import org.apache.turbine.util.Log;

// Slide

import org.apache.slide.structure.*;
import org.apache.slide.content.*;
import org.apache.slide.common.*;
import org.apache.slide.lock.*;
import org.apache.slide.security.*;
import org.apache.slide.authenticate.CredentialsToken;
import org.apache.slide.authenticate.SecurityToken;
import org.apache.slide.security.AccessDeniedException;

// Jetspeed
import org.apache.jetspeed.om.cms.*;
import org.apache.jetspeed.om.cms.slide.SlideResource;
import org.apache.jetspeed.services.cms.CmsService;
import org.apache.jetspeed.services.cms.JetspeedCMSException;


public class CmsManagerSlideImpl implements CmsManager
{

	private NamespaceAccessToken token = null;
	private Structure structure = null;
	private Security security = null;
	private Content content = null;
	private Lock lock = null;
	private static String PROPERTY_CLASS_NAME = "resourceClassName";
	String nameSpace = null;

	/**
	* Initialise the CmsManager.
	* @param nameSpace used assigned to the CmsManager. This namespace can be
	* used to read parameters assigned to this namespace in the JR.prop
	*/
	public void init (String namespace)
			throws JetspeedCMSException
	{
		try
		{
            String domainFile =TurbineResources.getString("services.CmsService.CmsManager." + namespace + ".domainfile");
			Domain.init(new FileInputStream(domainFile));
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException();
		}

		token = Domain.accessNamespace(new SecurityToken(new String()), namespace);

		structure = token.getStructureHelper();
		security = token.getSecurityHelper();
		content = token.getContentHelper();
		lock = token.getLockHelper();
		this.nameSpace = namespace;
	}

	/**
	* Export the CMS repository into an xml file
	* @param userNode User reference
	* @param xmlFileName output file
	*/

	public void exportRepository (String userNode, String xmlFileName)
		   throws JetspeedCMSException
	{
		try
		{
			SlideToken slideToken = this.getSlideToken ( userNode );
			token.exportData(slideToken, new FileWriter( xmlFileName ));
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException("Impossible to export the content respository");
		}
	}

	/**
	* Populate a catalog structure from the repository
	*
	* @param userNode User reference
	* @param Catalog object to populate
	*/
	public void populateCatalog (String userNode, Catalog catalog)
		   throws JetspeedCMSException
	{
		this.populateCatalog(userNode, catalog, true, true, -1);
	}

	/**
	* Populate a catalog structure from the repository
	*
	* @param userNode User reference
	* @param catalog object to populate
	* @param withSubCatalog - retrieve or not sub-catalogs
	* @param withContent - retrieve or not child ContentItems
	*/
	public void populateCatalog (String userNode, Catalog catalog,
								 boolean withSubCatalog, boolean withContent)
		   throws JetspeedCMSException
	{
		this.populateCatalog(userNode, catalog, withSubCatalog,
							 withContent, -1);
	}

	/**
	* Populate a catalog structure from the repository
	*
	* @param userNode User reference
	* @param catalog object to populate
	* @param withSubCatalog - retrieve or not sub-catalogs
	* @param withContent - retrieve or not child ContentItems
	* @param numberOfLevels - number of tree level to populate
	*/
	public void populateCatalog (String userNode, Catalog catalog,
								 boolean withSubCatalog, boolean withContent,
								 int numberOfLevels )
		   throws JetspeedCMSException
	{
		try
		{
			int tmpLevel = numberOfLevels ;
			SlideToken slideToken = this.getSlideToken ( userNode );
			ObjectNode objectNode = structure.retrieve(slideToken, catalog.getUri());

			// -----------------  Get catalog children
			Enumeration childrenUri = objectNode.enumerateChildren();
			this.getChildren(userNode, childrenUri, catalog,
							 withSubCatalog, withContent, numberOfLevels);

			// -----------------  Get links
			Enumeration linksUri = objectNode.enumerateLinks();
			this.getChildren(userNode, linksUri, catalog,
							 withSubCatalog, withContent, tmpLevel);
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException("Impossible to get catalog information");
		}
	}

	/**
	* Get all children from a specific parent uri
	*
	* @param userNode User reference
	* @param parentUri uri used to get children
	*/
	public Vector getUriList (String userNode, String parentUri)
		   throws JetspeedCMSException
	{
		try
		{

			Vector uriList = new Vector();

			// Add the parent uri
			uriList.add(parentUri);
			//System.out.println("URI : " + parentUri);

			// Retrieve children
			SlideToken slideToken = this.getSlideToken ( userNode );
			ObjectNode objectNode = structure.retrieve(slideToken, parentUri);
			Enumeration children = objectNode.enumerateChildren();

			// Add children to the vector
			while (children.hasMoreElements())
			{
				String uriChild  = (String) children.nextElement();
				Vector v = this.getUriList(userNode, uriChild);
				uriList.addAll(v);
			}

			return uriList;
		}
		catch (Exception e)
		{
		   throw new JetspeedCMSException("Impossible to get the uri List");
		}
	}


	/**
	* Get a resource object (Catalog or ContentItem)  from the repository
	* based on a uri.
	* @param userNode User reference
	* @param uri
	* @return resource that match to this uri
	*/
	public Resource getResource (String userNode, String uri )
		   throws JetspeedCMSException
	{

		SlideResource resource = null;

		try
		{
			// Get the last NodeRevisionDescriptor
			SlideToken slideToken = this.getSlideToken( userNode );

			// uri parameter can be point to a linkNode => retrieve its target
			// objectnode
			ObjectNode objectNode = structure.retrieve(slideToken, uri, true);

			// Get the last Node revision descriptor
			NodeRevisionDescriptors revisionDescriptors =
				 content.retrieve(slideToken, objectNode.getUri());
			NodeRevisionDescriptor revisionDescriptor =
				 content.retrieve(slideToken, revisionDescriptors);

			// Check if the resource is a Catalog or an ContentItem
			// (based on the property class name) & an create an object for this resource
			NodeProperty p = revisionDescriptor.getProperty(PROPERTY_CLASS_NAME);
			String className = (String) p.getValue();
			resource = (SlideResource) Class.forName( className ).newInstance();
			resource.setUri(uri);
			// Populate the resource properties
			resource.setDescriptor(revisionDescriptor);

			// populate Security info
			resource.setPermissions(this.getSecurity(userNode, uri));
			return resource;
		}
		catch (AccessDeniedException e)
		{
			JetspeedCMSException e1 = new JetspeedCMSException("Impossible to get the content resource");
			e1.setIsAccessDenied(true);
			throw e1;
		}
		catch (NullPointerException e)
		{
			// Trigger when the objectNode has not the correct properties
			// eg. :  for a User or Action node in Slide. These type of node are mapped to Catalog

			Catalog catalog = (Catalog) CmsFactory.getCmsOmInstance("Catalog");
			catalog.setUri(uri);
			catalog.setLogicalName(uri);
			catalog.setTitle(uri);
			return catalog;
		}
		catch (RevisionDescriptorNotFoundException e)
		{
			// if  no revision descriptor => create an empty catalog object which
			// contains the uri data, a default title and a default logical name
			// This case arrives when the descriptor stores are not init by the
			// method Slide.createResource(...)
			// For example, after reading domain.xml, Slide will add all
			// namespace defined in the "scoop" xml tag in the target store
			// But in our case, we need different properties defined via the
			// NodeRevisionDescriptor like the title, logical name, ...

		    if (uri.equals(TurbineResources.getString("services.CmsService.catalog.root.uri")))
			{
				Catalog catalog =  (Catalog) CmsFactory.getCmsOmInstance("Catalog");
				catalog.setUri(uri);
				catalog.setLogicalName(TurbineResources.getString("services.CmsService.catalog.root.logicalname"));
				catalog.setTitle(TurbineResources.getString("services.CmsService.catalog.root.title"));
				return catalog;
			}
			else
			{
				// getResource called for a user/action ...
				Catalog catalog =  (Catalog) CmsFactory.getCmsOmInstance("Catalog");
				catalog.setUri(uri);
				catalog.setLogicalName(uri);
				catalog.setTitle(uri);
				return catalog;
				//e.printStackTrace();
				//return null;
			}
		}
		catch ( Exception e)
		{
			throw new JetspeedCMSException("Impossible to get the content resource");
		}
	}

	/**
	* Get all catalogs defined between 2 uri (from - to)
	*
	* @param userNode User reference
	* @param fromuri : first element of the interval
	* @param toUri  : last element of the interval
	* @return catalogs found between both uri
	*/
	public Vector getCatalogs(String userNode, String fromUri, String toUri)
		   throws JetspeedCMSException
	{

		Vector catalogs = new Vector();
		String currentUri = toUri;

		while ( true )
		{
			try
			{
				Catalog catalog = (Catalog) this.getResource(userNode , currentUri);
				catalogs.insertElementAt(catalog, 0);

				if ((currentUri.equals(fromUri)) ||
					(catalog.getParentUri() == null) ||
					(catalog.getParentUri().equals("")) )
				{
					break;
				}

				currentUri = catalog.getParentUri();
			}
			catch (JetspeedCMSException e)
			{
				// If the exception is accessDenied : continue the loop for other catalog
				// else throw the exception
				if (!e.isAccessDenied())
				{
					throw new JetspeedCMSException("Impossible to get the complete catalog path");
				}
			}
		}

		return catalogs;
	}

	/**
	* Get Security information assigned to an uri
	* @param userNode User reference
	* @param uri for which the security info is asked
	*/
	public Vector getSecurity (String userNode, String uri )
		   throws JetspeedCMSException
	{
		try
		{
			// Get the SlideToken in function of a SubjectNode string
			SlideToken slideToken = this.getSlideToken( userNode );
			// Retrieve security info
			Enumeration e = security.enumeratePermissions(slideToken, uri);
			Vector permissions = new Vector();

			while (e.hasMoreElements())
			{
				org.apache.slide.security.NodePermission node =
					(org.apache.slide.security.NodePermission) e.nextElement();
				Permission p = (Permission) CmsFactory.getCmsOmInstance("Permission");
				p.setAction(node.getActionUri());
				p.setActor(node.getSubjectUri());
				p.setInheritable(node.isInheritable());
				p.setNegative(node.isNegative());
				permissions.add(p);
			}

			return permissions;
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException("Impossible to get the security information " + uri);
		}
	}

	/**
	* Grant permission to an uri
	* @param userNode User reference
	* @param uri on which permission has to be set
	* @param userUri User uri reference
	* @param actionUri Action used in the permission (read, modify, write, ...)
	* @param inheritable true if the permission has to be apply on child uri
	* @param negative true if the permission is negative
	*/
	public void grantPermission (String userNode, String objectUri , String userUri,
								 String actionUri, boolean inheritable,
								 boolean negative)
		   throws JetspeedCMSException
	{

		try
		{

			// Get the SlideToken in function of a SubjectNode string
			SlideToken slideToken = this.getSlideToken( userNode );

			// uri parameter can be point to a linkNode => retrieve its target
			// objectnode
			ObjectNode objectNode = structure.retrieve(slideToken, objectUri, true);

			// uri parameter can be point to a linkNode => retrieve its target
			// objectnode
			SubjectNode subjectNode = (SubjectNode) structure.retrieve(slideToken, userUri, true);

			// Retrieve the Action Node
			ActionNode actionNode = (ActionNode) structure.retrieve(slideToken, actionUri);

			// Grant the permission
			NodePermission perm = new NodePermission(objectNode, subjectNode,
													  actionNode, inheritable,
													  negative);
			security.grantPermission(slideToken, perm);


		}
		catch ( Exception e)
		{
			throw new JetspeedCMSException("Impossible to grant the permission");
		}
	}

	/**
	* Create a new resource in the repository
	* @param userNode User reference
	* @param resource object describing the new resource
	*/
	public void createResource (String userNode, Resource resource)
		   throws JetspeedCMSException
	{
		try
		{

			// Get the SlideToken in function of a SubjectNode string
			SlideToken slideToken = this.getSlideToken( userNode );

			// Create the new resource ( Slide subjectNode )
			SubjectNode subject = new SubjectNode();
			structure.create(slideToken, subject, resource.getUri());

			// ---- Create the new content revision descriptor
			NodeRevisionDescriptor descriptor = (( SlideResource )resource).getDesciptor();
			descriptor.setProperty(this.PROPERTY_CLASS_NAME,
									 resource.getClass().getName());

			if (resource instanceof Catalog)
			{
				// Get content length
				descriptor.setProperty("getcontentlength", new Long(0));

				// Resource type (collection) - use xml element recognize by webdav client
				descriptor.setProperty(new NodeProperty("resourcetype", "<collection/>", true));
			}
			else
			{
				// Get content length
				descriptor.setProperty("getcontentlength", new Long(0));

				// Last modification date
				descriptor.setLastModified(new Date());

				// Etag generation
				String etag = resource.getUri().hashCode() + "_"
								//+ revisionNumber.hashCode() + "_"
							   + descriptor.getContentLength();
				descriptor.setProperty(new NodeProperty("getetag", etag, true));

				descriptor.setProperty(new NodeProperty("getcontenttype", "", true));

				// Resource type (collection or other type of content)
				descriptor.setProperty(new NodeProperty("resourcetype", "", true));

				// Source
				descriptor.setProperty(new NodeProperty("source", "", true));

				// Content Language
				//revisionDescriptor.setProperty("getcontentlanguage", stream., true);

				// Owner
				//String owner = slideToken.getCredentialsToken().getPublicCredentials();
				//descriptorescriptor.setProperty(new NodeProperty("owner", owner, true));

				// For Microsoft tools

				// Is hidden
				NodeProperty property = new NodeProperty("ishidden", "0", "MICROSOFT");
				descriptor.setProperty(property);

				// Is collection
				property = new NodeProperty("iscollection", "0", "MICROSOFT");
				descriptor.setProperty(property);

				// Is read only
				property = new NodeProperty("isreadonly", "0", "MICROSOFT");
				descriptor.setProperty(property);

				// Last accessed
				property = new NodeProperty("lastaccessed", (new Date()).toString(),
											"MICROSOFT");
				descriptor.setProperty(property);
			}

			content.create(slideToken, resource.getUri(), descriptor, null);

		   // Grant permission if needed

			Vector permissions = resource.getPermissions();

			for (Enumeration e = permissions.elements(); e.hasMoreElements();)
			{
				Permission p = (Permission) e.nextElement();
				this.grantPermission(userNode,resource.getUri(), p.getActor(),
							  		p.getAction(), p.isInheritable(), p.isNegative());
			}
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException("Impossible to create the content resource");
		}
	}

	/**
	* Create a new resource in the repository with a content stream
	* @param userNode User reference
	* @param resource object describing the new resource
	* @param stream stream which match to the resource content
	*/
	public void createResource (String userNode, Resource resource, FileItem stream)
		   throws JetspeedCMSException
	{
		try
		{

			// Get the SlideToken in function of a SubjectNode string
			SlideToken slideToken = this.getSlideToken( userNode );

			// Create the new resource ( Slide subjectNode )
			SubjectNode subject = new SubjectNode();
			structure.create(slideToken, subject, resource.getUri());

			// Create the new content revision descriptor
			NodeRevisionDescriptor descriptor = ((SlideResource) resource).getDesciptor();
	   		descriptor.setProperty(this.PROPERTY_CLASS_NAME,
								   resource.getClass().getName());

	   		// Get content length
	   		descriptor.setProperty("getcontentlength", new Long(stream.getSize()));

	   		// Last modification date
	   		descriptor.setLastModified(new Date());

	   		// Etag generation
	   		String etag = resource.getUri().hashCode() + "_"
						   //+ revisionNumber.hashCode() + "_"
						   + descriptor.getContentLength();
	   		descriptor.setProperty(new NodeProperty("getetag", etag, true));

	   		String contentType = stream.getContentType();
	   		descriptor.setProperty(new NodeProperty("getcontenttype", contentType, true));

	   		// Resource type (collection or other type of content
	   		descriptor.setProperty(new NodeProperty("resourcetype", "", true));

	   		// Source
	   		descriptor.setProperty(new NodeProperty("source", "", true));

	   		// displayname : use in a webdav client
	   		String displayName = stream.getFileName().substring( stream.getFileName().lastIndexOf("\\")+1 );

	   		descriptor.setProperty(new NodeProperty("displayname",
												    "<![CDATA[" + displayName+ "]]>"));

	   		// Content Language
	   		//revisionDescriptor.setProperty("getcontentlanguage", stream., true);

	   		// Owner
	   		//String owner = slideToken.getCredentialsToken().getPublicCredentials();
	   		//descriptorescriptor.setProperty(new NodeProperty("owner", owner, true));

	   		// For Microsoft tools

	   		// Is hidden
	   		NodeProperty property = new NodeProperty("ishidden", "0", "MICROSOFT");
	   		descriptor.setProperty(property);

	   		// Is collection
	   		property = new NodeProperty("iscollection", "0", "MICROSOFT");
	   		descriptor.setProperty(property);

	   		// Is read only
	   		property = new NodeProperty("isreadonly", "0", "MICROSOFT");
	   		descriptor.setProperty(property);

	   		// Last accessed
	   		property = new NodeProperty("lastaccessed", (new Date()).toString(),
									    "MICROSOFT");
	   		descriptor.setProperty(property);

	   		// Add the content stream
	   		NodeRevisionContent revisionContent = new NodeRevisionContent();
	   		revisionContent.setContent(stream.getInputStream());

	   		// Add all stuff in the slide repository
	   		content.create(slideToken, resource.getUri(), descriptor, revisionContent);

	   		// Grant permission if needed
	   		Vector permissions = resource.getPermissions();

	   		for (Enumeration e = permissions.elements(); e.hasMoreElements();)
	   		{
		 		Permission p = (Permission) e.nextElement();
		 		this.grantPermission(userNode,resource.getUri(), p.getActor(),
							  		 p.getAction(), p.isInheritable(), p.isNegative());
	   		}
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException("Impossible to create the content resource");
		}
	}

	/**
	* Create a new link in the repository
	* @param userNode User reference
	* @param link object describing the new link
	*/
	public void createLink (String userNode, Link link)
		throws JetspeedCMSException
	{
		try
		{
			// Get the SlideToken in function of a SubjectNode string
			SlideToken slideToken = this.getSlideToken( userNode );

			ObjectNode resourceNode = (ObjectNode) structure.retrieve(slideToken,
											link.getTargetResourceUri() );
			LinkNode linkNode = new LinkNode();
			structure.createLink(slideToken, linkNode, link.getUri(), resourceNode);
		}
		catch (Exception e)
		{
			throw new JetspeedCMSException("Impossible to get the link");
		}
	}


	// ------------------------------------------------------------------------------------------------
	//
	//  PRIVATE METHODS
	//
	// ------------------------------------------------------------------------------------------------

	/**
	* Create a new SlideToken instance in function
	* of a SubjectNode
	*
	* @param user reference
	* @returnv a Slide toke object
	*/
	private SlideToken getSlideToken (String userNode)
	{
		CredentialsToken credToken = new CredentialsToken(new String(userNode));

		return new SlideTokenImpl(credToken);
	}

	private void getChildren(String userNode, Enumeration children,
							 Catalog catalog, boolean withSubCatalog,
							 boolean withContent,int numberOfLevels)
		throws JetspeedCMSException
	{

		if (Log.getLogger().isDebugEnabled())
		{
			Log.debug("Slide.getChildren for : " + catalog.getUri());
		}

		if (numberOfLevels != -1)
		{
			numberOfLevels--;
		}

		while (children.hasMoreElements())
		{
			String childUri = (String) children.nextElement();

			if (Log.getLogger().isDebugEnabled())
			{
				Log.debug("Slide.getChildren -  Child found : " + childUri);
			}

		   try
		   {
				Resource resource = this.getResource(userNode, childUri);

				if (resource instanceof Catalog)
				{
					if (withSubCatalog)
					{
						// numberOfLevels == -1 means all levels in the repository tree
						if ((numberOfLevels == -1) || (numberOfLevels > 0))
						{
							int tmp = numberOfLevels;
							this.populateCatalog( userNode,
												  (Catalog) resource,
								  				  withSubCatalog,
								  				  withContent,
								  				  numberOfLevels );
						}

						if (Log.getLogger().isDebugEnabled())
						{
							Log.debug("Slide.add Children : " + resource.getKey());
						}

						catalog.addResource( resource.getKey(), resource);
					}
				}
				else
				{
					if (withContent)
					{
						catalog.addResource(resource.getKey(), resource);
					}
				}
			}
			catch (JetspeedCMSException e)
			{
				// if the exception is AccessDenied : continue the loop for other children
				// else it is a more serious exception => throws the exception to the caller
				if (!e.isAccessDenied())
				{
					throw new JetspeedCMSException("Impossible to get the children information ");
				}
			}
		}
	}
}
