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.graph.connector.federation;
25
26 import java.io.Serializable;
27 import java.lang.reflect.Method;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Set;
35 import java.util.concurrent.CopyOnWriteArrayList;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38 import net.jcip.annotations.Immutable;
39 import org.modeshape.common.text.TextEncoder;
40 import org.modeshape.common.util.CheckArg;
41 import org.modeshape.common.util.HashCode;
42 import org.modeshape.common.util.Logger;
43 import org.modeshape.graph.ExecutionContext;
44 import org.modeshape.graph.GraphI18n;
45 import org.modeshape.graph.connector.RepositorySource;
46 import org.modeshape.graph.property.NamespaceRegistry;
47 import org.modeshape.graph.property.Path;
48 import org.modeshape.graph.property.PathFactory;
49
50
51
52
53
54
55
56
57 @Immutable
58 public class Projection implements Comparable<Projection>, Serializable {
59
60
61
62
63 private static final long serialVersionUID = 1L;
64 protected static final List<Method> parserMethods;
65 private static final Logger LOGGER = Logger.getLogger(Projection.class);
66
67 static {
68 parserMethods = new CopyOnWriteArrayList<Method>();
69 try {
70 parserMethods.add(Projection.class.getDeclaredMethod("parsePathRule", String.class, ExecutionContext.class));
71 } catch (Throwable err) {
72 LOGGER.error(err, GraphI18n.errorAddingProjectionRuleParseMethod);
73 }
74 }
75
76
77
78
79
80
81
82
83
84
85
86
87 public static void addRuleParser( Method method ) {
88 if (method != null) parserMethods.add(method);
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 public static void addRuleParser( ClassLoader classLoader,
110 String className,
111 String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
112 CheckArg.isNotNull(classLoader, "classLoader");
113 CheckArg.isNotEmpty(className, "className");
114 CheckArg.isNotEmpty(methodName, "methodName");
115 Class<?> clazz = Class.forName(className, true, classLoader);
116 parserMethods.add(clazz.getMethod(className, String.class, ExecutionContext.class));
117 }
118
119
120
121
122
123
124
125 public static boolean removeRuleParser( Method method ) {
126 return parserMethods.remove(method);
127 }
128
129
130
131
132
133
134
135
136
137
138 public static boolean removeRuleParser( String declaringClassName,
139 String methodName ) {
140 CheckArg.isNotEmpty(declaringClassName, "declaringClassName");
141 CheckArg.isNotEmpty(methodName, "methodName");
142 for (Method method : parserMethods) {
143 if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) {
144 return parserMethods.remove(method);
145 }
146 }
147 return false;
148 }
149
150
151
152
153
154
155
156
157 public static Rule fromString( String definition,
158 ExecutionContext context ) {
159 CheckArg.isNotNull(context, "env");
160 definition = definition != null ? definition.trim() : "";
161 if (definition.length() == 0) return null;
162 for (Method method : parserMethods) {
163 try {
164 Rule rule = (Rule)method.invoke(null, definition, context);
165 if (rule != null) return rule;
166 } catch (Throwable err) {
167 String msg = "Error while parsing project rule definition \"{0}\" using {1}";
168 context.getLogger(Projection.class).trace(err, msg, definition, method);
169 }
170 }
171 return null;
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 protected static final String PATH_RULE_PATTERN_STRING = "((?:[^=$]|=(?!>))+)(?:(?:=>((?:[^=$]|=(?!>))+))( \\$ (?:(?:[^=]|=(?!>))+))*)?";
200 protected static final Pattern PATH_RULE_PATTERN = Pattern.compile(PATH_RULE_PATTERN_STRING);
201
202
203
204
205
206
207
208
209
210 public static PathRule parsePathRule( String definition,
211 ExecutionContext context ) {
212 definition = definition != null ? definition.trim() : "";
213 if (definition.length() == 0) return null;
214 Matcher matcher = PATH_RULE_PATTERN.matcher(definition);
215 if (!matcher.find()) return null;
216 String reposPathStr = matcher.group(1);
217 String sourcePathStr = matcher.group(2);
218 if (reposPathStr == null || sourcePathStr == null) return null;
219 reposPathStr = reposPathStr.trim();
220 sourcePathStr = sourcePathStr.trim();
221 if (reposPathStr.length() == 0 || sourcePathStr.length() == 0) return null;
222 PathFactory pathFactory = context.getValueFactories().getPathFactory();
223 Path repositoryPath = pathFactory.create(reposPathStr);
224 Path sourcePath = pathFactory.create(sourcePathStr);
225
226
227 List<Path> exceptions = new LinkedList<Path>();
228 while (matcher.find()) {
229 String exceptionStr = matcher.group(1);
230 Path exception = pathFactory.create(exceptionStr);
231 exceptions.add(exception);
232 }
233 return new PathRule(repositoryPath, sourcePath, exceptions);
234 }
235
236 private final String sourceName;
237 private final String workspaceName;
238 private final List<Rule> rules;
239 private final boolean simple;
240 private final boolean readOnly;
241 private final int hc;
242
243
244
245
246
247
248
249
250
251
252
253 public Projection( String sourceName,
254 String workspaceName,
255 boolean readOnly,
256 Rule... rules ) {
257 CheckArg.isNotEmpty(sourceName, "sourceName");
258 CheckArg.isNotEmpty(rules, "rules");
259 this.sourceName = sourceName;
260 this.workspaceName = workspaceName;
261 List<Rule> rulesList = new ArrayList<Rule>();
262 for (Rule rule : rules) {
263 if (rule != null) rulesList.add(rule);
264 }
265 this.readOnly = readOnly;
266 this.rules = Collections.unmodifiableList(rulesList);
267 CheckArg.isNotEmpty(this.rules, "rules");
268 this.simple = computeSimpleProjection(this.rules);
269 this.hc = HashCode.compute(this.sourceName, this.workspaceName);
270 }
271
272
273
274
275
276
277
278 public String getSourceName() {
279 return sourceName;
280 }
281
282
283
284
285
286
287 public String getWorkspaceName() {
288 return workspaceName;
289 }
290
291
292
293
294
295
296 public List<Rule> getRules() {
297 return rules;
298 }
299
300
301
302
303
304
305
306
307
308
309
310 public Set<Path> getPathsInSource( Path canonicalPathInRepository,
311 PathFactory factory ) {
312 CheckArg.isNotNull(factory, "factory");
313 assert canonicalPathInRepository == null ? true : canonicalPathInRepository.equals(canonicalPathInRepository.getCanonicalPath());
314 Set<Path> paths = new HashSet<Path>();
315 for (Rule rule : getRules()) {
316 Path pathInSource = rule.getPathInSource(canonicalPathInRepository, factory);
317 if (pathInSource != null) paths.add(pathInSource);
318 }
319 return paths;
320 }
321
322
323
324
325
326
327
328
329
330
331
332 public Set<Path> getPathsInRepository( Path canonicalPathInSource,
333 PathFactory factory ) {
334 CheckArg.isNotNull(factory, "factory");
335 assert canonicalPathInSource == null ? true : canonicalPathInSource.equals(canonicalPathInSource.getCanonicalPath());
336 Set<Path> paths = new HashSet<Path>();
337 for (Rule rule : getRules()) {
338 Path pathInRepository = rule.getPathInRepository(canonicalPathInSource, factory);
339 if (pathInRepository != null) paths.add(pathInRepository);
340 }
341 return paths;
342 }
343
344
345
346
347
348
349
350 public List<Path> getTopLevelPathsInRepository( PathFactory factory ) {
351 CheckArg.isNotNull(factory, "factory");
352 List<Rule> rules = getRules();
353 Set<Path> uniquePaths = new HashSet<Path>();
354 List<Path> paths = new ArrayList<Path>(rules.size());
355 for (Rule rule : getRules()) {
356 for (Path path : rule.getTopLevelPathsInRepository(factory)) {
357 if (!uniquePaths.contains(path)) {
358 paths.add(path);
359 uniquePaths.add(path);
360 }
361 }
362 }
363 return paths;
364 }
365
366
367
368
369
370
371
372 public boolean isTopLevelPath( Path repositoryPath ) {
373 for (Rule rule : getRules()) {
374 if (rule.isTopLevelPath(repositoryPath)) return true;
375 }
376 return false;
377 }
378
379
380
381
382
383
384
385
386 public boolean isSimple() {
387 return simple;
388 }
389
390
391
392
393
394
395 public boolean isReadOnly() {
396 return readOnly;
397 }
398
399 protected boolean computeSimpleProjection( List<Rule> rules ) {
400
401 Set<Path> repositoryPaths = new HashSet<Path>();
402 for (Rule rule : rules) {
403 if (rule instanceof PathRule) {
404 PathRule pathRule = (PathRule)rule;
405 Path repoPath = pathRule.getPathInRepository();
406 if (!repositoryPaths.isEmpty()) {
407 if (repositoryPaths.contains(repoPath)) return false;
408 for (Path path : repositoryPaths) {
409 if (path.isAtOrAbove(repoPath)) return false;
410 if (repoPath.isAtOrAbove(path)) return false;
411 }
412 }
413 repositoryPaths.add(repoPath);
414 } else {
415 return false;
416 }
417 }
418 return true;
419 }
420
421
422
423
424
425
426 @Override
427 public int hashCode() {
428 return this.hc;
429 }
430
431
432
433
434
435
436 @Override
437 public boolean equals( Object obj ) {
438 if (obj == this) return true;
439 if (obj instanceof Projection) {
440 Projection that = (Projection)obj;
441 if (this.hashCode() != that.hashCode()) return false;
442 if (!this.getSourceName().equals(that.getSourceName())) return false;
443 if (!this.getWorkspaceName().equals(that.getWorkspaceName())) return false;
444 if (!this.getRules().equals(that.getRules())) return false;
445 return true;
446 }
447 return false;
448 }
449
450
451
452
453
454
455 public int compareTo( Projection that ) {
456 if (this == that) return 0;
457 int diff = this.getSourceName().compareTo(that.getSourceName());
458 if (diff != 0) return diff;
459 diff = this.getWorkspaceName().compareTo(that.getWorkspaceName());
460 if (diff != 0) return diff;
461 Iterator<Rule> thisIter = this.getRules().iterator();
462 Iterator<Rule> thatIter = that.getRules().iterator();
463 while (thisIter.hasNext() && thatIter.hasNext()) {
464 diff = thisIter.next().compareTo(thatIter.next());
465 if (diff != 0) return diff;
466 }
467 if (thisIter.hasNext()) return 1;
468 if (thatIter.hasNext()) return -1;
469 return 0;
470 }
471
472
473
474
475
476
477 @Override
478 public String toString() {
479 StringBuilder sb = new StringBuilder();
480 if (this.workspaceName != null) {
481 sb.append(this.workspaceName);
482 sb.append('@');
483 }
484 sb.append(this.sourceName);
485 sb.append(" { ");
486 boolean first = true;
487 for (Rule rule : this.getRules()) {
488 if (!first) sb.append(" ; ");
489 sb.append(rule.toString());
490 first = false;
491 }
492 sb.append(" }");
493 return sb.toString();
494 }
495
496
497
498
499
500
501
502
503
504
505
506 @Immutable
507 public static abstract class Rule implements Comparable<Rule> {
508
509
510
511
512
513
514
515 public abstract List<Path> getTopLevelPathsInRepository( PathFactory factory );
516
517
518
519
520
521
522
523
524 public abstract boolean isTopLevelPath( Path path );
525
526
527
528
529
530
531
532
533
534 public abstract Path getPathInSource( Path pathInRepository,
535 PathFactory factory );
536
537
538
539
540
541
542
543
544
545 public abstract Path getPathInRepository( Path pathInSource,
546 PathFactory factory );
547
548 public abstract String getString( NamespaceRegistry registry,
549 TextEncoder encoder );
550
551 public abstract String getString( TextEncoder encoder );
552
553 public abstract String getString();
554 }
555
556
557
558
559
560
561
562
563 @Immutable
564 public static class PathRule extends Rule {
565
566 private final Path sourcePath;
567
568 private final Path repositoryPath;
569
570 private final List<Path> exceptions;
571 private final int hc;
572 private final List<Path> topLevelRepositoryPaths;
573
574 public PathRule( Path repositoryPath,
575 Path sourcePath ) {
576 this(repositoryPath, sourcePath, (Path[])null);
577 }
578
579 public PathRule( Path repositoryPath,
580 Path sourcePath,
581 Path... exceptions ) {
582 CheckArg.isNotNull(sourcePath, "sourcePath");
583 CheckArg.isNotNull(repositoryPath, "repositoryPath");
584 this.sourcePath = sourcePath;
585 this.repositoryPath = repositoryPath;
586 if (exceptions == null || exceptions.length == 0) {
587 this.exceptions = Collections.emptyList();
588 } else {
589 List<Path> exceptionList = new ArrayList<Path>();
590 for (Path exception : exceptions) {
591 if (exception != null) exceptionList.add(exception);
592 }
593 this.exceptions = Collections.unmodifiableList(exceptionList);
594 }
595 this.hc = HashCode.compute(sourcePath, repositoryPath, exceptions);
596 if (this.exceptions != null) {
597 for (Path path : this.exceptions) {
598 if (path.isAbsolute()) {
599 throw new IllegalArgumentException(GraphI18n.pathIsNotRelative.text(path));
600 }
601 }
602 }
603 this.topLevelRepositoryPaths = Collections.singletonList(getPathInRepository());
604 }
605
606 public PathRule( Path repositoryPath,
607 Path sourcePath,
608 List<Path> exceptions ) {
609 CheckArg.isNotNull(sourcePath, "sourcePath");
610 CheckArg.isNotNull(repositoryPath, "repositoryPath");
611 this.sourcePath = sourcePath;
612 this.repositoryPath = repositoryPath;
613 if (exceptions == null || exceptions.isEmpty()) {
614 this.exceptions = Collections.emptyList();
615 } else {
616 this.exceptions = Collections.unmodifiableList(new ArrayList<Path>(exceptions));
617 }
618 this.hc = HashCode.compute(sourcePath, repositoryPath, exceptions);
619 if (this.exceptions != null) {
620 for (Path path : this.exceptions) {
621 if (path.isAbsolute()) {
622 throw new IllegalArgumentException(GraphI18n.pathIsNotRelative.text(path));
623 }
624 }
625 }
626 this.topLevelRepositoryPaths = Collections.singletonList(getPathInRepository());
627 }
628
629
630
631
632
633
634 public Path getPathInRepository() {
635 return repositoryPath;
636 }
637
638
639
640
641
642
643 public Path getPathInSource() {
644 return sourcePath;
645 }
646
647
648
649
650
651
652 public boolean hasExceptionsToRule() {
653 return exceptions.size() != 0;
654 }
655
656
657
658
659
660
661
662 public List<Path> getExceptionsToRule() {
663 return exceptions;
664 }
665
666
667
668
669
670 protected boolean includes( Path pathInSource ) {
671
672 if (pathInSource != null && this.sourcePath.isAtOrAbove(pathInSource)) {
673
674
675 List<Path> exceptions = getExceptionsToRule();
676 if (exceptions.size() != 0) {
677 Path subpathInSource = pathInSource.relativeTo(this.sourcePath);
678 if (subpathInSource.size() != 0) {
679 for (Path exception : exceptions) {
680 if (subpathInSource.isAtOrBelow(exception)) return false;
681 }
682 }
683 }
684 return true;
685 }
686 return false;
687 }
688
689
690
691
692
693
694 @Override
695 public List<Path> getTopLevelPathsInRepository( PathFactory factory ) {
696 return topLevelRepositoryPaths;
697 }
698
699
700
701
702
703
704 @Override
705 public boolean isTopLevelPath( Path path ) {
706 for (Path topLevel : topLevelRepositoryPaths) {
707 if (topLevel.equals(path)) return true;
708 }
709 return false;
710 }
711
712
713
714
715
716
717
718
719
720
721 @Override
722 public Path getPathInSource( Path pathInRepository,
723 PathFactory factory ) {
724 assert pathInRepository.equals(pathInRepository.getCanonicalPath());
725
726 Path pathInSource = projectPathInRepositoryToPathInSource(pathInRepository, factory);
727
728
729 return includes(pathInSource) ? pathInSource : null;
730 }
731
732
733
734
735
736
737 @Override
738 public Path getPathInRepository( Path pathInSource,
739 PathFactory factory ) {
740 assert pathInSource.equals(pathInSource.getCanonicalPath());
741
742 if (!includes(pathInSource)) return null;
743
744
745 return projectPathInSourceToPathInRepository(pathInSource, factory);
746 }
747
748
749
750
751
752
753
754
755
756 protected Path projectPathInSourceToPathInRepository( Path pathInSource,
757 PathFactory factory ) {
758 if (this.sourcePath.equals(pathInSource)) return this.repositoryPath;
759 if (!this.sourcePath.isAncestorOf(pathInSource)) return null;
760
761 Path relativeSourcePath = pathInSource.relativeTo(this.sourcePath);
762
763 Path result = factory.create(this.repositoryPath, relativeSourcePath);
764 return result.getNormalizedPath();
765 }
766
767
768
769
770
771
772
773
774
775 protected Path projectPathInRepositoryToPathInSource( Path pathInRepository,
776 PathFactory factory ) {
777 if (this.repositoryPath.equals(pathInRepository)) return this.sourcePath;
778 if (!this.repositoryPath.isAncestorOf(pathInRepository)) return null;
779
780 Path pathInRegion = pathInRepository.relativeTo(this.repositoryPath);
781
782 Path result = factory.create(this.sourcePath, pathInRegion);
783 return result.getNormalizedPath();
784 }
785
786 @Override
787 public String getString( NamespaceRegistry registry,
788 TextEncoder encoder ) {
789 StringBuilder sb = new StringBuilder();
790 sb.append(this.getPathInRepository().getString(registry, encoder));
791 sb.append(" => ");
792 sb.append(this.getPathInSource().getString(registry, encoder));
793 if (this.getExceptionsToRule().size() != 0) {
794 for (Path exception : this.getExceptionsToRule()) {
795 sb.append(" $ ");
796 sb.append(exception.getString(registry, encoder));
797 }
798 }
799 return sb.toString();
800 }
801
802
803
804
805
806
807 @Override
808 public String getString( TextEncoder encoder ) {
809 StringBuilder sb = new StringBuilder();
810 sb.append(this.getPathInRepository().getString(encoder));
811 sb.append(" => ");
812 sb.append(this.getPathInSource().getString(encoder));
813 if (this.getExceptionsToRule().size() != 0) {
814 for (Path exception : this.getExceptionsToRule()) {
815 sb.append(" $ ");
816 sb.append(exception.getString(encoder));
817 }
818 }
819 return sb.toString();
820 }
821
822
823
824
825
826
827 @Override
828 public String getString() {
829 return getString(Path.JSR283_ENCODER);
830 }
831
832
833
834
835
836
837 @Override
838 public int hashCode() {
839 return hc;
840 }
841
842
843
844
845
846
847 @Override
848 public boolean equals( Object obj ) {
849 if (obj == this) return true;
850 if (obj instanceof PathRule) {
851 PathRule that = (PathRule)obj;
852 if (!this.getPathInRepository().equals(that.getPathInRepository())) return false;
853 if (!this.getPathInSource().equals(that.getPathInSource())) return false;
854 if (!this.getExceptionsToRule().equals(that.getExceptionsToRule())) return false;
855 return true;
856 }
857 return false;
858 }
859
860
861
862
863
864
865 public int compareTo( Rule other ) {
866 if (other == this) return 0;
867 if (other instanceof PathRule) {
868 PathRule that = (PathRule)other;
869 int diff = this.getPathInRepository().compareTo(that.getPathInRepository());
870 if (diff != 0) return diff;
871 diff = this.getPathInSource().compareTo(that.getPathInSource());
872 if (diff != 0) return diff;
873 Iterator<Path> thisIter = this.getExceptionsToRule().iterator();
874 Iterator<Path> thatIter = that.getExceptionsToRule().iterator();
875 while (thisIter.hasNext() && thatIter.hasNext()) {
876 diff = thisIter.next().compareTo(thatIter.next());
877 if (diff != 0) return diff;
878 }
879 if (thisIter.hasNext()) return 1;
880 if (thatIter.hasNext()) return -1;
881 return 0;
882 }
883 return other.getClass().getName().compareTo(this.getClass().getName());
884 }
885
886
887
888
889
890
891 @Override
892 public String toString() {
893 return getString();
894 }
895 }
896 }