You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by fo...@apache.org on 2020/12/22 12:54:34 UTC

svn commit: r1884702 - in /jackrabbit/oak/trunk: oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ oak-search-elastic/src/test...

Author: fortino
Date: Tue Dec 22 12:54:34 2020
New Revision: 1884702

URL: http://svn.apache.org/viewvc?rev=1884702&view=rev
Log:
OAK-9302: oak-search-elastic -> configurable query fetch sizes

Modified:
    jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
    jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
    jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java
    jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java
    jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java
    jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java

Modified: jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java?rev=1884702&r1=1884701&r2=1884702&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java (original)
+++ jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java Tue Dec 22 12:54:34 2020
@@ -31,6 +31,7 @@ import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
 import static org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValue;
+import static org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValues;
 
 public class ElasticIndexDefinition extends IndexDefinition {
 
@@ -57,6 +58,9 @@ public class ElasticIndexDefinition exte
     public static final String NUMBER_OF_REPLICAS = "numberOfReplicas";
     public static final int NUMBER_OF_REPLICAS_DEFAULT = 1;
 
+    public static final String QUERY_FETCH_SIZES = "queryFetchSizes";
+    public static final Long[] QUERY_FETCH_SIZES_DEFAULT = new Long[]{100L, 1000L};
+
     /**
      * Hidden property for storing a seed value to be used as suffix in remote index name.
      */
@@ -95,6 +99,7 @@ public class ElasticIndexDefinition exte
     private final String remoteAlias;
     public final int numberOfShards;
     public final int numberOfReplicas;
+    public final int[] queryFetchSizes;
 
     private final Map<String, List<PropertyDefinition>> propertiesByName;
     private final List<PropertyDefinition> dynamicBoostProperties;
@@ -109,6 +114,8 @@ public class ElasticIndexDefinition exte
         this.bulkRetriesBackoff = getOptionalValue(defn, BULK_RETRIES_BACKOFF, BULK_RETRIES_BACKOFF_DEFAULT);
         this.numberOfShards = getOptionalValue(defn, NUMBER_OF_SHARDS, NUMBER_OF_SHARDS_DEFAULT);
         this.numberOfReplicas = getOptionalValue(defn, NUMBER_OF_REPLICAS, NUMBER_OF_REPLICAS_DEFAULT);
+        this.queryFetchSizes = Arrays.stream(getOptionalValues(defn, QUERY_FETCH_SIZES, Type.LONGS, Long.class, QUERY_FETCH_SIZES_DEFAULT))
+                .mapToInt(Long::intValue).toArray();
 
         this.propertiesByName = getDefinedRules()
                 .stream()

Modified: jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java?rev=1884702&r1=1884701&r2=1884702&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java (original)
+++ jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java Tue Dec 22 12:54:34 2020
@@ -169,8 +169,6 @@ public class ElasticResultRowAsyncIterat
     class ElasticQueryScanner implements ActionListener<SearchResponse> {
 
         private static final int SMALL_RESULT_SET_SIZE = 10;
-        private static final int MEDIUM_RESULT_SET_SIZE = 100;
-        private static final int LARGE_RESULT_SET_SIZE = 1000;
 
         private final Set<ElasticResponseListener> allListeners = new HashSet<>();
         private final List<SearchHitListener> searchHitListeners = new ArrayList<>();
@@ -183,9 +181,9 @@ public class ElasticResultRowAsyncIterat
         // concurrent data structures to coordinate chunks loading
         private final AtomicBoolean anyDataLeft = new AtomicBoolean(false);
 
-        private int scannedRows = 0;
-        private boolean firstRequest = true;
-        private boolean fullScan = false;
+        private int scannedRows;
+        private int requests;
+        private boolean fullScan;
         private long searchStartTime;
 
         // reference to the last document sort values for search_after queries
@@ -224,9 +222,9 @@ public class ElasticResultRowAsyncIterat
 
             final SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
                     .query(query)
-                    // use a smaller size when the client asks for facets. This improves performance
+                    // use a smaller size when the query contains aggregations. This improves performance
                     // when the client is only interested in insecure facets
-                    .size(needsAggregations.get() ? SMALL_RESULT_SET_SIZE : MEDIUM_RESULT_SET_SIZE)
+                    .size(needsAggregations.get() ? Math.min(SMALL_RESULT_SET_SIZE, getFetchSize(requests)) : getFetchSize(requests))
                     .fetchSource(sourceFields, null);
 
             this.sorts.forEach(searchSourceBuilder::sort);
@@ -242,6 +240,7 @@ public class ElasticResultRowAsyncIterat
             semaphore.tryAcquire();
 
             searchStartTime = System.currentTimeMillis();
+            requests++;
             indexNode.getConnection().getClient().searchAsync(searchRequest, RequestOptions.DEFAULT, this);
             elasticMetricHandler.markQuery(true);
         }
@@ -271,7 +270,7 @@ public class ElasticResultRowAsyncIterat
                 // now that we got the last hit we can release the semaphore to potentially unlock other requests
                 semaphore.release();
 
-                if (firstRequest) {
+                if (requests == 1) {
                     for (SearchHitListener l : searchHitListeners) {
                         l.startData(totalHits);
                     }
@@ -283,8 +282,6 @@ public class ElasticResultRowAsyncIterat
                             l.on(aggregations);
                         }
                     }
-
-                    firstRequest = false;
                 }
 
                 LOG.trace("Emitting {} search hits, for a total of {} scanned results", searchHits.length, scannedRows);
