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.inmemory.query;
  
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.COMPLEX_TYPE;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.FOLDER_TYPE;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.PROP_ID_BOOLEAN;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.PROP_ID_DATETIME;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.PROP_ID_DECIMAL;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.PROP_ID_INT;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.PROP_ID_STRING;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.PROP_ID_STRING_MULTI_VALUE;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.SECONDARY_INTEGER_PROP;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.SECONDARY_STRING_PROP;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.SECONDARY_TYPE;
  import static org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator.VERSION_PROPERTY_ID;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.fail;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.GregorianCalendar;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.TimeZone;
  
  import org.apache.chemistry.opencmis.commons.PropertyIds;
  import org.apache.chemistry.opencmis.commons.data.Acl;
  import org.apache.chemistry.opencmis.commons.data.ContentStream;
  import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
  import org.apache.chemistry.opencmis.commons.data.ObjectData;
  import org.apache.chemistry.opencmis.commons.data.Properties;
  import org.apache.chemistry.opencmis.commons.data.PropertyData;
  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.BindingsObjectFactoryImpl;
  import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory;
  import org.apache.chemistry.opencmis.commons.spi.Holder;
  import org.apache.chemistry.opencmis.commons.spi.ObjectService;
  import org.apache.chemistry.opencmis.commons.spi.VersioningService;
  import org.apache.chemistry.opencmis.inmemory.UnitTestTypeSystemCreator;
  import org.apache.chemistry.opencmis.inmemory.storedobj.impl.ContentStreamDataImpl;
  
  /**
   * Utility class that fills the in-memory repository with some test objects that
   * can be used for query
   * 
   *         This class uses the following data for query testing. We have one
   *         document type "ComplexType" and one folder type "FolderType" The
   *         document type has one property of each of the types boolean, integer,
   *         decimal, string and datetime. id, uri and html are treated like a
   *         string and do not make a difference.
   * 
   *         String Int Double DateTime Boolean
   *         ------------------------------------------------ Alpha -100 -1.6E-5
   *         23.05.1618 true Beta -50 -4.0E24 08.05.1945 false Gamma 0 3.141592
   *         (now) true Delta 50 1.23456E-6 20.01.2038 true Epsilon 100 1.2345E12
   *         14.07.2345 false
   * 
   *         For folder and tree tests this series is put in each of the three
   *         test folders
   */
  public class QueryTestDataCreator {
  
      private final BindingsObjectFactory fFactory = new BindingsObjectFactoryImpl();
      private final String rootFolderId;
      private final String repositoryId;
      private final ObjectService fObjSvc;
      private final VersioningService fVerSvc;
      private String doc1, doc2, doc3, doc4, doc5;
      private String folder1;
      private String folder2;
      private String folder11;
      private static final TimeZone TZ = TimeZone.getTimeZone("Zulu");
  
      public QueryTestDataCreator(String repositoryId, String rootFolderId, ObjectService objSvc, VersioningService verSvc) {
          this.rootFolderId = rootFolderId;
          this.repositoryId = repositoryId;
          fObjSvc = objSvc;
          fVerSvc = verSvc;
      }
  
      public String getFolder1() {
          return folder1;
      }
  
      public String getFolder2() {
          return folder2;
      }
  
      public String getFolder11() {
          return folder11;
      }
  
      public void createBasicTestData() {
          createTestFolders();
          createBasicTestDocuments();
      }
  
      @SuppressWarnings("serial")
      public void createBasicTestDocuments() {
  
          final GregorianCalendar gc1 = new GregorianCalendar(TZ);
          gc1.clear();
          gc1.set(1945, 4, 8);
  
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Alpha");
                  put(PROP_ID_INT, Integer.valueOf(-100));
                  put(PROP_ID_DECIMAL, Double.valueOf(-4.0E24d));
                  put(PROP_ID_DATETIME, gc1);
                  put(PROP_ID_BOOLEAN, true);
              }
          };
          ContentStream content1 = createContent("I have a cat.");
          doc1 = createDocument("alpha", rootFolderId, COMPLEX_TYPE, propertyMap1, content1);
          assertNotNull(doc1);
  
          final GregorianCalendar gc2 = new GregorianCalendar(TZ);
          gc2.clear();
          gc2.set(1618, 4, 23);
  
          final Map<String, Object> propertyMap2 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Beta");
                  put(PROP_ID_INT, Integer.valueOf(-50));
                  put(PROP_ID_DECIMAL, Double.valueOf(-1.6E-5d));
                  put(PROP_ID_DATETIME, gc2);
                  put(PROP_ID_BOOLEAN, false);
              }
          };
          ContentStream content2 = createContent("I have a cat named Kitty Katty.");
          doc2 = createDocument("beta", rootFolderId, COMPLEX_TYPE, propertyMap2, content2);
          assertNotNull(doc2);
  
          final Map<String, Object> propertyMap3 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Gamma");
                  put(PROP_ID_INT, Integer.valueOf(0));
                  put(PROP_ID_DECIMAL, Double.valueOf(Math.PI));
                  put(PROP_ID_DATETIME, new GregorianCalendar(TZ));
                  put(PROP_ID_BOOLEAN, true);
              }
          };
  
          ContentStream content3 = createContent("I have a dog.");
          doc3 = createDocument("gamma", rootFolderId, COMPLEX_TYPE, propertyMap3, content3);
          assertNotNull(doc3);
  
          final GregorianCalendar gc4 = new GregorianCalendar(TZ);
          gc4.clear();
          gc4.set(2038, 0, 20);
  
          final Map<String, Object> propertyMap4 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Delta");
                  put(PROP_ID_INT, Integer.valueOf(50));
                  put(PROP_ID_DECIMAL, Double.valueOf(1.23456E-6));
                  put(PROP_ID_DATETIME, gc4);
                  put(PROP_ID_BOOLEAN, true);
              }
          };
          ContentStream content4 = createContent("I have a cat and a dog.");
          doc4 = createDocument("delta", rootFolderId, COMPLEX_TYPE, propertyMap4, content4);
          assertNotNull(doc4);
  
          final GregorianCalendar gc5 = new GregorianCalendar(TZ);
          gc5.clear();
          gc5.set(2345, 6, 14);
  
          final Map<String, Object> propertyMap5 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Epsilon");
                  put(PROP_ID_INT, Integer.valueOf(100));
                  put(PROP_ID_DECIMAL, Double.valueOf(1.2345E12));
                  put(PROP_ID_DATETIME, gc5);
                  put(PROP_ID_BOOLEAN, false);
              }
          };
          ContentStream content5 = createContent("I hate having pets.");
          doc5 = createDocument("epsilon", rootFolderId, COMPLEX_TYPE, propertyMap5, content5);
          assertNotNull(doc5);
          
          final Map<String, Object> propertyMap6 = new HashMap<String, Object>();
          doc4 = createDocument("John's Document", rootFolderId, BaseTypeId.CMIS_DOCUMENT.value(), propertyMap6, null);
          assertNotNull(doc4);
  
      }
  
      @SuppressWarnings("serial")
      public void createMultiValueDocuments() {
          final List<String> mvProps1 = new ArrayList<String>() {
              {
                  add("red");
                  add("green");
                  add("blue");
              }
          };
  
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING_MULTI_VALUE, mvProps1);
                  put(PROP_ID_INT, Integer.valueOf(100));
              }
          };
          createDocument("mv-alpha", rootFolderId, COMPLEX_TYPE, propertyMap1);
  
          final List<String> mvProps2 = new ArrayList<String>() {
              {
                  add("red");
                  add("pink");
                  add("violet");
              }
          };
  
          final Map<String, Object> propertyMap2 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING_MULTI_VALUE, mvProps2);
                  put(PROP_ID_INT, Integer.valueOf(200));
              }
          };
          createDocument("mv-beta", rootFolderId, COMPLEX_TYPE, propertyMap2);
      }
  
      @SuppressWarnings("serial")
      public void createTestFolders() {
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_INT, Integer.valueOf(1234));
                  put(PROP_ID_STRING, "ABCD");
              }
          };
          folder1 = createFolder("Folder 1", rootFolderId, FOLDER_TYPE, propertyMap1);
  
          final Map<String, Object> propertyMap2 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_INT, Integer.valueOf(-2345));
                  put(PROP_ID_STRING, "defg");
              }
          };
          folder2 = createFolder("Folder 2", rootFolderId, FOLDER_TYPE, propertyMap2);
  
          final Map<String, Object> propertyMap3 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_INT, Integer.valueOf(123));
                  put(PROP_ID_STRING, "ZZZZ");
              }
          };
          folder11 = createFolder("Folder 11", folder1, FOLDER_TYPE, propertyMap3);
      }
  
      @SuppressWarnings("serial")
      public void createNullTestDocument() {
  
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "DocumentWithNulls");
              }
          };
          createDocument("nulldoc", rootFolderId, COMPLEX_TYPE, propertyMap1);
      }
  
      @SuppressWarnings("serial")
      public void createLikeTestDocuments(String folderId) {
  
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "ABCDEF");
              }
          };
          createDocument("likedoc1", folderId, COMPLEX_TYPE, propertyMap1);
  
          final Map<String, Object> propertyMap2 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "ABC123");
              }
          };
          createDocument("likedoc2", folderId, COMPLEX_TYPE, propertyMap2);
  
          final Map<String, Object> propertyMap3 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "123ABC");
              }
          };
          createDocument("likedoc3", folderId, COMPLEX_TYPE, propertyMap3);
      }
  
      @SuppressWarnings("serial")
      public String createVersionedDocument() {
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(VERSION_PROPERTY_ID, "ver123");
              }
          };
  
          String verIdV1 = createDocument("verdoc1", rootFolderId, UnitTestTypeSystemCreator.VERSIONED_TYPE,
                  propertyMap1, VersioningState.MAJOR, null);
          ObjectData version = fObjSvc.getObject(repositoryId, verIdV1, "*", false, IncludeRelationships.NONE, null,
                  false, false, null);
  
          // get version series id
          String verIdSer = (String) version.getProperties().getProperties().get(PropertyIds.VERSION_SERIES_ID)
                  .getFirstValue();
  
          // create second version
          final Map<String, Object> propertyMap2 = new HashMap<String, Object>() {
              {
                  put(VERSION_PROPERTY_ID, "ver456");
              }
          };
          Properties propsV2 = createDocumentProperties(null, null, propertyMap2);
  
          Holder<String> idHolder = new Holder<String>(verIdV1);
          Holder<Boolean> contentCopied = new Holder<Boolean>(false);
          fVerSvc.checkOut(repositoryId, idHolder, null, contentCopied);
  
          // Test check-in and pass content and properties
          String checkinComment = "Here comes next version.";
          fVerSvc.checkIn(repositoryId, idHolder, true, propsV2, null, checkinComment, null, null, null, null);
          // String verIdV2 = idHolder.getValue();
          return verIdSer;
      }
  
      @SuppressWarnings("serial")
      public void createSecondaryTestDocuments() {
  
          final int intPropVal1 = 100;
          final String stringPropVal1 = "Secondary Property Value";
          final int intPropVal2 = 200;
          final String stringPropVal2 = "Secondary String";
  
          final GregorianCalendar gc1 = new GregorianCalendar(TZ);
          gc1.clear();
          gc1.set(1945, 4, 8);
  
          final Map<String, Object> propertyMap1 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Secondary");
                  put(PROP_ID_INT, Integer.valueOf(-100));
                  put(PROP_ID_DECIMAL, Double.valueOf(-4.0E24d));
                  put(PROP_ID_DATETIME, gc1);
                  put(PROP_ID_BOOLEAN, true);
                  put(SECONDARY_STRING_PROP, stringPropVal1);
                  put(SECONDARY_INTEGER_PROP, intPropVal1);
                  put(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, SECONDARY_TYPE);
              }
          };
          ContentStream content1 = createContent("Some more content.");
          doc1 = createDocument("docwithsecondary", rootFolderId, COMPLEX_TYPE, propertyMap1, content1);
          assertNotNull(doc1);
  
          final Map<String, Object> propertyMap2 = new HashMap<String, Object>() {
              {
                  put(PROP_ID_STRING, "Secondary 2");
                  put(PROP_ID_INT, Integer.valueOf(123));
                  put(PROP_ID_DECIMAL, Double.valueOf(1.23E24d));
                  put(PROP_ID_BOOLEAN, false);
                  put(SECONDARY_STRING_PROP, stringPropVal2);
                  put(SECONDARY_INTEGER_PROP, intPropVal2);
                  put(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, SECONDARY_TYPE);
              }
          };
          ContentStream content2 = createContent("Even still some more content.");
          doc1 = createDocument("docwithsecondary2", rootFolderId, COMPLEX_TYPE, propertyMap2, content2);
          assertNotNull(doc1);
      }
  
      private String createFolder(String folderName, String parentFolderId, String typeId, Map<String, Object> properties) {
          Properties props = createFolderProperties(folderName, typeId, properties);
          String id = null;
          try {
              id = fObjSvc.createFolder(repositoryId, props, parentFolderId, null, null, null, null);
              if (null == id) {
                  fail("createFolder failed.");
              }
          } catch (Exception e) {
              fail("createFolder() failed with exception: " + e);
          }
          return id;
      }
  
      private String createDocument(String name, String folderId, String typeId, Map<String, Object> properties) {
          return createDocument(name, folderId, typeId, properties, VersioningState.NONE, null);
      }
  
      private String createDocument(String name, String folderId, String typeId, Map<String, Object> properties,
              ContentStream contentStream) {
          return createDocument(name, folderId, typeId, properties, VersioningState.NONE, contentStream);
      }
  
      private String createDocument(String name, String folderId, String typeId, Map<String, Object> properties,
              VersioningState verState, ContentStream contentStream) {
          List<String> policies = null;
          Acl addACEs = null;
          Acl removeACEs = null;
          ExtensionsData extension = null;
  
          Properties props = createDocumentProperties(name, typeId, properties);
  
          String id = null;
          try {
              id = fObjSvc.createDocument(repositoryId, props, folderId, contentStream, verState, policies, addACEs,
                      removeACEs, extension);
              if (null == id) {
                  fail("createDocument failed.");
              }
          } catch (Exception e) {
              fail("createDocument() failed with exception: " + e);
          }
          return id;
  
      }
  
      private Properties createDocumentProperties(String name, String typeId, Map<String, Object> propertyMap) {
          List<PropertyData<?>> properties = new ArrayList<PropertyData<?>>();
          if (name != null) {
              properties.add(fFactory.createPropertyIdData(PropertyIds.NAME, name));
          }
          if (typeId != null) {
              properties.add(fFactory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, typeId));
          }
          for (Map.Entry<String, Object> propEntry : propertyMap.entrySet()) {
              PropertyData<?> pd = createPropertyData(propEntry.getKey(), propEntry.getValue());
              properties.add(pd);
          }
          Properties props = fFactory.createPropertiesData(properties);
          return props;
      }
  
      private Properties createFolderProperties(String folderName, String typeId, Map<String, Object> propertyMap) {
          List<PropertyData<?>> properties = new ArrayList<PropertyData<?>>();
          properties.add(fFactory.createPropertyIdData(PropertyIds.NAME, folderName));
          properties.add(fFactory.createPropertyIdData(PropertyIds.OBJECT_TYPE_ID, typeId));
          for (Map.Entry<String, Object> propEntry : propertyMap.entrySet()) {
              PropertyData<?> pd = createPropertyData(propEntry.getKey(), propEntry.getValue());
              properties.add(pd);
          }
          Properties props = fFactory.createPropertiesData(properties);
          return props;
      }
  
      /**
       * Simplified property creation method, create Property of Boolean, String,
       * Integer, Decimal, or DataTime depending on class of value (Boolean,
       * String, Integer, Double, or GregorianCalendar. Id, Html and URI are not
       * supported
       * 
       * @param propId
       * @param value
       * @return
       */
      @SuppressWarnings("unchecked")
      private PropertyData<?> createPropertyData(String propId, Object value) {
          Class<?> clazz = value.getClass();
          if (clazz.equals(Boolean.class)) {
              return fFactory.createPropertyBooleanData(propId, (Boolean) value);
          } else if (clazz.equals(Double.class)) {
              return fFactory.createPropertyDecimalData(propId, BigDecimal.valueOf((Double) value));
          } else if (clazz.equals(Integer.class)) {
              return fFactory.createPropertyIntegerData(propId, BigInteger.valueOf((Integer) value));
          } else if (clazz.equals(String.class)) {
              return fFactory.createPropertyStringData(propId, (String) value);
          } else if (clazz.equals(GregorianCalendar.class)) {
              return fFactory.createPropertyDateTimeData(propId, (GregorianCalendar) value);
          } else if (value instanceof List) {
              clazz = ((List<?>) value).get(0).getClass();
              if (clazz.equals(Boolean.class)) {
                  return fFactory.createPropertyBooleanData(propId, (List<Boolean>) value);
              } else if (clazz.equals(Double.class)) {
                  return fFactory.createPropertyDecimalData(propId, (List<BigDecimal>) value);
              } else if (clazz.equals(Integer.class)) {
                  return fFactory.createPropertyIntegerData(propId, (List<BigInteger>) value);
              } else if (clazz.equals(String.class)) {
                  return fFactory.createPropertyStringData(propId, (List<String>) value);
              } else if (clazz.equals(GregorianCalendar.class)) {
                  return fFactory.createPropertyDateTimeData(propId, (List<GregorianCalendar>) value);
              } else {
                  fail("unsupported type in propery value: " + clazz);
              }
          } else {
              fail("unsupported type in propery value: " + clazz);
          }
          return null;
      }
  
      private ContentStream createContent(String text) {
          ContentStreamDataImpl content = new ContentStreamDataImpl(-1);
          content.setFileName("data.txt");
          content.setMimeType("text/plain");
  
          try {
              content.setContent(new ByteArrayInputStream(text.getBytes()));
          } catch (IOException e) {
              throw new RuntimeException("Failed to fill content stream with data", e);
          }
          return content;
      }
  
  }
  