/* $Id: RepositoryDocumentFactory.java 1650351 2015-01-08 18:41:51Z kwright $ */

/**
* 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.manifoldcf.agents.incrementalingest;

import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.agents.interfaces.*;
import org.apache.manifoldcf.agents.system.Logging;
import org.apache.manifoldcf.agents.system.ManifoldCF;
import java.util.*;
import java.io.*;

/** This class accepts a RepositoryDocument in its constructor, and then
* allows multiple copies to me made, as part of a split in the pipeline.
* It must be closed in order to release all temporary resources.
*/
public class RepositoryDocumentFactory
{
  
  // The objects we need to track are:
  // (1) The binary stream
  // (2) All metadata values that are Readers
  // Everything else can be pulled out of the original RepositoryDocument
  
  protected final RepositoryDocument original;

  // The binary stream file and stream (if any)
  protected BinaryInput binaryTracker;
  
  // Readers (organized by metadata)
  protected final Map<String,CharacterInput[]> metadataReaders = new HashMap<String,CharacterInput[]>();
  
  /** Constructor.
  * Pass a RepositoryDocument.  This constructor reads all streams and stores them in
  * a temporary local location. 
  * If there is an error reading the streams, an IOException will be thrown.  Otherwise,
  * a ManifoldCFException will be thrown instead.
  *@param document is the repository document to read.
  */
  public RepositoryDocumentFactory(RepositoryDocument document)
    throws ManifoldCFException, IOException
  {
    this.original = document;
    try
    {
      this.binaryTracker = new TempFileInput(document.getBinaryStream());
      // Copy all reader streams
      Iterator<String> iter = document.getFields();
      while (iter.hasNext())
      {
        String fieldName = iter.next();
        Object[] objects = document.getField(fieldName);
        if (objects instanceof Reader[])
        {
          CharacterInput[] newValues = new CharacterInput[objects.length];
          metadataReaders.put(fieldName,newValues);
          // Populate newValues
          for (int i = 0; i < newValues.length; i++)
          {
            newValues[i] = new TempFileCharacterInput((Reader)objects[i]);
          }
        }
      }
    }
    catch (Throwable e)
    {
      // Clean up everything we've done so far.
      if (this.binaryTracker != null)
        this.binaryTracker.discard();
      for (String key : metadataReaders.keySet())
      {
        CharacterInput[] rt = metadataReaders.get(key);
        for (CharacterInput r : rt)
        {
          if (r != null)
            r.discard();
        }
      }
      if (e instanceof IOException)
        throw (IOException)e;
      else if (e instanceof RuntimeException)
        throw (RuntimeException)e;
      else if (e instanceof Error)
        throw (Error)e;
      else
        throw new RuntimeException("Unknown exception type: "+e.getClass().getName()+": "+e.getMessage(),e);
    }
  }
  
  /** Create a new RepositoryDocument object from the saved local resources.
  * As a side effect, this method also releases any resources held on behalf of the previously
  * created RepositoryDocument.
  *@return a repository document object.
  */
  public RepositoryDocument createDocument()
    throws ManifoldCFException
  {
    RepositoryDocument rd = new RepositoryDocument();
    
    // Copy scalar values
    rd.setCreatedDate(original.getCreatedDate());
    rd.setModifiedDate(original.getModifiedDate());
    rd.setIndexingDate(original.getIndexingDate());
    rd.setMimeType(original.getMimeType());
    rd.setFileName(original.getFileName());
    
    Iterator<String> securityTypes = original.securityTypesIterator();
    while (securityTypes.hasNext())
    {
      String securityType = securityTypes.next();
      rd.setSecurityACL(securityType,original.getSecurityACL(securityType));
      rd.setSecurityDenyACL(securityType,original.getSecurityDenyACL(securityType));
    }
    
    // Copy binary
    binaryTracker.doneWithStream();
    rd.setBinary(binaryTracker.getStream(),original.getBinaryLength());
    // Copy metadata fields (including minting new Readers where needed)
    Iterator<String> iter = original.getFields();
    while (iter.hasNext())
    {
      String fieldName = iter.next();
      Object[] objects = original.getField(fieldName);
      if (objects instanceof Reader[])
      {
        CharacterInput[] rts = metadataReaders.get(fieldName);
        Reader[] newReaders = new Reader[rts.length];
        for (int i = 0; i < rts.length; i++)
        {
          rts[i].doneWithStream();
          newReaders[i] = rts[i].getStream();
        }
        rd.addField(fieldName,newReaders);
      }
      else if (objects instanceof Date[])
      {
        rd.addField(fieldName,(Date[])objects);
      }
      else if (objects instanceof String[])
      {
        rd.addField(fieldName,(String[])objects);
      }
      else
        throw new RuntimeException("Unknown kind of metadata: "+objects.getClass().getName());
    }

    return rd;
  }
  
  /** Close this object and release its resources.
  */
  public void close()
    throws ManifoldCFException
  {
    binaryTracker.discard();
    for (String key : metadataReaders.keySet())
    {
      CharacterInput[] rt = metadataReaders.get(key);
      for (CharacterInput r : rt)
      {
        r.discard();
      }
    }
  }
  
}