@@ -321,7 +318,7 @@ public class ElasticResultRowAsyncIterat
             if (semaphore.tryAcquire() && anyDataLeft.get()) {
                 final SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
                         .query(query)
-                        .size(LARGE_RESULT_SET_SIZE)
+                        .size(getFetchSize(requests++))
                         .fetchSource(sourceFields, null)
                         .searchAfter(lastHitSortValues);
 
@@ -339,6 +336,13 @@ public class ElasticResultRowAsyncIterat
             }
         }
 
+        /* picks the size in the fetch array at index=requests or the last if out of bound */
+        private int getFetchSize(int requestId) {
+            int[] queryFetchSizes = indexNode.getDefinition().queryFetchSizes;
+            return queryFetchSizes.length > requestId ?
+                    queryFetchSizes[requestId] : queryFetchSizes[queryFetchSizes.length -1];
+        }
+
         // close all listeners
         private void close() {
             semaphore.release();

Modified: jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java?rev=1884702&r1=1884701&r2=1884702&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticAbstractQueryTest.java Tue Dec 22 12:54:34 2020
@@ -143,8 +143,7 @@ public abstract class ElasticAbstractQue
         esConnection = elasticRule.useDocker() ? elasticRule.getElasticConnectionForDocker() :
                 elasticRule.getElasticConnectionFromString();
         ElasticIndexEditorProvider editorProvider = getElasticIndexEditorProvider(esConnection);
-        ElasticIndexProvider indexProvider = new ElasticIndexProvider(esConnection,
-                new ElasticMetricHandler(StatisticsProvider.NOOP));
+        ElasticIndexProvider indexProvider = new ElasticIndexProvider(esConnection, getMetricHandler());
 
         nodeStore = getNodeStore();
 
@@ -172,6 +171,10 @@ public abstract class ElasticAbstractQue
         return oak.createContentRepository();
     }
 
