1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * Unless otherwise indicated, all code in ModeShape is licensed
10 * to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.cnd;
25
26 import static org.modeshape.common.text.TokenStream.ANY_VALUE;
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Set;
36 import net.jcip.annotations.NotThreadSafe;
37 import org.modeshape.common.collection.Problems;
38 import org.modeshape.common.text.ParsingException;
39 import org.modeshape.common.text.Position;
40 import org.modeshape.common.text.TokenStream;
41 import org.modeshape.common.text.TokenStream.Tokenizer;
42 import org.modeshape.common.util.CheckArg;
43 import org.modeshape.common.util.IoUtil;
44 import org.modeshape.graph.ExecutionContext;
45 import org.modeshape.graph.JcrLexicon;
46 import org.modeshape.graph.JcrNtLexicon;
47 import org.modeshape.graph.io.Destination;
48 import org.modeshape.graph.property.Name;
49 import org.modeshape.graph.property.NameFactory;
50 import org.modeshape.graph.property.Path;
51 import org.modeshape.graph.property.PathFactory;
52 import org.modeshape.graph.property.Property;
53 import org.modeshape.graph.property.PropertyFactory;
54 import org.modeshape.graph.property.PropertyType;
55 import org.modeshape.graph.property.ValueFactories;
56 import org.modeshape.graph.property.ValueFormatException;
57
58 /**
59 * A class that imports the node types contained in a JCR Compact Node Definition (CND) file into graph content. The content is
60 * written using the graph structured defined by JCR and the "{@code nt:nodeType}", "{@code nt:propertyDefinition}", and "{@code
61 * nt:childNodeDefinition}" node types.
62 * <p>
63 * Although instances of this class never change their behavior and all processing is done in local contexts, {@link Destination}
64 * is not thread-safe and therefore this component may not be considered thread-safe.
65 * </p>
66 */
67 @NotThreadSafe
68 public class CndImporter {
69
70 protected final List<String> VALID_PROPERTY_TYPES = Collections.unmodifiableList(Arrays.asList(new String[] {"STRING",
71 "BINARY", "LONG", "DOUBLE", "BOOLEAN", "DATE", "NAME", "PATH", "REFERENCE", "WEAKREFERENCE", "DECIMAL", "URI",
72 "UNDEFINED", "*", "?"}));
73
74 protected final List<String> VALID_ON_PARENT_VERSION = Collections.unmodifiableList(Arrays.asList(new String[] {"COPY",
75 "VERSION", "INITIALIZE", "COMPUTE", "IGNORE", "ABORT"}));
76
77 protected final Set<String> VALID_QUERY_OPERATORS = Collections.unmodifiableSet(new HashSet<String>(
78 Arrays.asList(new String[] {
79 "=", "<>", "<", "<=",
80 ">", ">=", "LIKE"})));
81
82 protected final Destination destination;
83 protected final Path outputPath;
84 protected final PropertyFactory propertyFactory;
85 protected final PathFactory pathFactory;
86 protected final NameFactory nameFactory;
87 protected final ValueFactories valueFactories;
88 protected final boolean jcr170;
89
90 /**
91 * Create a new importer that will place the content in the supplied destination under the supplied path.
92 *
93 * @param destination the destination where content is to be written
94 * @param parentPath the path in the destination below which the generated content is to appear
95 * @param compatibleWithPreJcr2 true if this parser should accept the CND format that was used in the reference implementation
96 * prior to JCR 2.0.
97 * @throws IllegalArgumentException if either parameter is null
98 */
99 public CndImporter( Destination destination,
100 Path parentPath,
101 boolean compatibleWithPreJcr2 ) {
102 CheckArg.isNotNull(destination, "destination");
103 CheckArg.isNotNull(parentPath, "parentPath");
104 this.destination = destination;
105 this.outputPath = parentPath;
106 ExecutionContext context = destination.getExecutionContext();
107 this.valueFactories = context.getValueFactories();
108 this.propertyFactory = context.getPropertyFactory();
109 this.pathFactory = valueFactories.getPathFactory();
110 this.nameFactory = valueFactories.getNameFactory();
111 this.jcr170 = compatibleWithPreJcr2;
112 }
113
114 /**
115 * Create a new importer that will place the content in the supplied destination under the supplied path. This parser will
116 * accept the CND format that was used in the reference implementation prior to JCR 2.0.
117 *
118 * @param destination the destination where content is to be written
119 * @param parentPath the path in the destination below which the generated content is to appear
120 * @throws IllegalArgumentException if either parameter is null
121 */
122 public CndImporter( Destination destination,
123 Path parentPath ) {
124 this(destination, parentPath, true);
125 }
126
127 /**
128 * Import the CND content from the supplied stream, placing the content into the importer's destination.
129 *
130 * @param stream the stream containing the CND content
131 * @param problems where any problems encountered during import should be reported
132 * @param resourceName a logical name for the resource name to be used when reporting problems; may be null if there is no
133 * useful name
134 * @throws IOException if there is a problem reading from the supplied stream
135 */
136 public void importFrom( InputStream stream,
137 Problems problems,
138 String resourceName ) throws IOException {
139 importFrom(IoUtil.read(stream), problems, resourceName);
140 }
141
142 /**
143 * Import the CND content from the supplied stream, placing the content into the importer's destination.
144 *
145 * @param file the file containing the CND content
146 * @param problems where any problems encountered during import should be reported
147 * @throws IOException if there is a problem reading from the supplied stream
148 */
149 public void importFrom( File file,
150 Problems problems ) throws IOException {
151 importFrom(IoUtil.read(file), problems, file.getCanonicalPath());
152 }
153
154 /**
155 * Import the CND content from the supplied stream, placing the content into the importer's destination.
156 *
157 * @param content the string containing the CND content
158 * @param problems where any problems encountered during import should be reported
159 * @param resourceName a logical name for the resource name to be used when reporting problems; may be null if there is no
160 * useful name
161 */
162 public void importFrom( String content,
163 Problems problems,
164 String resourceName ) {
165 try {
166 parse(content);
167 destination.submit();
168 } catch (RuntimeException e) {
169 problems.addError(e, CndI18n.errorImportingCndContent, (Object)resourceName, e.getMessage());
170 }
171 }
172
173 /**
174 * Parse the CND content.
175 *
176 * @param content the content
177 * @throws ParsingException if there is a problem parsing the content
178 */
179 protected void parse( String content ) {
180 Tokenizer tokenizer = new CndTokenizer(false, false);
181 TokenStream tokens = new TokenStream(content, tokenizer, false);
182 tokens.start();
183 while (tokens.hasNext()) {
184 // Keep reading while we can recognize one of the two types of statements ...
185 if (tokens.matches("<", ANY_VALUE, "=", ANY_VALUE, ">")) {
186 parseNamespaceMapping(tokens);
187 } else if (tokens.matches("[", ANY_VALUE, "]")) {
188 parseNodeTypeDefinition(tokens, outputPath);
189 } else {
190 Position position = tokens.previousPosition();
191 throw new ParsingException(position, CndI18n.expectedNamespaceOrNodeDefinition.text(tokens.consume(),
192 position.getLine(),
193 position.getColumn()));
194 }
195 }
196 }
197
198 /**
199 * Parse the namespace mapping statement that is next on the token stream.
200 *
201 * @param tokens the tokens containing the namespace statement; never null
202 * @throws ParsingException if there is a problem parsing the content
203 */
204 protected void parseNamespaceMapping( TokenStream tokens ) {
205 tokens.consume('<');
206 String prefix = removeQuotes(tokens.consume());
207 tokens.consume('=');
208 String uri = removeQuotes(tokens.consume());
209 tokens.consume('>');
210 // Register the namespace ...
211 destination.getExecutionContext().getNamespaceRegistry().register(prefix, uri);
212 }
213
214 /**
215 * Parse the node type definition that is next on the token stream.
216 *
217 * @param tokens the tokens containing the node type definition; never null
218 * @param path the path in the destination under which the node type definition should be stored; never null
219 * @throws ParsingException if there is a problem parsing the content
220 */
221 protected void parseNodeTypeDefinition( TokenStream tokens,
222 Path path ) {
223 // Parse the name, and create the path and a property for the name ...
224 Name name = parseNodeTypeName(tokens);
225 Path nodeTypePath = pathFactory.create(path, name);
226 List<Property> properties = new ArrayList<Property>();
227 properties.add(propertyFactory.create(JcrLexicon.NODE_TYPE_NAME, name));
228
229 // Read the (optional) supertypes ...
230 List<Name> supertypes = parseSupertypes(tokens);
231 properties.add(propertyFactory.create(JcrLexicon.SUPERTYPES, supertypes)); // even if empty
232
233 // Read the node type options ...
234 parseNodeTypeOptions(tokens, properties);
235 destination.create(nodeTypePath, properties);
236
237 // Parse property and child node definitions ...
238 parsePropertyOrChildNodeDefinitions(tokens, nodeTypePath);
239 }
240
241 /**
242 * Parse a node type name that appears next on the token stream.
243 *
244 * @param tokens the tokens containing the node type name; never null
245 * @return the node type name
246 * @throws ParsingException if there is a problem parsing the content
247 */
248 protected Name parseNodeTypeName( TokenStream tokens ) {
249 tokens.consume('[');
250 Name name = parseName(tokens);
251 tokens.consume(']');
252 return name;
253 }
254
255 /**
256 * Parse an optional list of supertypes if they appear next on the token stream.
257 *
258 * @param tokens the tokens containing the supertype names; never null
259 * @return the list of supertype names; never null, but possibly empty
260 * @throws ParsingException if there is a problem parsing the content
261 */
262 protected List<Name> parseSupertypes( TokenStream tokens ) {
263 if (tokens.canConsume('>')) {
264 // There is at least one supertype ...
265 return parseNameList(tokens);
266 }
267 return Collections.emptyList();
268 }
269
270 /**
271 * Parse a list of strings, separated by commas. Any quotes surrounding the strings are removed.
272 *
273 * @param tokens the tokens containing the comma-separated strings; never null
274 * @return the list of string values; never null, but possibly empty
275 * @throws ParsingException if there is a problem parsing the content
276 */
277 protected List<String> parseStringList( TokenStream tokens ) {
278 List<String> strings = new ArrayList<String>();
279 if (tokens.canConsume('?')) {
280 // This list is variant ...
281 strings.add("?");
282 } else {
283 // Read names until we see a ','
284 do {
285 strings.add(removeQuotes(tokens.consume()));
286 } while (tokens.canConsume(','));
287 }
288 return strings;
289 }
290
291 /**
292 * Parse a list of names, separated by commas. Any quotes surrounding the names are removed.
293 *
294 * @param tokens the tokens containing the comma-separated strings; never null
295 * @return the list of string values; never null, but possibly empty
296 * @throws ParsingException if there is a problem parsing the content
297 */
298 protected List<Name> parseNameList( TokenStream tokens ) {
299 List<Name> names = new ArrayList<Name>();
300 if (!tokens.canConsume('?')) {
301 // Read names until we see a ','
302 do {
303 names.add(parseName(tokens));
304 } while (tokens.canConsume(','));
305 }
306 return names;
307 }
308
309 /**
310 * Parse the options for the node types, including whether the node type is orderable, a mixin, abstract, whether it supports
311 * querying, and which property/child node (if any) is the primary item for the node type.
312 *
313 * @param tokens the tokens containing the comma-separated strings; never null
314 * @param properties the list into which the properties that represent the options should be placed
315 * @throws ParsingException if there is a problem parsing the content
316 */
317 protected void parseNodeTypeOptions( TokenStream tokens,
318 List<Property> properties ) {
319 // Set up the defaults ...
320 boolean isOrderable = false;
321 boolean isMixin = false;
322 boolean isAbstract = false;
323 boolean isQueryable = true;
324 Name primaryItem = null;
325 String onParentVersion = "COPY";
326 while (true) {
327 // Keep reading while we see a valid option ...
328 if (tokens.canConsumeAnyOf("ORDERABLE", "ORD", "O")) {
329 tokens.canConsume('?');
330 isOrderable = true;
331 } else if (tokens.canConsumeAnyOf("MIXIN", "MIX", "M")) {
332 tokens.canConsume('?');
333 isMixin = true;
334 } else if (tokens.canConsumeAnyOf("ABSTRACT", "ABS", "A")) {
335 tokens.canConsume('?');
336 isAbstract = true;
337 } else if (tokens.canConsumeAnyOf("NOQUERY", "NOQ")) {
338 tokens.canConsume('?');
339 isQueryable = false;
340 } else if (tokens.canConsumeAnyOf("PRIMARYITEM", "!")) {
341 primaryItem = parseName(tokens);
342 tokens.canConsume('?');
343 } else if (tokens.matchesAnyOf(VALID_ON_PARENT_VERSION)) {
344 onParentVersion = tokens.consume();
345 tokens.canConsume('?');
346 } else if (tokens.matches("OPV")) {
347 // variant on-parent-version
348 onParentVersion = tokens.consume();
349 tokens.canConsume('?');
350 } else {
351 // No more valid options on the stream, so stop ...
352 break;
353 }
354 }
355 properties.add(propertyFactory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, isOrderable));
356 properties.add(propertyFactory.create(JcrLexicon.IS_MIXIN, isMixin));
357 properties.add(propertyFactory.create(JcrLexicon.IS_ABSTRACT, isAbstract));
358 properties.add(propertyFactory.create(JcrLexicon.IS_QUERYABLE, isQueryable));
359 properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion.toUpperCase()));
360 if (primaryItem != null) {
361 properties.add(propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, primaryItem));
362 }
363 }
364
365 /**
366 * Parse a node type's property or child node definitions that appear next on the token stream.
367 *
368 * @param tokens the tokens containing the definitions; never null
369 * @param nodeTypePath the path in the destination where the node type has been created, and under which the property and
370 * child node type definitions should be placed
371 * @throws ParsingException if there is a problem parsing the content
372 */
373 protected void parsePropertyOrChildNodeDefinitions( TokenStream tokens,
374 Path nodeTypePath ) {
375 while (true) {
376 // Keep reading while we see a property definition or child node definition ...
377 if (tokens.matches('-')) {
378 parsePropertyDefinition(tokens, nodeTypePath);
379 } else if (tokens.matches('+')) {
380 parseChildNodeDefinition(tokens, nodeTypePath);
381 } else {
382 // The next token does not signal either one of these, so stop ...
383 break;
384 }
385 }
386 }
387
388 /**
389 * Parse a node type's property definition from the next tokens on the stream.
390 *
391 * @param tokens the tokens containing the definition; never null
392 * @param nodeTypePath the path in the destination where the node type has been created, and under which the property and
393 * child node type definitions should be placed
394 * @throws ParsingException if there is a problem parsing the content
395 */
396 protected void parsePropertyDefinition( TokenStream tokens,
397 Path nodeTypePath ) {
398 tokens.consume('-');
399 Name name = parseName(tokens);
400 Path path = pathFactory.create(nodeTypePath, JcrLexicon.PROPERTY_DEFINITION);
401 List<Property> properties = new ArrayList<Property>();
402 properties.add(propertyFactory.create(JcrLexicon.NAME, name));
403
404 // Parse the (optional) required type ...
405 parsePropertyType(tokens, properties, PropertyType.STRING.getName());
406
407 // Parse the default values ...
408 parseDefaultValues(tokens, properties);
409
410 // Parse the property attributes ...
411 parsePropertyAttributes(tokens, properties, name, path);
412
413 // Parse the property constraints ...
414 parseValueConstraints(tokens, properties);
415
416 // Create the node in the destination ...
417 destination.create(path, properties);
418 }
419
420 /**
421 * Parse the property type, if a valid one appears next on the token stream.
422 *
423 * @param tokens the tokens containing the definition; never null
424 * @param properties the list into which the property that represents the property type should be placed
425 * @param defaultPropertyType the default property type if none is actually found
426 * @throws ParsingException if there is a problem parsing the content
427 */
428 protected void parsePropertyType( TokenStream tokens,
429 List<Property> properties,
430 String defaultPropertyType ) {
431 if (tokens.canConsume('(')) {
432 // Parse the (optional) property type ...
433 String propertyType = defaultPropertyType;
434 if (tokens.matchesAnyOf(VALID_PROPERTY_TYPES)) {
435 propertyType = tokens.consume();
436 if ("*".equals(propertyType)) propertyType = "UNDEFINED";
437 }
438 tokens.consume(')');
439 properties.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE, propertyType.toUpperCase()));
440 }
441 }
442
443 /**
444 * Parse the property definition's default value, if they appear next on the token stream.
445 *
446 * @param tokens the tokens containing the definition; never null
447 * @param properties the list into which the property that represents the default values should be placed
448 * @throws ParsingException if there is a problem parsing the content
449 */
450 protected void parseDefaultValues( TokenStream tokens,
451 List<Property> properties ) {
452 if (tokens.canConsume('=')) {
453 List<String> defaultValues = parseStringList(tokens);
454 if (!defaultValues.isEmpty()) {
455 properties.add(propertyFactory.create(JcrLexicon.DEFAULT_VALUES, defaultValues));
456 }
457 }
458 }
459
460 /**
461 * Parse the property definition's value constraints, if they appear next on the token stream.
462 *
463 * @param tokens the tokens containing the definition; never null
464 * @param properties the list into which the property that represents the value constraints should be placed
465 * @throws ParsingException if there is a problem parsing the content
466 */
467 protected void parseValueConstraints( TokenStream tokens,
468 List<Property> properties ) {
469 if (tokens.canConsume('<')) {
470 List<String> defaultValues = parseStringList(tokens);
471 if (!defaultValues.isEmpty()) {
472 properties.add(propertyFactory.create(JcrLexicon.VALUE_CONSTRAINTS, defaultValues));
473 }
474 }
475 }
476
477 /**
478 * Parse the property definition's attributes, if they appear next on the token stream.
479 *
480 * @param tokens the tokens containing the attributes; never null
481 * @param properties the list into which the properties that represents the attributes should be placed
482 * @param propDefnName the name of the property definition; never null
483 * @param propDefnPath the path in the destination to the property definition node; never null
484 * @throws ParsingException if there is a problem parsing the content
485 */
486 protected void parsePropertyAttributes( TokenStream tokens,
487 List<Property> properties,
488 Name propDefnName,
489 Path propDefnPath ) {
490 boolean autoCreated = false;
491 boolean mandatory = false;
492 boolean isProtected = false;
493 boolean multiple = false;
494 boolean isFullTextSearchable = true;
495 boolean isQueryOrderable = true;
496 String onParentVersion = "COPY";
497 while (true) {
498 if (tokens.canConsumeAnyOf("AUTOCREATED", "AUT", "A")) {
499 tokens.canConsume('?');
500 autoCreated = true;
501 } else if (tokens.canConsumeAnyOf("MANDATORY", "MAN", "M")) {
502 tokens.canConsume('?');
503 mandatory = true;
504 } else if (tokens.canConsumeAnyOf("PROTECTED", "PRO", "P")) {
505 tokens.canConsume('?');
506 isProtected = true;
507 } else if (tokens.canConsumeAnyOf("MULTIPLE", "MUL", "*")) {
508 tokens.canConsume('?');
509 multiple = true;
510 } else if (tokens.matchesAnyOf(VALID_ON_PARENT_VERSION)) {
511 onParentVersion = tokens.consume();
512 tokens.canConsume('?');
513 } else if (tokens.matches("OPV")) {
514 // variant on-parent-version
515 onParentVersion = tokens.consume();
516 tokens.canConsume('?');
517 } else if (tokens.canConsumeAnyOf("NOFULLTEXT", "NOF")) {
518 tokens.canConsume('?');
519 isFullTextSearchable = false;
520 } else if (tokens.canConsumeAnyOf("NOQUERYORDER", "NQORD")) {
521 tokens.canConsume('?');
522 isQueryOrderable = false;
523 } else if (tokens.canConsumeAnyOf("QUERYOPS", "QOP")) {
524 parseQueryOperators(tokens, properties);
525 } else if (jcr170 && tokens.canConsumeAnyOf("PRIMARY", "PRI", "!")) {
526 // Then this child node is considered the primary item ...
527 Property primaryItem = propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, propDefnName);
528 destination.setProperties(propDefnPath.getParent(), primaryItem);
529 } else {
530 break;
531 }
532 }
533 properties.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, autoCreated));
534 properties.add(propertyFactory.create(JcrLexicon.MANDATORY, mandatory));
535 properties.add(propertyFactory.create(JcrLexicon.PROTECTED, isProtected));
536 properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion.toUpperCase()));
537 properties.add(propertyFactory.create(JcrLexicon.MULTIPLE, multiple));
538 properties.add(propertyFactory.create(JcrLexicon.IS_FULL_TEXT_SEARCHABLE, isFullTextSearchable));
539 properties.add(propertyFactory.create(JcrLexicon.IS_QUERY_ORDERABLE, isQueryOrderable));
540 }
541
542 /**
543 * Parse the property definition's query operators, if they appear next on the token stream.
544 *
545 * @param tokens the tokens containing the definition; never null
546 * @param properties the list into which the property that represents the value constraints should be placed
547 * @throws ParsingException if there is a problem parsing the content
548 */
549 protected void parseQueryOperators( TokenStream tokens,
550 List<Property> properties ) {
551 if (tokens.canConsume('?')) {
552 return;
553 }
554 // The query operators are expected to be enclosed in a single quote, so therefore will be a single token ...
555 List<String> operators = new ArrayList<String>();
556 String operatorList = removeQuotes(tokens.consume());
557 // Now split this string on ',' ...
558 for (String operatorValue : operatorList.split(",")) {
559 String operator = operatorValue.trim();
560 if (!VALID_QUERY_OPERATORS.contains(operator)) {
561 throw new ParsingException(tokens.previousPosition(), CndI18n.expectedValidQueryOperator.text(operator));
562 }
563 operators.add(operator);
564 }
565 if (operators.isEmpty()) {
566 operators.addAll(VALID_QUERY_OPERATORS);
567 }
568 properties.add(propertyFactory.create(JcrLexicon.QUERY_OPERATORS, operators));
569 }
570
571 /**
572 * Parse a node type's child node definition from the next tokens on the stream.
573 *
574 * @param tokens the tokens containing the definition; never null
575 * @param nodeTypePath the path in the destination where the node type has been created, and under which the child node type
576 * definitions should be placed
577 * @throws ParsingException if there is a problem parsing the content
578 */
579 protected void parseChildNodeDefinition( TokenStream tokens,
580 Path nodeTypePath ) {
581 tokens.consume('+');
582 Name name = parseName(tokens);
583 Path path = pathFactory.create(nodeTypePath, JcrLexicon.CHILD_NODE_DEFINITION);
584 List<Property> properties = new ArrayList<Property>();
585 properties.add(propertyFactory.create(JcrLexicon.NAME, name));
586
587 parseRequiredPrimaryTypes(tokens, properties);
588 parseDefaultType(tokens, properties);
589 parseNodeAttributes(tokens, properties, name, path);
590
591 // Create the node in the destination ...
592 destination.create(path, properties);
593 }
594
595 /**
596 * Parse the child node definition's list of required primary types, if they appear next on the token stream.
597 *
598 * @param tokens the tokens containing the definition; never null
599 * @param properties the list into which the property that represents the required types should be placed
600 * @throws ParsingException if there is a problem parsing the content
601 */
602 protected void parseRequiredPrimaryTypes( TokenStream tokens,
603 List<Property> properties ) {
604 if (tokens.canConsume('(')) {
605 List<Name> requiredTypes = parseNameList(tokens);
606 if (requiredTypes.isEmpty()) {
607 requiredTypes.add(JcrNtLexicon.BASE);
608 }
609 properties.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, requiredTypes));
610 tokens.consume(')');
611 }
612 }
613
614 /**
615 * Parse the child node definition's default type, if they appear next on the token stream.
616 *
617 * @param tokens the tokens containing the definition; never null
618 * @param properties the list into which the property that represents the default primary type should be placed
619 * @throws ParsingException if there is a problem parsing the content
620 */
621 protected void parseDefaultType( TokenStream tokens,
622 List<Property> properties ) {
623 if (tokens.canConsume('=')) {
624 if (!tokens.canConsume('?')) {
625 Name defaultType = parseName(tokens);
626 properties.add(propertyFactory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, defaultType));
627 }
628 }
629 }
630
631 /**
632 * Parse the child node definition's attributes, if they appear next on the token stream.
633 *
634 * @param tokens the tokens containing the attributes; never null
635 * @param properties the list into which the properties that represents the attributes should be placed
636 * @param childNodeDefnName the name of the child node definition; never null
637 * @param childNodeDefnPath the path in the destination to the child node definition node; never null
638 * @throws ParsingException if there is a problem parsing the content
639 */
640 protected void parseNodeAttributes( TokenStream tokens,
641 List<Property> properties,
642 Name childNodeDefnName,
643 Path childNodeDefnPath ) {
644 boolean autoCreated = false;
645 boolean mandatory = false;
646 boolean isProtected = false;
647 boolean sns = false;
648 String onParentVersion = "COPY";
649 while (true) {
650 if (tokens.canConsumeAnyOf("AUTOCREATED", "AUT", "A")) {
651 tokens.canConsume('?');
652 autoCreated = true;
653 } else if (tokens.canConsumeAnyOf("MANDATORY", "MAN", "M")) {
654 tokens.canConsume('?');
655 mandatory = true;
656 } else if (tokens.canConsumeAnyOf("PROTECTED", "PRO", "P")) {
657 tokens.canConsume('?');
658 isProtected = true;
659 } else if (tokens.canConsumeAnyOf("SNS", "*")) { // standard JCR 2.0 keywords for SNS ...
660 tokens.canConsume('?');
661 sns = true;
662 } else if (jcr170 && tokens.canConsumeAnyOf("MULTIPLE", "MUL", "*")) { // from pre-JCR 2.0 ref impl
663 tokens.canConsume('?');
664 sns = true;
665 } else if (tokens.matchesAnyOf(VALID_ON_PARENT_VERSION)) {
666 onParentVersion = tokens.consume();
667 tokens.canConsume('?');
668 } else if (tokens.matches("OPV")) {
669 // variant on-parent-version
670 onParentVersion = tokens.consume();
671 tokens.canConsume('?');
672 } else if (jcr170 && tokens.canConsumeAnyOf("PRIMARY", "PRI", "!")) {
673 // Then this child node is considered the primary item ...
674 Property primaryItem = propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, childNodeDefnName);
675 destination.setProperties(childNodeDefnPath.getParent(), primaryItem);
676 } else {
677 break;
678 }
679 }
680 properties.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, autoCreated));
681 properties.add(propertyFactory.create(JcrLexicon.MANDATORY, mandatory));
682 properties.add(propertyFactory.create(JcrLexicon.PROTECTED, isProtected));
683 properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion.toUpperCase()));
684 properties.add(propertyFactory.create(JcrLexicon.SAME_NAME_SIBLINGS, sns));
685 }
686
687 /**
688 * Parse the name that is expected to be next on the token stream.
689 *
690 * @param tokens the tokens containing the name; never null
691 * @return the name; never null
692 * @throws ParsingException if there is a problem parsing the content
693 */
694 protected Name parseName( TokenStream tokens ) {
695 String value = tokens.consume();
696 try {
697 return nameFactory.create(removeQuotes(value));
698 } catch (ValueFormatException e) {
699 throw new ParsingException(tokens.previousPosition(), CndI18n.expectedValidNameLiteral.text(value));
700 }
701 }
702
703 protected final String removeQuotes( String text ) {
704 // Remove leading and trailing quotes, if there are any ...
705 return text.replaceFirst("^['\"]+", "").replaceAll("['\"]+$", "");
706 }
707 }