@Test
  public void testReadOnlyEntryFromMultidirectory() throws Exception {
    MultiDirectory readonlyMultidir =
        (MultiDirectory) directoryService.getDirectory("readonlymulti");
    MultiDirectorySession readonlyDir = (MultiDirectorySession) readonlyMultidir.getSession();

    // all should be readonly
    assertTrue(BaseSession.isReadOnlyEntry(readonlyDir.getEntry("1")));
    assertTrue(BaseSession.isReadOnlyEntry(readonlyDir.getEntry("2")));
    assertTrue(BaseSession.isReadOnlyEntry(readonlyDir.getEntry("3")));
    assertTrue(BaseSession.isReadOnlyEntry(readonlyDir.getEntry("4")));
  }
 private static void updateSubDirectoryEntry(
     SubDirectoryInfo dirInfo,
     Map<String, Object> fieldMap,
     String id,
     boolean canCreateIfOptional) {
   DocumentModel dirEntry = dirInfo.getSession().getEntry(id);
   if (dirInfo.getSession().isReadOnly() || (dirEntry != null && isReadOnlyEntry(dirEntry))) {
     return;
   }
   if (dirEntry == null && !canCreateIfOptional) {
     // entry to update doesn't belong to this directory
     return;
   }
   Map<String, Object> map = new HashMap<String, Object>();
   map.put(dirInfo.idField, id);
   for (Entry<String, String> e : dirInfo.fromSource.entrySet()) {
     map.put(e.getValue(), fieldMap.get(e.getKey()));
   }
   if (map.size() > 1) {
     if (canCreateIfOptional && dirInfo.isOptional && dirEntry == null) {
       // if entry does not exist, create it
       dirInfo.getSession().createEntry(map);
     } else {
       final DocumentModel entry =
           BaseSession.createEntryModel(null, dirInfo.dirSchemaName, id, null);
       // Do not set dataModel values with constructor to force fields
       // dirty
       entry.getDataModel(dirInfo.dirSchemaName).setMap(map);
       dirInfo.getSession().updateEntry(entry);
     }
   }
 }
  private static DirectoryEntry getDirectoryEntryFromNode(
      JsonNode propertiesNode, Directory directory) throws DirectoryException, IOException {

    String schema = directory.getSchema();
    String id = propertiesNode.get(directory.getIdField()).getTextValue();

    try (Session session = directory.getSession()) {
      DocumentModel entry = session.getEntry(id);

      if (entry == null) {
        entry = BaseSession.createEntryModel(null, schema, id, new HashMap<>());
      }

      Properties props = new Properties();
      Iterator<Entry<String, JsonNode>> fields = propertiesNode.getFields();
      while (fields.hasNext()) {
        Entry<String, JsonNode> fieldEntry = fields.next();
        props.put(schema + ":" + fieldEntry.getKey(), fieldEntry.getValue().getTextValue());
      }

      DocumentHelper.setProperties(null, entry, props);

      return new DirectoryEntry(directory.getName(), entry);
    }
  }
  @Override
  public DocumentModel getEntry(String id, boolean fetchReferences) throws DirectoryException {
    if (!isCurrentUserAllowed(SecurityConstants.READ)) {
      return null;
    }
    init();
    String entryId = id;
    source_loop:
    for (SourceInfo sourceInfo : sourceInfos) {
      boolean isReadOnlyEntry = true;
      final Map<String, Object> map = new HashMap<String, Object>();

      for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
        final DocumentModel entry = dirInfo.getSession().getEntry(id, fetchReferences);
        boolean isOptional = dirInfo.isOptional;
        if (entry == null && !isOptional) {
          // not in this source
          continue source_loop;
        }
        if (entry != null && !isReadOnlyEntry(entry)) {
          // set readonly to false if at least one source is writable
          isReadOnlyEntry = false;
        }
        if (entry == null && isOptional && !dirInfo.getSession().isReadOnly()) {
          // set readonly to false if null entry is from optional and writable directory
          isReadOnlyEntry = false;
        }
        if (entry != null && entry.getId() == null) {
          entryId = entry.getId();
        }
        for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
          if (entry != null) {
            try {
              map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
            } catch (PropertyException e1) {
              throw new DirectoryException(e1);
            }
          } else {
            // fill with default values for this directory
            if (!map.containsKey(e.getValue())) {
              map.put(e.getValue(), dirInfo.defaultEntry.get(e.getKey()));
            }
          }
        }
      }
      // force the entry in readonly if it's defined on the multidirectory
      if (isReadOnly()) {
        isReadOnlyEntry = true;
      }
      // ok we have the data
      try {
        return BaseSession.createEntryModel(null, schemaName, entryId, map, isReadOnlyEntry);
      } catch (PropertyException e) {
        throw new DirectoryException(e);
      }
    }
    return null;
  }
  @Test
  public void testCreateFromModel() throws Exception {
    String schema = "schema3";
    DocumentModel entry = BaseSession.createEntryModel(null, schema, null, null);
    entry.setProperty("schema3", "uid", "yo");

    assertNull(dir.getEntry("yo"));
    dir.createEntry(entry);
    assertNotNull(dir.getEntry("yo"));

    // create one with existing same id, must fail
    entry.setProperty("schema3", "uid", "1");
    try {
      entry = dir.createEntry(entry);
      fail("Should raise an error, entry already exists");
    } catch (DirectoryException e) {
    }
  }
  @Override
  @SuppressWarnings("boxing")
  public DocumentModelList query(
      Map<String, Serializable> filter,
      Set<String> fulltext,
      Map<String, String> orderBy,
      boolean fetchReferences) {
    // list of entries
    final DocumentModelList results = new DocumentModelListImpl();
    if (!isCurrentUserAllowed(SecurityConstants.READ)) {
      return results;
    }
    init();

    // entry ids already seen (mapped to the source name)
    final Map<String, String> seen = new HashMap<String, String>();
    if (fulltext == null) {
      fulltext = Collections.emptySet();
    }
    Set<String> readOnlyEntries = new HashSet<String>();

    for (SourceInfo sourceInfo : sourceInfos) {
      // accumulated map for each entry
      final Map<String, Map<String, Object>> maps = new HashMap<String, Map<String, Object>>();
      // number of dirs seen for each entry
      final Map<String, Integer> counts = new HashMap<String, Integer>();

      // list of optional dirs where filter matches default values
      List<SubDirectoryInfo> optionalDirsMatching = new ArrayList<SubDirectoryInfo>();
      for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
        // compute filter
        final Map<String, Serializable> dirFilter = new HashMap<String, Serializable>();
        for (Entry<String, Serializable> e : filter.entrySet()) {
          final String fieldName = dirInfo.fromSource.get(e.getKey());
          if (fieldName == null) {
            continue;
          }
          dirFilter.put(fieldName, e.getValue());
        }
        if (dirInfo.isOptional) {
          // check if filter matches directory default values
          boolean matches = true;
          for (Map.Entry<String, Serializable> dirFilterEntry : dirFilter.entrySet()) {
            Object defaultValue = dirInfo.defaultEntry.get(dirFilterEntry.getKey());
            Object filterValue = dirFilterEntry.getValue();
            if (defaultValue == null && filterValue != null) {
              matches = false;
            } else if (defaultValue != null && !defaultValue.equals(filterValue)) {
              matches = false;
            }
          }
          if (matches) {
            optionalDirsMatching.add(dirInfo);
          }
        }
        // compute fulltext
        Set<String> dirFulltext = new HashSet<String>();
        for (String sourceFieldName : fulltext) {
          final String fieldName = dirInfo.fromSource.get(sourceFieldName);
          if (fieldName != null) {
            dirFulltext.add(fieldName);
          }
        }
        // make query to subdirectory
        DocumentModelList l =
            dirInfo.getSession().query(dirFilter, dirFulltext, null, fetchReferences);
        for (DocumentModel entry : l) {
          final String id = entry.getId();
          Map<String, Object> map = maps.get(id);
          if (map == null) {
            map = new HashMap<String, Object>();
            maps.put(id, map);
            counts.put(id, 1);
          } else {
            counts.put(id, counts.get(id) + 1);
          }
          for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
            map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
          }
          if (BaseSession.isReadOnlyEntry(entry)) {
            readOnlyEntries.add(id);
          }
        }
      }
      // add default entry values for optional dirs
      for (SubDirectoryInfo dirInfo : optionalDirsMatching) {
        // add entry for every data found in other dirs
        Set<String> existingIds =
            new HashSet<String>(
                dirInfo
                    .getSession()
                    .getProjection(Collections.<String, Serializable>emptyMap(), dirInfo.idField));
        for (Entry<String, Map<String, Object>> result : maps.entrySet()) {
          final String id = result.getKey();
          if (!existingIds.contains(id)) {
            counts.put(id, counts.get(id) + 1);
            final Map<String, Object> map = result.getValue();
            for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
              String value = e.getValue();
              if (!map.containsKey(value)) {
                map.put(value, dirInfo.defaultEntry.get(e.getKey()));
              }
            }
          }
        }
      }
      // intersection, ignore entries not in all subdirectories
      final int numdirs = sourceInfo.subDirectoryInfos.size();
      for (Iterator<String> it = maps.keySet().iterator(); it.hasNext(); ) {
        final String id = it.next();
        if (counts.get(id) != numdirs) {
          it.remove();
        }
      }
      // now create entries
      ((ArrayList<?>) results).ensureCapacity(results.size() + maps.size());
      for (Entry<String, Map<String, Object>> e : maps.entrySet()) {
        final String id = e.getKey();
        if (seen.containsKey(id)) {
          log.warn(
              String.format(
                  "Entry '%s' is present in source '%s' but also in source '%s'. "
                      + "The second one will be ignored.",
                  id, seen.get(id), sourceInfo.source.name));
          continue;
        }
        final Map<String, Object> map = e.getValue();
        seen.put(id, sourceInfo.source.name);
        final DocumentModel entry =
            BaseSession.createEntryModel(null, schemaName, id, map, readOnlyEntries.contains(id));
        results.add(entry);
      }
    }
    if (orderBy != null && !orderBy.isEmpty()) {
      directory.orderEntries(results, orderBy);
    }
    return results;
  }
  @Override
  @SuppressWarnings("boxing")
  public DocumentModelList getEntries() {
    if (!isCurrentUserAllowed(SecurityConstants.READ)) {
      return null;
    }
    init();

    // list of entries
    final DocumentModelList results = new DocumentModelListImpl();
    // entry ids already seen (mapped to the source name)
    final Map<String, String> seen = new HashMap<String, String>();
    Set<String> readOnlyEntries = new HashSet<String>();

    for (SourceInfo sourceInfo : sourceInfos) {
      // accumulated map for each entry
      final Map<String, Map<String, Object>> maps = new HashMap<String, Map<String, Object>>();
      // number of dirs seen for each entry
      final Map<String, Integer> counts = new HashMap<String, Integer>();
      for (SubDirectoryInfo dirInfo : sourceInfo.requiredSubDirectoryInfos) {
        final DocumentModelList entries = dirInfo.getSession().getEntries();
        for (DocumentModel entry : entries) {
          final String id = entry.getId();
          // find or create map for this entry
          Map<String, Object> map = maps.get(id);
          if (map == null) {
            map = new HashMap<String, Object>();
            maps.put(id, map);
            counts.put(id, 1);
          } else {
            counts.put(id, counts.get(id) + 1);
          }
          // put entry data in map
          for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
            map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
          }
          if (BaseSession.isReadOnlyEntry(entry)) {
            readOnlyEntries.add(id);
          }
        }
      }
      for (SubDirectoryInfo dirInfo : sourceInfo.optionalSubDirectoryInfos) {
        final DocumentModelList entries = dirInfo.getSession().getEntries();
        Set<String> existingIds = new HashSet<String>();
        for (DocumentModel entry : entries) {
          final String id = entry.getId();
          final Map<String, Object> map = maps.get(id);
          if (map != null) {
            existingIds.add(id);
            // put entry data in map
            for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
              map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
            }
          } else {
            log.warn(
                String.format(
                    "Entry '%s' for source '%s' is present in optional directory '%s' "
                        + "but not in any required one. "
                        + "It will be skipped.",
                    id, sourceInfo.source.name, dirInfo.dirName));
          }
        }
        for (Entry<String, Map<String, Object>> mapEntry : maps.entrySet()) {
          if (!existingIds.contains(mapEntry.getKey())) {
            final Map<String, Object> map = mapEntry.getValue();
            // put entry data in map
            for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
              // fill with default values for this directory
              if (!map.containsKey(e.getValue())) {
                map.put(e.getValue(), dirInfo.defaultEntry.get(e.getKey()));
              }
            }
          }
        }
      }
      // now create entries for all full maps
      int numdirs = sourceInfo.requiredSubDirectoryInfos.size();
      ((ArrayList<?>) results).ensureCapacity(results.size() + maps.size());
      for (Entry<String, Map<String, Object>> e : maps.entrySet()) {
        final String id = e.getKey();
        if (seen.containsKey(id)) {
          log.warn(
              String.format(
                  "Entry '%s' is present in source '%s' but also in source '%s'. "
                      + "The second one will be ignored.",
                  id, seen.get(id), sourceInfo.source.name));
          continue;
        }
        final Map<String, Object> map = e.getValue();
        if (counts.get(id) != numdirs) {
          log.warn(
              String.format(
                  "Entry '%s' for source '%s' is not present in all directories. "
                      + "It will be skipped.",
                  id, sourceInfo.source.name));
          continue;
        }
        seen.put(id, sourceInfo.source.name);
        final DocumentModel entry =
            BaseSession.createEntryModel(null, schemaName, id, map, readOnlyEntries.contains(id));
        results.add(entry);
      }
    }
    return results;
  }
  @Test
  public void testReadOnlyEntryInGetEntriesResults() throws Exception {
    Map<String, String> orderBy = new HashMap<String, String>();
    orderBy.put("schema3:uid", "asc");
    DocumentModelComparator comp = new DocumentModelComparator(orderBy);

    DocumentModelList results = dir.getEntries();
    Collections.sort(results, comp);

    // by default no backing dir is readonly
    assertFalse(BaseSession.isReadOnlyEntry(results.get(0)));
    assertFalse(BaseSession.isReadOnlyEntry(results.get(1)));
    assertFalse(BaseSession.isReadOnlyEntry(results.get(2)));
    assertFalse(BaseSession.isReadOnlyEntry(results.get(3)));

    memdir1.setReadOnly(true);
    memdir2.setReadOnly(false);
    memdir3.setReadOnly(false);
    results = dir.getEntries();
    Collections.sort(results, comp);
    assertTrue(BaseSession.isReadOnlyEntry(results.get(0)));
    assertTrue(BaseSession.isReadOnlyEntry(results.get(1)));
    assertFalse(BaseSession.isReadOnlyEntry(results.get(2)));
    assertFalse(BaseSession.isReadOnlyEntry(results.get(3)));

    memdir1.setReadOnly(false);
    memdir2.setReadOnly(false);
    memdir3.setReadOnly(true);
    results = dir.getEntries();
    Collections.sort(results, comp);
    assertFalse(BaseSession.isReadOnlyEntry(results.get(0)));
    assertFalse(BaseSession.isReadOnlyEntry(results.get(1)));
    assertTrue(BaseSession.isReadOnlyEntry(results.get(2)));
    assertTrue(BaseSession.isReadOnlyEntry(results.get(3)));
  }
  @Test
  public void testReadOnlyEntryFromGetEntry() throws Exception {

    // by default no backing dir is readonly
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("1")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("2")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("3")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("4")));

    memdir1.setReadOnly(true);
    memdir2.setReadOnly(false);
    memdir3.setReadOnly(false);
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("1")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("2")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("3")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("4")));

    memdir1.setReadOnly(false);
    memdir2.setReadOnly(true);
    memdir3.setReadOnly(true);
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("1")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("2")));
    assertTrue(BaseSession.isReadOnlyEntry(dir.getEntry("3")));
    assertTrue(BaseSession.isReadOnlyEntry(dir.getEntry("4")));

    memdir1.setReadOnly(false);
    memdir2.setReadOnly(false);
    memdir3.setReadOnly(true);
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("1")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("2")));
    assertTrue(BaseSession.isReadOnlyEntry(dir.getEntry("3")));
    assertTrue(BaseSession.isReadOnlyEntry(dir.getEntry("4")));

    memdir1.setReadOnly(true);
    memdir2.setReadOnly(true);
    memdir3.setReadOnly(false);
    assertTrue(BaseSession.isReadOnlyEntry(dir.getEntry("1")));
    assertTrue(BaseSession.isReadOnlyEntry(dir.getEntry("2")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("3")));
    assertFalse(BaseSession.isReadOnlyEntry(dir.getEntry("4")));
  }