+    protected ElasticMetricHandler getMetricHandler() {
+        return new ElasticMetricHandler(StatisticsProvider.NOOP);
+    }
+
     protected void assertEventually(Runnable r) {
         ElasticTestUtils.assertEventually(r,
                 ((useAsyncIndexing() ? DEFAULT_ASYNC_INDEXING_TIME_IN_SECONDS : 0) + BULK_FLUSH_INTERVAL_MS_DEFAULT) * 5);

Modified: jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java?rev=1884702&r1=1884701&r2=1884702&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java (original)
+++ jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticContentTest.java Tue Dec 22 12:54:34 2020
@@ -17,20 +17,39 @@
 package org.apache.jackrabbit.oak.plugins.index.elastic;
 
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Random;
 import java.util.UUID;
+import java.util.function.BiConsumer;
+import java.util.stream.IntStream;
 
 import static org.apache.jackrabbit.oak.plugins.index.elastic.ElasticTestUtils.randomString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 public class ElasticContentTest extends ElasticAbstractQueryTest {
 
+    private final ElasticMetricHandler spyMetricHandler = spy(new ElasticMetricHandler(StatisticsProvider.NOOP));
+
+    @Override
+    protected ElasticMetricHandler getMetricHandler() {
+        return spyMetricHandler;
+    }
+
     @Test
     public void indexWithAnalyzedProperty() throws Exception {
         IndexDefinitionBuilder builder = createIndex("a").noAsync();
@@ -115,7 +134,7 @@ public class ElasticContentTest extends
         assertTrue(exists(index));
 
         builder = createIndex("a").noAsync();
-        index = setIndex(testId, builder);
+        setIndex(testId, builder);
         root.commit();
     }
 
@@ -135,4 +154,43 @@ public class ElasticContentTest extends
 
         assertEventually(() -> assertThat(countDocuments(index), equalTo(1L)));
     }
+
+    @Test
+    public void indexWithCustomFetchSizes() throws Exception {
+        BiConsumer<String, Iterable<Long>> buildIndex = (p, fetchSizes) -> {
+            IndexDefinitionBuilder builder = createIndex(p).noAsync();
+            builder.getBuilderTree().setProperty("queryFetchSizes", fetchSizes, Type.LONGS);
+            builder.indexRule("nt:base").property(p).propertyIndex();
+            setIndex(UUID.randomUUID().toString(), builder);
+        };
+
+        buildIndex.accept("a", Collections.singletonList(1L));
+        buildIndex.accept("b", Arrays.asList(1L, 2L));
+        buildIndex.accept("c", Arrays.asList(3L, 100L));
+        root.commit();
+
+        Tree content = root.getTree("/").addChild("content");
+        IntStream.range(0, 3).forEach(n -> {
+                    Tree child = content.addChild("child_" + n);
+                    child.setProperty("a", "text");
+                    child.setProperty("b", "text");
+                    child.setProperty("c", "text");
+                }
+        );
+        root.commit(Collections.singletonMap("sync-mode", "rt"));
+
+        List<String> results = Arrays.asList("/content/child_0", "/content/child_1", "/content/child_2");
+
+        reset(spyMetricHandler);
+        assertQuery("select [jcr:path] from [nt:base] where [a] = 'text'", results);
+        verify(spyMetricHandler, times(3)).markQuery(anyBoolean());
+
+        reset(spyMetricHandler);
+        assertQuery("select [jcr:path] from [nt:base] where [b] = 'text'", results);
+        verify(spyMetricHandler, times(2)).markQuery(anyBoolean());
+
+        reset(spyMetricHandler);
+        assertQuery("select [jcr:path] from [nt:base] where [c] = 'text'", results);
+        verify(spyMetricHandler, times(1)).markQuery(anyBoolean());
+    }
 }

Modified: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java?rev=1884702&r1=1884701&r2=1884702&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java (original)
+++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java Tue Dec 22 12:54:34 2020
@@ -85,6 +85,7 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.*;
 import static org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition.DEFAULT_BOOST;
 import static org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValue;
+import static org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValues;
 import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
 import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.JCR_NODE_TYPES;
 import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.NODE_TYPES_PATH;
@@ -362,7 +363,7 @@ public class IndexDefinition implements
             this.definition = defn;
             this.indexPath = checkNotNull(indexPath);
             this.indexName = indexPath;
-            this.indexTags = getOptionalStrings(defn, IndexConstants.INDEX_TAGS);
+            this.indexTags = getOptionalValues(defn, IndexConstants.INDEX_TAGS, Type.STRINGS, String.class);
             this.nodeTypeIndex = getOptionalValue(defn, FulltextIndexConstants.PROP_INDEX_NODE_TYPE, false);
 
             this.blobSize = getOptionalValue(defn, BLOB_SIZE, DEFAULT_BLOB_SIZE);
@@ -411,7 +412,7 @@ public class IndexDefinition implements
             this.scorerProviderName = getOptionalValue(defn, FulltextIndexConstants.PROP_SCORER_PROVIDER, null);
             this.reindexCount = getOptionalValue(defn, REINDEX_COUNT, 0);
             this.pathFilter = PathFilter.from(new ReadOnlyBuilder(defn));
