View Javadoc

1   /*
2    * JBoss, Home of Professional Open Source.
3    * Copyright 2008, Red Hat Middleware LLC, and individual contributors
4    * as indicated by the @author tags. See the copyright.txt file in the
5    * distribution for a full listing of individual contributors. 
6    *
7    * This is free software; you can redistribute it and/or modify it
8    * under the terms of the GNU Lesser General Public License as
9    * published by the Free Software Foundation; either version 2.1 of
10   * the License, or (at your option) any later version.
11   *
12   * This software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this software; if not, write to the Free
19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21   */
22  package org.modeshape.repository;
23  
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.UUID;
31  import java.util.concurrent.ExecutorService;
32  import java.util.concurrent.Executors;
33  import java.util.concurrent.ThreadFactory;
34  import java.util.concurrent.TimeUnit;
35  import net.jcip.annotations.Immutable;
36  import org.modeshape.common.collection.Problem;
37  import org.modeshape.common.collection.Problems;
38  import org.modeshape.common.collection.SimpleProblems;
39  import org.modeshape.common.util.CheckArg;
40  import org.modeshape.common.util.Logger;
41  import org.modeshape.common.util.NamedThreadFactory;
42  import org.modeshape.graph.ExecutionContext;
43  import org.modeshape.graph.Graph;
44  import org.modeshape.graph.JcrLexicon;
45  import org.modeshape.graph.JcrMixLexicon;
46  import org.modeshape.graph.JcrNtLexicon;
47  import org.modeshape.graph.Location;
48  import org.modeshape.graph.Node;
49  import org.modeshape.graph.Subgraph;
50  import org.modeshape.graph.connector.RepositoryConnectionFactory;
51  import org.modeshape.graph.connector.RepositoryContext;
52  import org.modeshape.graph.connector.RepositorySource;
53  import org.modeshape.graph.connector.RepositorySourceException;
54  import org.modeshape.graph.mimetype.ExtensionBasedMimeTypeDetector;
55  import org.modeshape.graph.mimetype.MimeTypeDetector;
56  import org.modeshape.graph.mimetype.MimeTypeDetectorConfig;
57  import org.modeshape.graph.mimetype.MimeTypeDetectors;
58  import org.modeshape.graph.observe.ObservationBus;
59  import org.modeshape.graph.property.Name;
60  import org.modeshape.graph.property.Path;
61  import org.modeshape.graph.property.PathExpression;
62  import org.modeshape.graph.property.PathNotFoundException;
63  import org.modeshape.graph.property.Property;
64  import org.modeshape.repository.cluster.ClusteringConfig;
65  import org.modeshape.repository.cluster.ClusteringService;
66  import org.modeshape.repository.sequencer.SequencerConfig;
67  import org.modeshape.repository.sequencer.SequencingService;
68  
69  /**
70   * A single instance of the ModeShape services, which is obtained after setting up the {@link ModeShapeConfiguration#build()
71   * configuration}.
72   * 
73   * @see ModeShapeConfiguration
74   */
75  @Immutable
76  public class ModeShapeEngine {
77  
78      public static final String CONFIGURATION_REPOSITORY_NAME = "dna:configuration";
79      protected static final Logger LOGGER = Logger.getLogger(ModeShapeEngine.class);
80  
81      protected final ModeShapeConfiguration.ConfigurationDefinition configuration;
82      private final ConfigurationScanner scanner;
83      private final Problems problems;
84      protected final ExecutionContext context;
85  
86      private final RepositoryService repositoryService;
87      private final SequencingService sequencingService;
88      private final ExecutorService executorService;
89      private final ClusteringService clusteringService;
90      private final MimeTypeDetectors detectors;
91      private final String engineId = UUID.randomUUID().toString();
92  
93      protected ModeShapeEngine( ExecutionContext context,
94                                 ModeShapeConfiguration.ConfigurationDefinition configuration ) {
95          this.problems = new SimpleProblems();
96  
97          // Use the configuration's context ...
98          this.detectors = new MimeTypeDetectors();
99          this.context = context.with(detectors).with(engineId);
100 
101         // And set up the scanner ...
102         this.configuration = configuration;
103         this.scanner = new ConfigurationScanner(this.problems, this.context, this.configuration);
104 
105         // Add the mime type detectors in the configuration ...
106         for (MimeTypeDetectorConfig config : scanner.getMimeTypeDetectors()) {
107             detectors.addDetector(config);
108         }
109         // Add an extension-based detector by default ...
110         detectors.addDetector(new MimeTypeDetectorConfig("ExtensionDetector", "Extension-based MIME type detector",
111                                                          ExtensionBasedMimeTypeDetector.class));
112 
113         // Create the clustering service ...
114         ClusteringConfig clusterConfig = scanner.getClusteringConfiguration();
115         clusteringService = new ClusteringService();
116         clusteringService.setExecutionContext(context);
117         clusteringService.setClusteringConfig(clusterConfig);
118 
119         // Create the RepositoryService, pointing it to the configuration repository ...
120         Path pathToConfigurationRoot = this.configuration.getPath();
121         String configWorkspaceName = this.configuration.getWorkspace();
122         RepositorySource configSource = this.configuration.getRepositorySource();
123         repositoryService = new RepositoryService(configSource, configWorkspaceName, pathToConfigurationRoot, context,
124                                                   clusteringService, problems);
125 
126         // Create the executor service (which starts out with 0 threads, so it's okay to do here) ...
127         ThreadFactory threadPoolFactory = new NamedThreadFactory(configuration.getName());
128         executorService = Executors.newCachedThreadPool(threadPoolFactory);
129 
130         // Create the sequencing service ...
131         sequencingService = new SequencingService();
132         sequencingService.setExecutionContext(context);
133         sequencingService.setExecutorService(executorService);
134         sequencingService.setRepositoryLibrary(repositoryService.getRepositoryLibrary());
135         for (SequencerConfig sequencerConfig : scanner.getSequencingConfigurations()) {
136             sequencingService.addSequencer(sequencerConfig);
137         }
138 
139         // The rest of the instantiation/configuration will be done in start()
140     }
141 
142     /**
143      * Get the problems that were encountered when setting up this engine from the configuration.
144      * 
145      * @return the problems, which may be empty but will never be null
146      */
147     public Problems getProblems() {
148         return problems;
149     }
150 
151     /**
152      * Get the context in which this engine is executing.
153      * 
154      * @return the execution context; never null
155      */
156     public final ExecutionContext getExecutionContext() {
157         return context;
158     }
159 
160     /**
161      * Get the {@link RepositorySource} instance used by this engine.
162      * 
163      * @param repositoryName the name of the repository source
164      * @return the source, or null if no source with the given name exists
165      * @throws IllegalStateException if this engine was not {@link #start() started}
166      */
167     public final RepositorySource getRepositorySource( String repositoryName ) {
168         checkRunning();
169         return repositoryService.getRepositoryLibrary().getSource(repositoryName);
170     }
171 
172     /**
173      * Get a factory of connections, backed by the RepositorySor
174      * 
175      * @return the connection factory; never null
176      * @throws IllegalStateException if this engine was not {@link #start() started}
177      */
178     public final RepositoryConnectionFactory getRepositoryConnectionFactory() {
179         checkRunning();
180         return repositoryService.getRepositoryLibrary();
181     }
182 
183     /**
184      * Get the repository service.
185      * 
186      * @return the repository service owned by this engine; never null
187      * @throws IllegalStateException if this engine was not {@link #start() started}
188      */
189     public final RepositoryService getRepositoryService() {
190         checkRunning();
191         return repositoryService;
192     }
193 
194     /**
195      * Get a graph to the underlying source.
196      * 
197      * @param sourceName the name of the source
198      * @return the graph
199      * @throws IllegalArgumentException if the source name is null
200      * @throws RepositorySourceException if a source with the supplied name does not exist
201      * @throws IllegalStateException if this engine was not {@link #start() started}
202      */
203     public final Graph getGraph( String sourceName ) {
204         CheckArg.isNotNull(sourceName, "sourceName");
205         return getGraph(getExecutionContext(), sourceName);
206     }
207 
208     /**
209      * Get a graph to the underlying source, using the supplied context. Note that the supplied context should be a derivative of
210      * the engine's {@link #getExecutionContext() context}.
211      * 
212      * @param context the context of execution for this graph; may not be null
213      * @param sourceName the name of the source
214      * @return the graph
215      * @throws IllegalArgumentException if the context or source name are null
216      * @throws RepositorySourceException if a source with the supplied name does not exist
217      * @throws IllegalStateException if this engine was not {@link #start() started}
218      */
219     public final Graph getGraph( ExecutionContext context,
220                                  String sourceName ) {
221         CheckArg.isNotNull(context, "context");
222         CheckArg.isNotNull(sourceName, "sourceName");
223         checkRunning();
224         Graph graph = Graph.create(sourceName, getRepositoryService().getRepositoryLibrary(), context);
225         if (configuration.getRepositorySource().getName().equals(sourceName) && configuration.getWorkspace() != null) {
226             // set the workspace ...
227             graph.useWorkspace(configuration.getWorkspace());
228         }
229         return graph;
230     }
231 
232     /**
233      * Get the sequencing service.
234      * 
235      * @return the sequencing service owned by this engine; never null
236      * @throws IllegalStateException if this engine was not {@link #start() started}
237      */
238     public final SequencingService getSequencingService() {
239         checkRunning();
240         return sequencingService;
241     }
242 
243     /**
244      * Return the component that is able to detect MIME types given the name of a stream and a stream.
245      * 
246      * @return the MIME type detector used by this engine; never null
247      * @throws IllegalStateException if this engine was not {@link #start() started}
248      */
249     protected final MimeTypeDetector getMimeTypeDetector() {
250         checkRunning();
251         return detectors;
252     }
253 
254     protected final boolean checkRunning() {
255         if (repositoryService.getAdministrator().isStarted() && sequencingService.getAdministrator().isStarted()) {
256             return true;
257         }
258         throw new IllegalStateException(RepositoryI18n.engineIsNotRunning.text());
259     }
260 
261     /*
262      * Lifecycle methods
263      */
264     /**
265      * Start this engine to make it available for use.
266      * 
267      * @throws IllegalStateException if this method is called when already shut down.
268      * @see #shutdown()
269      */
270     public void start() {
271         if (getProblems().hasErrors()) {
272             // First log the messages ...
273             LOGGER.error(RepositoryI18n.errorsPreventStarting);
274             for (Problem problem : getProblems()) {
275                 LOGGER.error(problem.getMessage(), problem.getParameters());
276             }
277             // Then throw an exception ...
278             throw new IllegalStateException(RepositoryI18n.errorsPreventStarting.text());
279         }
280 
281         // Create the RepositoryContext that the configuration repository source should use ...
282         RepositoryContext configContext = new SimpleRepositoryContext(context, clusteringService, null);
283         configuration.getRepositorySource().initialize(configContext);
284 
285         // Start the various services ...
286         clusteringService.getAdministrator().start();
287         repositoryService.getAdministrator().start();
288         sequencingService.getAdministrator().start();
289 
290         // Now register the repository service to be notified of changes to the configuration ...
291         clusteringService.register(repositoryService);
292     }
293 
294     /**
295      * Shutdown this engine to close all connections, terminate any ongoing background operations (such as sequencing), and
296      * reclaim any resources that were acquired by this engine. This method may be called multiple times, but only the first time
297      * has an effect.
298      * 
299      * @see #start()
300      */
301     public void shutdown() {
302         preShutdown();
303         postShutdown();
304     }
305 
306     protected void preShutdown() {
307         // Terminate the executor service, which may be running background jobs that are not yet completed
308         // and which will prevent new jobs being submitted (to the sequencing service) ...
309         executorService.shutdown();
310 
311         // Next, shutdown the sequencing service, which will prevent any additional jobs from going through ...
312         sequencingService.getAdministrator().shutdown();
313 
314         // Shut down the repository source, which closes all connections ...
315         repositoryService.getAdministrator().shutdown();
316     }
317 
318     protected void postShutdown() {
319         // Finally shut down the clustering service ...
320         clusteringService.shutdown();
321     }
322 
323     /**
324      * Blocks until the shutdown has completed, or the timeout occurs, or the current thread is interrupted, whichever happens
325      * first.
326      * 
327      * @param timeout the maximum time to wait for each component in this engine
328      * @param unit the time unit of the timeout argument
329      * @return <tt>true</tt> if this service complete shut down and <tt>false</tt> if the timeout elapsed before it was shut down
330      *         completely
331      * @throws InterruptedException if interrupted while waiting
332      */
333     public boolean awaitTermination( long timeout,
334                                      TimeUnit unit ) throws InterruptedException {
335         if (!sequencingService.getAdministrator().awaitTermination(timeout, unit)) return false;
336         if (!executorService.awaitTermination(timeout, unit)) return false;
337         if (!repositoryService.getAdministrator().awaitTermination(timeout, unit)) return false;
338         return true;
339     }
340 
341     /**
342      * Get a graph to the configuration content.
343      * 
344      * @return a graph to the configuration content
345      */
346     protected Graph getConfigurationGraph() {
347         Graph result = Graph.create(configuration.getRepositorySource(), context);
348         if (configuration.getWorkspace() != null) {
349             result.useWorkspace(configuration.getWorkspace());
350         }
351         return result;
352     }
353 
354     /**
355      * The component responsible for reading the configuration repository and (eventually) for propagating changes in the
356      * configuration repository into the services.
357      */
358     protected class ConfigurationScanner {
359         /**
360          * The name of the {@link ObservationBus} implementation class that will be used when clustering. This class will be
361          * loaded reflectively (so that this library doesn't always require the clustering dependencies).
362          */
363         protected static final String CLUSTERED_OBSERVATION_BUS_CLASSNAME = "org.modeshape.clustering.ClusteredObservationBus";
364 
365         private final Problems problems;
366         private final ExecutionContext context;
367         private final ModeShapeConfiguration.ConfigurationDefinition configurationRepository;
368 
369         protected ConfigurationScanner( Problems problems,
370                                         ExecutionContext context,
371                                         ModeShapeConfiguration.ConfigurationDefinition configurationRepository ) {
372             this.problems = problems;
373             this.context = context;
374             this.configurationRepository = configurationRepository;
375         }
376 
377         public List<MimeTypeDetectorConfig> getMimeTypeDetectors() {
378             List<MimeTypeDetectorConfig> detectors = new ArrayList<MimeTypeDetectorConfig>();
379             Graph graph = Graph.create(configurationRepository.getRepositorySource(), context);
380             Path pathToSequencersNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(),
381                                                                                             ModeShapeLexicon.MIME_TYPE_DETECTORS);
382             try {
383                 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToSequencersNode);
384 
385                 Set<Name> skipProperties = new HashSet<Name>();
386                 skipProperties.add(ModeShapeLexicon.READABLE_NAME);
387                 skipProperties.add(ModeShapeLexicon.DESCRIPTION);
388                 skipProperties.add(ModeShapeLexicon.CLASSNAME);
389                 skipProperties.add(ModeShapeLexicon.CLASSPATH);
390                 skipProperties.add(ModeShapeLexicon.PATH_EXPRESSION);
391                 Set<String> skipNamespaces = new HashSet<String>();
392                 skipNamespaces.add(JcrLexicon.Namespace.URI);
393                 skipNamespaces.add(JcrNtLexicon.Namespace.URI);
394                 skipNamespaces.add(JcrMixLexicon.Namespace.URI);
395 
396                 for (Location detectorLocation : subgraph.getRoot().getChildren()) {
397                     Node node = subgraph.getNode(detectorLocation);
398                     String name = stringValueOf(node, ModeShapeLexicon.READABLE_NAME);
399                     if (name == null) name = stringValueOf(node);
400                     String desc = stringValueOf(node, ModeShapeLexicon.DESCRIPTION);
401                     String classname = stringValueOf(node, ModeShapeLexicon.CLASSNAME);
402                     String[] classpath = stringValuesOf(node, ModeShapeLexicon.CLASSPATH);
403                     Map<String, Object> properties = new HashMap<String, Object>();
404                     for (Property property : node.getProperties()) {
405                         Name propertyName = property.getName();
406                         if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue;
407                         if (skipProperties.contains(propertyName)) continue;
408                         if (property.isSingle()) {
409                             properties.put(propertyName.getLocalName(), property.getFirstValue());
410                         } else {
411                             properties.put(propertyName.getLocalName(), property.getValuesAsArray());
412                         }
413                     }
414                     MimeTypeDetectorConfig config = new MimeTypeDetectorConfig(name, desc, properties, classname, classpath);
415                     detectors.add(config);
416                 }
417             } catch (PathNotFoundException e) {
418                 // no detectors registered ...
419             }
420             return detectors;
421         }
422 
423         public ClusteringConfig getClusteringConfiguration() {
424             Graph graph = Graph.create(configurationRepository.getRepositorySource(), context);
425             Path pathToClusteringNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(),
426                                                                                             ModeShapeLexicon.CLUSTERING);
427             try {
428                 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToClusteringNode);
429 
430                 Set<Name> skipProperties = new HashSet<Name>();
431                 skipProperties.add(ModeShapeLexicon.DESCRIPTION);
432                 skipProperties.add(ModeShapeLexicon.CLASSNAME);
433                 skipProperties.add(ModeShapeLexicon.CLASSPATH);
434                 Set<String> skipNamespaces = new HashSet<String>();
435                 skipNamespaces.add(JcrLexicon.Namespace.URI);
436                 skipNamespaces.add(JcrNtLexicon.Namespace.URI);
437                 skipNamespaces.add(JcrMixLexicon.Namespace.URI);
438 
439                 Node clusterNode = subgraph.getRoot();
440                 // String name = stringValueOf(clusterNode);
441                 String clusterName = stringValueOf(clusterNode, ModeShapeLexicon.CLUSTER_NAME);
442                 String desc = stringValueOf(clusterNode, ModeShapeLexicon.DESCRIPTION);
443                 String classname = stringValueOf(clusterNode, ModeShapeLexicon.CLASSNAME);
444                 String[] classpath = stringValuesOf(clusterNode, ModeShapeLexicon.CLASSPATH);
445                 if (classname == null || classname.trim().length() == 0) {
446                     classname = CLUSTERED_OBSERVATION_BUS_CLASSNAME;
447                 }
448                 if (clusterName == null || clusterName.trim().length() == 0) {
449                     LOGGER.warn(RepositoryI18n.clusteringConfigurationRequiresClusterName);
450                     problems.addWarning(RepositoryI18n.clusteringConfigurationRequiresClusterName);
451                     return null; // Signifies no clustering
452                 }
453 
454                 Map<String, Object> properties = new HashMap<String, Object>();
455                 for (Property property : clusterNode.getProperties()) {
456                     Name propertyName = property.getName();
457                     if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue;
458                     if (skipProperties.contains(propertyName)) continue;
459                     if (property.isSingle()) {
460                         properties.put(propertyName.getLocalName(), property.getFirstValue());
461                     } else {
462                         properties.put(propertyName.getLocalName(), property.getValuesAsArray());
463                     }
464                 }
465                 return new ClusteringConfig(clusterName, desc, properties, classname, classpath);
466             } catch (PathNotFoundException e) {
467                 // no detectors registered ...
468             }
469             return null;
470         }
471 
472         public List<SequencerConfig> getSequencingConfigurations() {
473             List<SequencerConfig> configs = new ArrayList<SequencerConfig>();
474             Graph graph = Graph.create(configurationRepository.getRepositorySource(), context);
475             Path pathToSequencersNode = context.getValueFactories().getPathFactory().create(configurationRepository.getPath(),
476                                                                                             ModeShapeLexicon.SEQUENCERS);
477             try {
478                 Subgraph subgraph = graph.getSubgraphOfDepth(2).at(pathToSequencersNode);
479 
480                 Set<Name> skipProperties = new HashSet<Name>();
481                 skipProperties.add(ModeShapeLexicon.READABLE_NAME);
482                 skipProperties.add(ModeShapeLexicon.DESCRIPTION);
483                 skipProperties.add(ModeShapeLexicon.CLASSNAME);
484                 skipProperties.add(ModeShapeLexicon.CLASSPATH);
485                 skipProperties.add(ModeShapeLexicon.PATH_EXPRESSION);
486                 Set<String> skipNamespaces = new HashSet<String>();
487                 skipNamespaces.add(JcrLexicon.Namespace.URI);
488                 skipNamespaces.add(JcrNtLexicon.Namespace.URI);
489                 skipNamespaces.add(JcrMixLexicon.Namespace.URI);
490 
491                 for (Location sequencerLocation : subgraph.getRoot().getChildren()) {
492                     Node sequencerNode = subgraph.getNode(sequencerLocation);
493                     String name = stringValueOf(sequencerNode, ModeShapeLexicon.READABLE_NAME);
494                     if (name == null) name = stringValueOf(sequencerNode);
495                     String desc = stringValueOf(sequencerNode, ModeShapeLexicon.DESCRIPTION);
496                     String classname = stringValueOf(sequencerNode, ModeShapeLexicon.CLASSNAME);
497                     String[] classpath = stringValuesOf(sequencerNode, ModeShapeLexicon.CLASSPATH);
498                     String[] expressionStrings = stringValuesOf(sequencerNode, ModeShapeLexicon.PATH_EXPRESSION);
499                     List<PathExpression> pathExpressions = new ArrayList<PathExpression>();
500                     if (expressionStrings != null) {
501                         for (String expressionString : expressionStrings) {
502                             try {
503                                 pathExpressions.add(PathExpression.compile(expressionString));
504                             } catch (Throwable t) {
505                                 problems.addError(t,
506                                                   RepositoryI18n.pathExpressionIsInvalidOnSequencer,
507                                                   expressionString,
508                                                   name,
509                                                   t.getLocalizedMessage());
510                             }
511                         }
512                     }
513                     String[] goodExpressionStrings = new String[pathExpressions.size()];
514                     for (int i = 0; i != pathExpressions.size(); ++i) {
515                         PathExpression expression = pathExpressions.get(i);
516                         goodExpressionStrings[i] = expression.getExpression();
517                     }
518                     Map<String, Object> properties = new HashMap<String, Object>();
519                     for (Property property : sequencerNode.getProperties()) {
520                         Name propertyName = property.getName();
521                         if (skipNamespaces.contains(propertyName.getNamespaceUri())) continue;
522                         if (skipProperties.contains(propertyName)) continue;
523                         if (property.isSingle()) {
524                             properties.put(propertyName.getLocalName(), property.getFirstValue());
525                         } else {
526                             properties.put(propertyName.getLocalName(), property.getValuesAsArray());
527                         }
528                     }
529                     SequencerConfig config = new SequencerConfig(name, desc, properties, classname, classpath,
530                                                                  goodExpressionStrings);
531                     configs.add(config);
532                 }
533             } catch (PathNotFoundException e) {
534                 // no detectors registered ...
535             }
536             return configs;
537         }
538 
539         private String stringValueOf( Node node ) {
540             return node.getLocation().getPath().getLastSegment().getString(context.getNamespaceRegistry());
541         }
542 
543         private String stringValueOf( Node node,
544                                       Name propertyName ) {
545             Property property = node.getProperty(propertyName);
546             if (property == null) {
547                 // Check whether the property exists with no namespace ...
548                 property = node.getProperty(context.getValueFactories().getNameFactory().create(propertyName.getLocalName()));
549                 if (property == null) return null;
550             }
551             if (property.isEmpty()) return null;
552             return context.getValueFactories().getStringFactory().create(property.getFirstValue());
553         }
554 
555         private String[] stringValuesOf( Node node,
556                                          Name propertyName ) {
557             Property property = node.getProperty(propertyName);
558             if (property == null) {
559                 // Check whether the property exists with no namespace ...
560                 property = node.getProperty(context.getValueFactories().getNameFactory().create(propertyName.getLocalName()));
561                 if (property == null) return null;
562             }
563             return context.getValueFactories().getStringFactory().create(property.getValuesAsArray());
564         }
565 
566     }
567 }