1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
60
61
62
63
64
65
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
92
93
94
95
96
97
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
116
117
118
119
120
121
122 public CndImporter( Destination destination,
123 Path parentPath ) {
124 this(destination, parentPath, true);
125 }
126
127
128
129
130
131
132
133
134
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
144
145
146
147
148
149 public void importFrom( File file,
150 Problems problems ) throws IOException {
151 importFrom(IoUtil.read(file), problems, file.getCanonicalPath());
152 }
153
154
155
156
157
158
159
160
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
175
176
177
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
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
200
201
202
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
211 destination.getExecutionContext().getNamespaceRegistry().register(prefix, uri);
212 }
213
214
215
216
217
218
219
220
221 protected void parseNodeTypeDefinition( TokenStream tokens,
222 Path path ) {
223
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 properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.NODE_TYPE));
229
230
231 List<Name> supertypes = parseSupertypes(tokens);
232 properties.add(propertyFactory.create(JcrLexicon.SUPERTYPES, supertypes));
233
234
235 parseNodeTypeOptions(tokens, properties);
236 destination.create(nodeTypePath, properties);
237
238
239 parsePropertyOrChildNodeDefinitions(tokens, nodeTypePath);
240 }
241
242
243
244
245
246
247
248
249 protected Name parseNodeTypeName( TokenStream tokens ) {
250 tokens.consume('[');
251 Name name = parseName(tokens);
252 tokens.consume(']');
253 return name;
254 }
255
256
257
258
259
260
261
262
263 protected List<Name> parseSupertypes( TokenStream tokens ) {
264 if (tokens.canConsume('>')) {
265
266 return parseNameList(tokens);
267 }
268 return Collections.emptyList();
269 }
270
271
272
273
274
275
276
277
278 protected List<String> parseStringList( TokenStream tokens ) {
279 List<String> strings = new ArrayList<String>();
280 if (tokens.canConsume('?')) {
281
282 strings.add("?");
283 } else {
284
285 do {
286 strings.add(removeQuotes(tokens.consume()));
287 } while (tokens.canConsume(','));
288 }
289 return strings;
290 }
291
292
293
294
295
296
297
298
299 protected List<Name> parseNameList( TokenStream tokens ) {
300 List<Name> names = new ArrayList<Name>();
301 if (!tokens.canConsume('?')) {
302
303 do {
304 names.add(parseName(tokens));
305 } while (tokens.canConsume(','));
306 }
307 return names;
308 }
309
310
311
312
313
314
315
316
317
318 protected void parseNodeTypeOptions( TokenStream tokens,
319 List<Property> properties ) {
320
321 boolean isOrderable = false;
322 boolean isMixin = false;
323 boolean isAbstract = false;
324 boolean isQueryable = true;
325 Name primaryItem = null;
326 String onParentVersion = "COPY";
327 while (true) {
328
329 if (tokens.canConsumeAnyOf("ORDERABLE", "ORD", "O")) {
330 tokens.canConsume('?');
331 isOrderable = true;
332 } else if (tokens.canConsumeAnyOf("MIXIN", "MIX", "M")) {
333 tokens.canConsume('?');
334 isMixin = true;
335 } else if (tokens.canConsumeAnyOf("ABSTRACT", "ABS", "A")) {
336 tokens.canConsume('?');
337 isAbstract = true;
338 } else if (tokens.canConsumeAnyOf("NOQUERY", "NOQ")) {
339 tokens.canConsume('?');
340 isQueryable = false;
341 } else if (tokens.canConsumeAnyOf("QUERY", "Q")) {
342 tokens.canConsume('?');
343 isQueryable = true;
344 } else if (tokens.canConsumeAnyOf("PRIMARYITEM", "!")) {
345 primaryItem = parseName(tokens);
346 tokens.canConsume('?');
347 } else if (tokens.matchesAnyOf(VALID_ON_PARENT_VERSION)) {
348 onParentVersion = tokens.consume();
349 tokens.canConsume('?');
350 } else if (tokens.matches("OPV")) {
351
352 onParentVersion = tokens.consume();
353 tokens.canConsume('?');
354 } else {
355
356 break;
357 }
358 }
359 properties.add(propertyFactory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, isOrderable));
360 properties.add(propertyFactory.create(JcrLexicon.IS_MIXIN, isMixin));
361 properties.add(propertyFactory.create(JcrLexicon.IS_ABSTRACT, isAbstract));
362 properties.add(propertyFactory.create(JcrLexicon.IS_QUERYABLE, isQueryable));
363 properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion.toUpperCase()));
364 if (primaryItem != null) {
365 properties.add(propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, primaryItem));
366 }
367 }
368
369
370
371
372
373
374
375
376
377 protected void parsePropertyOrChildNodeDefinitions( TokenStream tokens,
378 Path nodeTypePath ) {
379 while (true) {
380
381 if (tokens.matches('-')) {
382 parsePropertyDefinition(tokens, nodeTypePath);
383 } else if (tokens.matches('+')) {
384 parseChildNodeDefinition(tokens, nodeTypePath);
385 } else {
386
387 break;
388 }
389 }
390 }
391
392
393
394
395
396
397
398
399
400 protected void parsePropertyDefinition( TokenStream tokens,
401 Path nodeTypePath ) {
402 tokens.consume('-');
403 Name name = parseName(tokens);
404 Path path = pathFactory.create(nodeTypePath, JcrLexicon.PROPERTY_DEFINITION);
405 List<Property> properties = new ArrayList<Property>();
406 properties.add(propertyFactory.create(JcrLexicon.NAME, name));
407 properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.PROPERTY_DEFINITION));
408
409
410 parsePropertyType(tokens, properties, PropertyType.STRING.getName());
411
412
413 parseDefaultValues(tokens, properties);
414
415
416 parsePropertyAttributes(tokens, properties, name, path);
417
418
419 parseValueConstraints(tokens, properties);
420
421
422 destination.create(path, properties);
423 }
424
425
426
427
428
429
430
431
432
433 protected void parsePropertyType( TokenStream tokens,
434 List<Property> properties,
435 String defaultPropertyType ) {
436 if (tokens.canConsume('(')) {
437
438 String propertyType = defaultPropertyType;
439 if (tokens.matchesAnyOf(VALID_PROPERTY_TYPES)) {
440 propertyType = tokens.consume();
441 if ("*".equals(propertyType)) propertyType = "UNDEFINED";
442 }
443 tokens.consume(')');
444 properties.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE, propertyType.toUpperCase()));
445 }
446 }
447
448
449
450
451
452
453
454
455 protected void parseDefaultValues( TokenStream tokens,
456 List<Property> properties ) {
457 if (tokens.canConsume('=')) {
458 List<String> defaultValues = parseStringList(tokens);
459 if (!defaultValues.isEmpty()) {
460 properties.add(propertyFactory.create(JcrLexicon.DEFAULT_VALUES, defaultValues));
461 }
462 }
463 }
464
465
466
467
468
469
470
471
472 protected void parseValueConstraints( TokenStream tokens,
473 List<Property> properties ) {
474 if (tokens.canConsume('<')) {
475 List<String> defaultValues = parseStringList(tokens);
476 if (!defaultValues.isEmpty()) {
477 properties.add(propertyFactory.create(JcrLexicon.VALUE_CONSTRAINTS, defaultValues));
478 }
479 }
480 }
481
482
483
484
485
486
487
488
489
490
491 protected void parsePropertyAttributes( TokenStream tokens,
492 List<Property> properties,
493 Name propDefnName,
494 Path propDefnPath ) {
495 boolean autoCreated = false;
496 boolean mandatory = false;
497 boolean isProtected = false;
498 boolean multiple = false;
499 boolean isFullTextSearchable = true;
500 boolean isQueryOrderable = true;
501 String onParentVersion = "COPY";
502 while (true) {
503 if (tokens.canConsumeAnyOf("AUTOCREATED", "AUT", "A")) {
504 tokens.canConsume('?');
505 autoCreated = true;
506 } else if (tokens.canConsumeAnyOf("MANDATORY", "MAN", "M")) {
507 tokens.canConsume('?');
508 mandatory = true;
509 } else if (tokens.canConsumeAnyOf("PROTECTED", "PRO", "P")) {
510 tokens.canConsume('?');
511 isProtected = true;
512 } else if (tokens.canConsumeAnyOf("MULTIPLE", "MUL", "*")) {
513 tokens.canConsume('?');
514 multiple = true;
515 } else if (tokens.matchesAnyOf(VALID_ON_PARENT_VERSION)) {
516 onParentVersion = tokens.consume();
517 tokens.canConsume('?');
518 } else if (tokens.matches("OPV")) {
519
520 onParentVersion = tokens.consume();
521 tokens.canConsume('?');
522 } else if (tokens.canConsumeAnyOf("NOFULLTEXT", "NOF")) {
523 tokens.canConsume('?');
524 isFullTextSearchable = false;
525 } else if (tokens.canConsumeAnyOf("NOQUERYORDER", "NQORD")) {
526 tokens.canConsume('?');
527 isQueryOrderable = false;
528 } else if (tokens.canConsumeAnyOf("QUERYOPS", "QOP")) {
529 parseQueryOperators(tokens, properties);
530 } else if (tokens.canConsumeAnyOf("PRIMARY", "PRI", "!")) {
531 if (!jcr170) {
532 Position pos = tokens.previousPosition();
533 int line = pos.getLine();
534 int column = pos.getColumn();
535 throw new ParsingException(tokens.previousPosition(),
536 CndI18n.primaryKeywordNotValidInJcr2CndFormat.text(line, column));
537 }
538
539 Property primaryItem = propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, propDefnName);
540 destination.setProperties(propDefnPath.getParent(), primaryItem);
541 } else {
542 break;
543 }
544 }
545 properties.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, autoCreated));
546 properties.add(propertyFactory.create(JcrLexicon.MANDATORY, mandatory));
547 properties.add(propertyFactory.create(JcrLexicon.PROTECTED, isProtected));
548 properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion.toUpperCase()));
549 properties.add(propertyFactory.create(JcrLexicon.MULTIPLE, multiple));
550 properties.add(propertyFactory.create(JcrLexicon.IS_FULL_TEXT_SEARCHABLE, isFullTextSearchable));
551 properties.add(propertyFactory.create(JcrLexicon.IS_QUERY_ORDERABLE, isQueryOrderable));
552 }
553
554
555
556
557
558
559
560
561 protected void parseQueryOperators( TokenStream tokens,
562 List<Property> properties ) {
563 if (tokens.canConsume('?')) {
564 return;
565 }
566
567 List<String> operators = new ArrayList<String>();
568 String operatorList = removeQuotes(tokens.consume());
569
570 for (String operatorValue : operatorList.split(",")) {
571 String operator = operatorValue.trim();
572 if (!VALID_QUERY_OPERATORS.contains(operator)) {
573 throw new ParsingException(tokens.previousPosition(), CndI18n.expectedValidQueryOperator.text(operator));
574 }
575 operators.add(operator);
576 }
577 if (operators.isEmpty()) {
578 operators.addAll(VALID_QUERY_OPERATORS);
579 }
580 properties.add(propertyFactory.create(JcrLexicon.QUERY_OPERATORS, operators));
581 }
582
583
584
585
586
587
588
589
590
591 protected void parseChildNodeDefinition( TokenStream tokens,
592 Path nodeTypePath ) {
593 tokens.consume('+');
594 Name name = parseName(tokens);
595 Path path = pathFactory.create(nodeTypePath, JcrLexicon.CHILD_NODE_DEFINITION);
596 List<Property> properties = new ArrayList<Property>();
597 properties.add(propertyFactory.create(JcrLexicon.NAME, name));
598 properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.CHILD_NODE_DEFINITION));
599
600 parseRequiredPrimaryTypes(tokens, properties);
601 parseDefaultType(tokens, properties);
602 parseNodeAttributes(tokens, properties, name, path);
603
604
605 destination.create(path, properties);
606 }
607
608
609
610
611
612
613
614
615 protected void parseRequiredPrimaryTypes( TokenStream tokens,
616 List<Property> properties ) {
617 if (tokens.canConsume('(')) {
618 List<Name> requiredTypes = parseNameList(tokens);
619 if (requiredTypes.isEmpty()) {
620 requiredTypes.add(JcrNtLexicon.BASE);
621 }
622 properties.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, requiredTypes));
623 tokens.consume(')');
624 }
625 }
626
627
628
629
630
631
632
633
634 protected void parseDefaultType( TokenStream tokens,
635 List<Property> properties ) {
636 if (tokens.canConsume('=')) {
637 if (!tokens.canConsume('?')) {
638 Name defaultType = parseName(tokens);
639 properties.add(propertyFactory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, defaultType));
640 }
641 }
642 }
643
644
645
646
647
648
649
650
651
652
653 protected void parseNodeAttributes( TokenStream tokens,
654 List<Property> properties,
655 Name childNodeDefnName,
656 Path childNodeDefnPath ) {
657 boolean autoCreated = false;
658 boolean mandatory = false;
659 boolean isProtected = false;
660 boolean sns = false;
661 String onParentVersion = "COPY";
662 while (true) {
663 if (tokens.canConsumeAnyOf("AUTOCREATED", "AUT", "A")) {
664 tokens.canConsume('?');
665 autoCreated = true;
666 } else if (tokens.canConsumeAnyOf("MANDATORY", "MAN", "M")) {
667 tokens.canConsume('?');
668 mandatory = true;
669 } else if (tokens.canConsumeAnyOf("PROTECTED", "PRO", "P")) {
670 tokens.canConsume('?');
671 isProtected = true;
672 } else if (tokens.canConsumeAnyOf("SNS", "*")) {
673 tokens.canConsume('?');
674 sns = true;
675 } else if (tokens.canConsumeAnyOf("MULTIPLE", "MUL", "*")) {
676 if (!jcr170) {
677 Position pos = tokens.previousPosition();
678 int line = pos.getLine();
679 int column = pos.getColumn();
680 throw new ParsingException(tokens.previousPosition(),
681 CndI18n.multipleKeywordNotValidInJcr2CndFormat.text(line, column));
682 }
683 tokens.canConsume('?');
684 sns = true;
685 } else if (tokens.matchesAnyOf(VALID_ON_PARENT_VERSION)) {
686 onParentVersion = tokens.consume();
687 tokens.canConsume('?');
688 } else if (tokens.matches("OPV")) {
689
690 onParentVersion = tokens.consume();
691 tokens.canConsume('?');
692 } else if (tokens.canConsumeAnyOf("PRIMARYITEM", "PRIMARY", "PRI", "!")) {
693
694 Property primaryItem = propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, childNodeDefnName);
695 destination.setProperties(childNodeDefnPath.getParent(), primaryItem);
696 } else {
697 break;
698 }
699 }
700 properties.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, autoCreated));
701 properties.add(propertyFactory.create(JcrLexicon.MANDATORY, mandatory));
702 properties.add(propertyFactory.create(JcrLexicon.PROTECTED, isProtected));
703 properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, onParentVersion.toUpperCase()));
704 properties.add(propertyFactory.create(JcrLexicon.SAME_NAME_SIBLINGS, sns));
705 }
706
707
708
709
710
711
712
713
714 protected Name parseName( TokenStream tokens ) {
715 String value = tokens.consume();
716 try {
717 return nameFactory.create(removeQuotes(value));
718 } catch (ValueFormatException e) {
719 throw new ParsingException(tokens.previousPosition(), CndI18n.expectedValidNameLiteral.text(value));
720 }
721 }
722
723 protected final String removeQuotes( String text ) {
724
725 return text.replaceFirst("^['\"]+", "").replaceAll("['\"]+$", "");
726 }
727 }