content: /*
   * 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.chemistry.opencmis.client.bindings.spi.atompub;
  
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ATOM_ID;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ATOM_TITLE;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ATOM_UPDATED;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_CONTENT;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_CONTENT_BASE64;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_CONTENT_FILENAME;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_CONTENT_MEDIATYPE;
  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ENTRY;
  
  import java.io.BufferedInputStream;
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.util.GregorianCalendar;
  import java.util.TimeZone;
  
  import org.apache.chemistry.opencmis.commons.PropertyIds;
  import org.apache.chemistry.opencmis.commons.data.ContentStream;
  import org.apache.chemistry.opencmis.commons.data.ObjectData;
  import org.apache.chemistry.opencmis.commons.data.PropertyData;
  import org.apache.chemistry.opencmis.commons.data.PropertyString;
  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
  import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
  import org.apache.chemistry.opencmis.commons.impl.Base64;
  import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
  import org.apache.chemistry.opencmis.commons.impl.XMLConstants;
  import org.apache.chemistry.opencmis.commons.impl.XMLConverter;
  import org.apache.chemistry.opencmis.commons.impl.XMLUtils;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.BulkUpdateImpl;
  import org.xmlpull.v1.XmlSerializer;
  
  /**
   * Writes a CMIS Atom entry to an output stream.
   */
  public class AtomEntryWriter {
  
      private static final int BUFFER_SIZE = 8 * 1024;
  
      private final CmisVersion cmisVersion;
      private final ObjectData object;
      private final ContentStream contentStream;
      private final InputStream stream;
      private final TypeDefinition typeDef;
      private final BulkUpdateImpl bulkUpdate;
  
      /**
       * Constructor for objects.
       */
      public AtomEntryWriter(ObjectData object, CmisVersion cmisVersion) {
          this(object, cmisVersion, null);
      }
  
      /**
       * Constructor for objects.
       */
      public AtomEntryWriter(ObjectData object, CmisVersion cmisVersion, ContentStream contentStream) {
          if (object == null || object.getProperties() == null) {
              throw new CmisInvalidArgumentException("Object and properties must not be null!");
          }
  
          if (contentStream != null && contentStream.getMimeType() == null) {
              throw new CmisInvalidArgumentException("Media type must be set if a stream is present!");
          }
  
          this.object = object;
          this.cmisVersion = cmisVersion;
          this.contentStream = contentStream;
          if (contentStream != null && contentStream.getStream() != null) {
              InputStream in = contentStream.getStream();
  
              // avoid double buffering
              if (!(in instanceof BufferedInputStream) && !(in instanceof ByteArrayInputStream)) {
                  stream = new BufferedInputStream(in, BUFFER_SIZE);
              } else {
                  stream = in;
              }
          } else {
              stream = null;
          }
          this.typeDef = null;
          this.bulkUpdate = null;
      }
  
      /**
       * Constructor for types.
       */
      public AtomEntryWriter(TypeDefinition type, CmisVersion cmisVersion) {
          if (type == null) {
              throw new CmisInvalidArgumentException("Type must not be null!");
          }
  
          this.typeDef = type;
          this.cmisVersion = cmisVersion;
          this.object = null;
          this.contentStream = null;
          this.stream = null;
          this.bulkUpdate = null;
      }
  
      /**
       * Constructor for bulk updates.
       */
      public AtomEntryWriter(BulkUpdateImpl bulkUpdate) {
          if (bulkUpdate == null) {
              throw new CmisInvalidArgumentException("Bulk update data must not be null!");
          }
  
          this.bulkUpdate = bulkUpdate;
          this.typeDef = null;
          this.cmisVersion = CmisVersion.CMIS_1_1;
          this.object = null;
          this.contentStream = null;
          this.stream = null;
      }
  
      /**
       * Writes the entry to an output stream.
       */
      public void write(OutputStream out) throws IOException {
          XmlSerializer writer = XMLUtils.createWriter(out);
  
          // start doc
          XMLUtils.startXmlDocument(writer);
  
          // start entry
          writer.startTag(XMLConstants.NAMESPACE_ATOM, TAG_ENTRY);
  
          writer.attribute("", XMLConstants.PREFIX_ATOM, XMLConstants.NAMESPACE_ATOM);
          writer.attribute("", XMLConstants.PREFIX_CMIS, XMLConstants.NAMESPACE_CMIS);
          writer.attribute("", XMLConstants.PREFIX_RESTATOM, XMLConstants.NAMESPACE_RESTATOM);
  
          if (contentStream != null && contentStream.getFileName() != null) {
              writer.attribute("", XMLConstants.PREFIX_APACHE_CHEMISTY, XMLConstants.NAMESPACE_APACHE_CHEMISTRY);
          }
  
          // atom:id
          writeTag(writer, XMLConstants.NAMESPACE_ATOM, TAG_ATOM_ID, "urn:uuid:00000000-0000-0000-0000-00000000000");
  
          // atom:title
          writeTag(writer, XMLConstants.NAMESPACE_ATOM, TAG_ATOM_TITLE, getTitle());
  
          // atom:updated
          writeTag(writer, XMLConstants.NAMESPACE_ATOM, TAG_ATOM_UPDATED, getUpdated());
  
          // content
          if (stream != null) {
              writer.startTag(XMLConstants.NAMESPACE_RESTATOM, TAG_CONTENT);
  
              writeTag(writer, XMLConstants.NAMESPACE_RESTATOM, TAG_CONTENT_MEDIATYPE, contentStream.getMimeType());
  
              if (contentStream.getFileName() != null) {
                  XMLUtils.write(writer, XMLConstants.PREFIX_APACHE_CHEMISTY, XMLConstants.NAMESPACE_APACHE_CHEMISTRY,
                          TAG_CONTENT_FILENAME, contentStream.getFileName());
              }
  
              writer.startTag(XMLConstants.NAMESPACE_RESTATOM, TAG_CONTENT_BASE64);
              writeContent(writer);
              writer.endTag(XMLConstants.NAMESPACE_RESTATOM, TAG_CONTENT_BASE64);
  
              writer.endTag(XMLConstants.NAMESPACE_RESTATOM, TAG_CONTENT);
          }
  
          // object
          if (object != null) {
              XMLConverter.writeObject(writer, cmisVersion, XMLConstants.NAMESPACE_RESTATOM, object);
          }
  
          // type
          if (typeDef != null) {
              XMLConverter.writeTypeDefinition(writer, cmisVersion, XMLConstants.NAMESPACE_RESTATOM, typeDef);
          }
  
          // bulk update
          if (bulkUpdate != null) {
              XMLConverter.writeBulkUpdate(writer, XMLConstants.NAMESPACE_RESTATOM, bulkUpdate);
          }
  
          // end entry
          writer.endTag(XMLConstants.NAMESPACE_ATOM, TAG_ENTRY);
  
          // end document
          XMLUtils.endXmlDocument(writer);
      }
  
      // ---- internal ----
  
      private String getTitle() {
          String result = "";
  
          if (object != null) {
              PropertyData<?> nameProperty = object.getProperties().getProperties().get(PropertyIds.NAME);
              if (nameProperty instanceof PropertyString) {
                  result = ((PropertyString) nameProperty).getFirstValue();
              }
          }
  
          if (typeDef != null) {
              if (typeDef.getDisplayName() != null) {
                  result = typeDef.getDisplayName();
              }
          }
  
          if (bulkUpdate != null) {
              result = "Bulk Update Properties";
          }
  
          return result;
      }
  
      private void writeContent(XmlSerializer writer) throws IOException {
          @SuppressWarnings("resource")
          Base64.InputStream b64stream = new Base64.InputStream(stream, Base64.ENCODE);
  
          char[] buffer = new char[BUFFER_SIZE];
          int pos = 0;
          int b;
  
          while ((b = b64stream.read()) > -1) {
              buffer[pos++] = (char) (b & 0xFF);
              if (pos == buffer.length) {
                  writer.text(buffer, 0, buffer.length);
                  pos = 0;
              }
          }
          if (pos > 0) {
              writer.text(buffer, 0, pos);
          }
      }
  
      private static String getUpdated() {
          return DateTimeHelper.formatXmlDateTime(new GregorianCalendar(DateTimeHelper.GMT));
      }
  
      private static void writeTag(XmlSerializer writer, String tagNameSpace, String tagName, String text)
              throws IOException {
          writer.startTag(tagNameSpace, tagName);
          writer.text(text);
          writer.endTag(tagNameSpace, tagName);
      }
  }
  