/*
 * 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.impl.xml.importing;

import org.exoplatform.services.jcr.access.AccessManager;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.dataflow.ItemDataConsumer;
import org.exoplatform.services.jcr.dataflow.ItemDataKeeper;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
import org.exoplatform.services.jcr.impl.core.value.ValueFactoryImpl;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.jcr.InvalidSerializedDataException;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

/**
 * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
 * @version $Id: StreamImporter.java 14100 2008-05-12 10:53:47Z gazarenkov $
 */
public class StreamImporter implements RawDataImporter
{

   private final Log log = ExoLogger.getLogger("exo.jcr.component.core.StreamImporter");

   private final ContentImporter importer;

   private boolean namespacesRegistered = false;

   private final ItemDataKeeper dataKeeper;

   /**
    *
    */
   public StreamImporter(NodeData parent, int uuidBehavior, ItemDataKeeper dataKeeper, ItemDataConsumer dataConsumer,
      NodeTypeDataManager ntManager, LocationFactory locationFactory, ValueFactoryImpl valueFactory,
      NamespaceRegistry namespaceRegistry, AccessManager accessManager, ConversationState userState,
      Map<String, Object> context, RepositoryImpl repository, String currentWorkspaceName)
   {
      super();
      this.dataKeeper = dataKeeper;
      this.importer =
         createContentImporter(parent, uuidBehavior, dataConsumer, ntManager, locationFactory, valueFactory,
            namespaceRegistry, accessManager, userState, context, repository, currentWorkspaceName);
   }

   /**
    * {@inheritDoc}
    */
   public ContentImporter createContentImporter(NodeData parent, int uuidBehavior, ItemDataConsumer dataConsumer,
      NodeTypeDataManager ntManager, LocationFactory locationFactory, ValueFactoryImpl valueFactory,
      NamespaceRegistry namespaceRegistry, AccessManager accessManager, ConversationState userState,
      Map<String, Object> context, RepositoryImpl repository, String currentWorkspaceName)
   {
      return new NeutralImporter(parent, parent.getQPath(), uuidBehavior, dataConsumer, ntManager, locationFactory,
         valueFactory, namespaceRegistry, accessManager, userState, context, repository, currentWorkspaceName);
   }

   /**
    * @param stream
    * @throws RepositoryException
    */
   public void importStream(InputStream stream) throws RepositoryException
   {
      try
      {
         XMLInputFactory factory = XMLInputFactory.newInstance();
         //needed to avoid XXE Attacks
         factory.setProperty("javax.xml.stream.isSupportingExternalEntities",false);
         if (log.isDebugEnabled())
            log.debug("FACTORY: " + factory);
         XMLEventReader reader = factory.createXMLEventReader(stream);

         if (log.isDebugEnabled())
            log.debug("Start event handling");
         while (reader.hasNext())
         {
            XMLEvent event = reader.nextEvent();
            // log.info(event.toString());
            switch (event.getEventType())
            {
               case XMLStreamConstants.START_ELEMENT :
                  StartElement element = event.asStartElement();

                  if (!namespacesRegistered)
                  {
                     namespacesRegistered = true;
                     registerNamespaces(element);
                  }
                  Iterator attributes = element.getAttributes();
                  Map<String, String> attr = new HashMap<String, String>();
                  while (attributes.hasNext())
                  {
                     Attribute attribute = (Attribute)attributes.next();
                     attr.put(attribute.getName().getPrefix() + ":" + attribute.getName().getLocalPart(), attribute
                        .getValue());
                  }
                  QName name = element.getName();
                  importer.startElement(name.getNamespaceURI(), name.getLocalPart(), name.getPrefix() + ":"
                     + name.getLocalPart(), attr);
                  break;
               case XMLStreamConstants.END_ELEMENT :
                  EndElement endElement = event.asEndElement();
                  importer.endElement(endElement.getName().getNamespaceURI(), endElement.getName().getLocalPart(),
                     endElement.getName().getPrefix() + ":" + endElement.getName().getLocalPart());
                  break;
               case XMLStreamConstants.PROCESSING_INSTRUCTION :
                  break;
               case XMLStreamConstants.CHARACTERS :
                  String chars = event.asCharacters().getData();
                  importer.characters(chars.toCharArray(), 0, chars.length());
                  break;
               case XMLStreamConstants.COMMENT :
                  break;
               case XMLStreamConstants.START_DOCUMENT :
                  break;
               case XMLStreamConstants.END_DOCUMENT :
                  dataKeeper.save(importer.getChanges());
                  break;
               case XMLStreamConstants.ENTITY_REFERENCE :
                  break;
               case XMLStreamConstants.ATTRIBUTE :
                  break;
               case XMLStreamConstants.DTD :
                  break;
               case XMLStreamConstants.CDATA :
                  break;
               case XMLStreamConstants.SPACE :
                  break;
               default :
                  break;
            }
         }
         if (log.isDebugEnabled())
            log.debug("Event handling finished");

      }
      catch (XMLStreamException e)
      {
         throw new InvalidSerializedDataException("ImportXML failed", e);
      }
   }

   /**
    * @param event
    */
   private void registerNamespaces(StartElement event)
   {
      Iterator<Namespace> iter = event.getNamespaces();
      while (iter.hasNext())
      {
         Namespace namespace = iter.next();
         importer.registerNamespace(namespace.getPrefix(), namespace.getNamespaceURI());
      }
   }
}