-            this.queryPaths = getOptionalStrings(defn, IndexConstants.QUERY_PATHS);
+            this.queryPaths = getOptionalValues(defn, IndexConstants.QUERY_PATHS, Type.STRINGS, String.class);
             this.suggestAnalyzed = evaluateSuggestAnalyzed(defn, false);
 
             {
@@ -678,8 +679,7 @@ public class IndexDefinition implements
 
     @Nullable
     public Aggregate getAggregate(String nodeType){
-        Aggregate agg = aggregates.get(nodeType);
-        return agg;
+        return aggregates.get(nodeType);
     }
 
     private Map<String, Aggregate> collectAggregates(NodeState defn) {
@@ -728,8 +728,7 @@ public class IndexDefinition implements
         List<IndexingRule> rules = null;
         List<IndexingRule> r = indexRules.get(primaryNodeType);
         if (r != null) {
-            rules = new ArrayList<IndexingRule>();
-            rules.addAll(r);
+            rules = new ArrayList<>(r);
         }
 
         if (rules != null) {
@@ -758,15 +757,14 @@ public class IndexDefinition implements
         List<IndexingRule> rules = null;
         List<IndexingRule> r = indexRules.get(getPrimaryTypeName(state));
         if (r != null) {
-            rules = new ArrayList<IndexingRule>();
-            rules.addAll(r);
+            rules = new ArrayList<>(r);
         }
 
         for (String name : getMixinTypeNames(state)) {
             r = indexRules.get(name);
             if (r != null) {
                 if (rules == null) {
-                    rules = new ArrayList<IndexingRule>();
+                    rules = new ArrayList<>();
                 }
                 rules.addAll(r);
             }
@@ -819,11 +817,7 @@ public class IndexDefinition implements
 
             for (String ntName : ntNames) {
                 if (ntReg.isNodeType(ntName, rule.getNodeTypeName())) {
-                    List<IndexingRule> perNtConfig = nt2rules.get(ntName);
-                    if (perNtConfig == null) {
-                        perNtConfig = new ArrayList<IndexingRule>();
-                        nt2rules.put(ntName, perNtConfig);
-                    }
+                    List<IndexingRule> perNtConfig = nt2rules.computeIfAbsent(ntName, k -> new ArrayList<>());
                     log.trace("Registering rule '{}' for name '{}'", rule, ntName);
                     perNtConfig.add(new IndexingRule(rule, ntName));
                 }
@@ -837,11 +831,6 @@ public class IndexDefinition implements
         return ImmutableMap.copyOf(nt2rules);
     }
 
-    private boolean areAllTypesIndexed() {
-        IndexingRule ntBaseRule = getApplicableIndexingRule(NT_BASE);
-        return ntBaseRule != null;
-    }
-
     private boolean evaluateSuggestionEnabled() {
         for (IndexingRule indexingRule : definedRules) {
             for (PropertyDefinition propertyDefinition : indexingRule.propConfigs.values()) {
@@ -1505,7 +1494,7 @@ public class IndexDefinition implements
             }
         }
 
-        List<String> propNames = new ArrayList<String>(propNamesSet);
+        List<String> propNames = new ArrayList<>(propNamesSet);
 
         final String includeAllProp = FulltextIndexConstants.REGEX_ALL_PROPS;
         if (fullTextEnabled
@@ -1616,7 +1605,7 @@ public class IndexDefinition implements
 
     private static Set<String> getMultiProperty(NodeState definition, String propName){
         PropertyState pse = definition.getProperty(propName);
-        return pse != null ? ImmutableSet.copyOf(pse.getValue(Type.STRINGS)) : Collections.<String>emptySet();
+        return pse != null ? ImmutableSet.copyOf(pse.getValue(Type.STRINGS)) : Collections.emptySet();
     }
 
     private static Set<String> toLowerCase(Set<String> values) {
@@ -1784,14 +1773,6 @@ public class IndexDefinition implements
         return result;
     }
 
-    private static String[] getOptionalStrings(NodeState defn, String propertyName) {
-        PropertyState ps = defn.getProperty(propertyName);
-        if (ps != null) {
-            return Iterables.toArray(ps.getValue(Type.STRINGS), String.class);
-        }
-        return null;
-    }
-
     private static IndexFormatVersion versionFrom(PropertyState ps){
         return IndexFormatVersion.getVersion(Ints.checkedCast(ps.getValue(Type.LONG)));
     }

Modified: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java?rev=1884702&r1=1884701&r2=1884702&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java (original)
+++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java Tue Dec 22 12:54:34 2020
@@ -21,6 +21,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import java.util.Collections;
 
+import com.google.common.collect.Iterables;
 import com.google.common.primitives.Ints;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.Blob;
@@ -40,10 +41,10 @@ public class ConfigUtil {
 
     public static boolean getOptionalValue(NodeState definition, String propName, boolean defaultVal) {
         try {
-             PropertyState ps = definition.getProperty(propName);
-             return ps == null ? defaultVal : ps.getValue(Type.BOOLEAN);
+            PropertyState ps = definition.getProperty(propName);
+            return ps == null ? defaultVal : ps.getValue(Type.BOOLEAN);
         } catch (IllegalStateException e) {
-             throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
+            throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
         }
     }
 
@@ -52,7 +53,7 @@ public class ConfigUtil {
             PropertyState ps = definition.getProperty(propName);
             return ps == null ? defaultVal : Ints.checkedCast(ps.getValue(Type.LONG));
         } catch (IllegalStateException e) {
-             throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
+            throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
         }
     }
 
@@ -61,7 +62,7 @@ public class ConfigUtil {
             PropertyState ps = definition.getProperty(propName);
             return ps == null ? defaultVal : ps.getValue(Type.STRING);
         } catch (IllegalStateException e) {
-             throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
+            throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
         }
     }
 
@@ -70,7 +71,7 @@ public class ConfigUtil {
             PropertyState ps = definition.getProperty(propName);
             return ps == null ? defaultVal : ps.getValue(Type.DOUBLE).floatValue();
         } catch (IllegalStateException e) {
-             throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
+            throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
         }
     }
 
@@ -79,10 +80,10 @@ public class ConfigUtil {
             PropertyState ps = definition.getProperty(propName);
             return ps == null ? defaultVal : ps.getValue(Type.DOUBLE);
         } catch (IllegalStateException e) {
-             throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
+            throw new IllegalStateException(String.format(ILLEGAL_STATE_EXCEPTION_ERROR_MESSAGE, propName), e);
         }
     }
-    
+
     public static long getOptionalValue(NodeState definition, String propName, long defaultVal) {
         try {
             PropertyState ps = definition.getProperty(propName);
@@ -103,7 +104,7 @@ public class ConfigUtil {
 
     public static Iterable<String> getMixinNames(NodeState nodeState) {
         PropertyState ps = nodeState.getProperty(JcrConstants.JCR_MIXINTYPES);
-        return (ps == null) ? Collections.<String>emptyList() : ps.getValue(Type.NAMES);
+        return (ps == null) ? Collections.emptyList() : ps.getValue(Type.NAMES);
     }
 
     /**
@@ -117,4 +118,22 @@ public class ConfigUtil {
         PropertyState property = contentNode.getProperty(JcrConstants.JCR_DATA);
         return property != null ? property.getValue(Type.BINARY) : null;
     }
+
+    /**
+     * Returns an array of optional values for the a given property
+     */
+    public static<T> T[] getOptionalValues(NodeState definition, String propName, Type<Iterable<T>> type, Class<T> typeParam) {
+        return getOptionalValues(definition, propName, type, typeParam, null);
+    }
+
+    /**
+     * Returns an array of optional values for the a given property if present, otherwise returns the specified default
+     */
+    public static<T> T[] getOptionalValues(NodeState definition, String propName, Type<Iterable<T>> type, Class<T> typeParam, T[] defaultValues) {
+        PropertyState ps = definition.getProperty(propName);
+        if (ps != null) {
+            return Iterables.toArray(ps.getValue(type), typeParam);
+        }
+        return defaultValues;
+    }
 }