001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.connector.federation; 025 026 import java.io.Serializable; 027 import java.lang.reflect.Method; 028 import java.util.ArrayList; 029 import java.util.Collections; 030 import java.util.HashSet; 031 import java.util.Iterator; 032 import java.util.LinkedList; 033 import java.util.List; 034 import java.util.Set; 035 import java.util.concurrent.CopyOnWriteArrayList; 036 import java.util.regex.Matcher; 037 import java.util.regex.Pattern; 038 import net.jcip.annotations.Immutable; 039 import org.jboss.dna.common.text.TextEncoder; 040 import org.jboss.dna.common.util.CheckArg; 041 import org.jboss.dna.common.util.HashCode; 042 import org.jboss.dna.common.util.Logger; 043 import org.jboss.dna.graph.ExecutionContext; 044 import org.jboss.dna.graph.connector.RepositorySource; 045 import org.jboss.dna.graph.property.NamespaceRegistry; 046 import org.jboss.dna.graph.property.Path; 047 import org.jboss.dna.graph.property.PathFactory; 048 049 /** 050 * A projection of content from a source into the integrated/federated repository. Each project consists of a set of {@link Rule 051 * rules} for a particular source, where each rule defines how content within a source is 052 * {@link Rule#getPathInRepository(Path, PathFactory) is project into the repository} and how the repository content is 053 * {@link Rule#getPathInSource(Path, PathFactory) projected into the source}. Different rule subclasses are used for different 054 * types. 055 * 056 * @author Randall Hauch 057 */ 058 @Immutable 059 public class Projection implements Comparable<Projection>, Serializable { 060 061 /** 062 * Initial version 063 */ 064 private static final long serialVersionUID = 1L; 065 protected static final List<Method> parserMethods; 066 static { 067 parserMethods = new CopyOnWriteArrayList<Method>(); 068 try { 069 parserMethods.add(Projection.class.getDeclaredMethod("parsePathRule", String.class, ExecutionContext.class)); 070 } catch (Throwable err) { 071 Logger.getLogger(Projection.class).error(err, FederationI18n.errorAddingProjectionRuleParseMethod); 072 } 073 } 074 075 /** 076 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition 077 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an 078 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or 079 * null if the definition format could not be understood by the method. Any exceptions during 080 * {@link Method#invoke(Object, Object...) invocation} will be logged at the 081 * {@link Logger#trace(Throwable, String, Object...) trace} level. 082 * 083 * @param method the method to be added 084 * @see #addRuleParser(ClassLoader, String, String) 085 */ 086 public static void addRuleParser( Method method ) { 087 if (method != null) parserMethods.add(method); 088 } 089 090 /** 091 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition 092 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an 093 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or 094 * null if the definition format could not be understood by the method. Any exceptions during 095 * {@link Method#invoke(Object, Object...) invocation} will be logged at the 096 * {@link Logger#trace(Throwable, String, Object...) trace} level. 097 * 098 * @param classLoader the class loader that should be used to load the class on which the method is defined; may not be null 099 * @param className the name of the class on which the static method is defined; may not be null 100 * @param methodName the name of the method 101 * @throws SecurityException if there is a security exception while loading the class or getting the method 102 * @throws NoSuchMethodException if the method does not exist on the class 103 * @throws ClassNotFoundException if the class could not be found given the supplied class loader 104 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or 105 * empty 106 * @see #addRuleParser(Method) 107 */ 108 public static void addRuleParser( ClassLoader classLoader, 109 String className, 110 String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException { 111 CheckArg.isNotNull(classLoader, "classLoader"); 112 CheckArg.isNotEmpty(className, "className"); 113 CheckArg.isNotEmpty(methodName, "methodName"); 114 Class<?> clazz = Class.forName(className, true, classLoader); 115 parserMethods.add(clazz.getMethod(className, String.class, ExecutionContext.class)); 116 } 117 118 /** 119 * Remove the rule parser method. 120 * 121 * @param method the method to remove 122 * @return true if the method was removed, or false if the method was not a registered rule parser method 123 */ 124 public static boolean removeRuleParser( Method method ) { 125 return parserMethods.remove(method); 126 } 127 128 /** 129 * Remove the rule parser method. 130 * 131 * @param declaringClassName the name of the class on which the static method is defined; may not be null 132 * @param methodName the name of the method 133 * @return true if the method was removed, or false if the method was not a registered rule parser method 134 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or 135 * empty 136 */ 137 public static boolean removeRuleParser( String declaringClassName, 138 String methodName ) { 139 CheckArg.isNotEmpty(declaringClassName, "declaringClassName"); 140 CheckArg.isNotEmpty(methodName, "methodName"); 141 for (Method method : parserMethods) { 142 if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) { 143 return parserMethods.remove(method); 144 } 145 } 146 return false; 147 } 148 149 /** 150 * Parse the string form of a rule definition and return the rule 151 * 152 * @param definition the definition of the rule that is to be parsed 153 * @param context the environment in which this method is being executed; may not be null 154 * @return the rule, or null if the definition could not be parsed 155 */ 156 public static Rule fromString( String definition, 157 ExecutionContext context ) { 158 CheckArg.isNotNull(context, "env"); 159 definition = definition != null ? definition.trim() : ""; 160 if (definition.length() == 0) return null; 161 for (Method method : parserMethods) { 162 try { 163 Rule rule = (Rule)method.invoke(null, definition, context); 164 if (rule != null) return rule; 165 } catch (Throwable err) { 166 String msg = "Error while parsing project rule definition \"{0}\" using {1}"; 167 context.getLogger(Projection.class).trace(err, msg, definition, method); 168 } 169 } 170 return null; 171 } 172 173 /** 174 * Pattern that identifies the form: 175 * 176 * <pre> 177 * repository_path => source_path [$ exception ]* 178 * </pre> 179 * 180 * where the following groups are captured on the first call to {@link Matcher#find()}: 181 * <ol> 182 * <li><code>repository_path</code></li> 183 * <li><code>source_path</code></li> 184 * </ol> 185 * and the following groups are captured on subsequent calls to {@link Matcher#find()}: 186 * <ol> 187 * <li>exception</code></li> 188 * </ol> 189 * <p> 190 * The regular expression is: 191 * 192 * <pre> 193 * ((?:[ˆ=$]|=(?!>))+)(?:(?:=>((?:[ˆ=$]|=(?!>))+))( \$ (?:(?:[ˆ=]|=(?!>))+))*)? 194 * </pre> 195 * 196 * </p> 197 */ 198 protected static final String PATH_RULE_PATTERN_STRING = "((?:[^=$]|=(?!>))+)(?:(?:=>((?:[^=$]|=(?!>))+))( \\$ (?:(?:[^=]|=(?!>))+))*)?"; 199 protected static final Pattern PATH_RULE_PATTERN = Pattern.compile(PATH_RULE_PATTERN_STRING); 200 201 /** 202 * Parse the string definition of a {@link PathRule}. This method is automatically registered in the {@link #parserMethods 203 * parser methods} by the static initializer of {@link Projection}. 204 * 205 * @param definition the definition 206 * @param context the environment 207 * @return the path rule, or null if the definition is not in the right form 208 */ 209 public static PathRule parsePathRule( String definition, 210 ExecutionContext context ) { 211 definition = definition != null ? definition.trim() : ""; 212 if (definition.length() == 0) return null; 213 Matcher matcher = PATH_RULE_PATTERN.matcher(definition); 214 if (!matcher.find()) return null; 215 String reposPathStr = matcher.group(1); 216 String sourcePathStr = matcher.group(2); 217 if (reposPathStr == null || sourcePathStr == null) return null; 218 reposPathStr = reposPathStr.trim(); 219 sourcePathStr = sourcePathStr.trim(); 220 if (reposPathStr.length() == 0 || sourcePathStr.length() == 0) return null; 221 PathFactory pathFactory = context.getValueFactories().getPathFactory(); 222 Path repositoryPath = pathFactory.create(reposPathStr); 223 Path sourcePath = pathFactory.create(sourcePathStr); 224 225 // Grab the exceptions ... 226 List<Path> exceptions = new LinkedList<Path>(); 227 while (matcher.find()) { 228 String exceptionStr = matcher.group(1); 229 Path exception = pathFactory.create(exceptionStr); 230 exceptions.add(exception); 231 } 232 return new PathRule(repositoryPath, sourcePath, exceptions); 233 } 234 235 private final String sourceName; 236 private final String workspaceName; 237 private final List<Rule> rules; 238 private final boolean simple; 239 private final int hc; 240 241 /** 242 * Create a new federated projection for the supplied source, using the supplied rules. 243 * 244 * @param sourceName the name of the source 245 * @param workspaceName the name of the workspace in the source; may be null if the default workspace is to be used 246 * @param rules the projection rules 247 * @throws IllegalArgumentException if the source name or rule array is null, empty, or contains all nulls 248 */ 249 public Projection( String sourceName, 250 String workspaceName, 251 Rule... rules ) { 252 CheckArg.isNotEmpty(sourceName, "sourceName"); 253 CheckArg.isNotEmpty(rules, "rules"); 254 this.sourceName = sourceName; 255 this.workspaceName = workspaceName; 256 List<Rule> rulesList = new ArrayList<Rule>(); 257 for (Rule rule : rules) { 258 if (rule != null) rulesList.add(rule); 259 } 260 this.rules = Collections.unmodifiableList(rulesList); 261 CheckArg.isNotEmpty(this.rules, "rules"); 262 this.simple = computeSimpleProjection(this.rules); 263 this.hc = HashCode.compute(this.sourceName, this.workspaceName); 264 } 265 266 /** 267 * Get the name of the source to which this projection applies. 268 * 269 * @return the source name 270 * @see RepositorySource#getName() 271 */ 272 public String getSourceName() { 273 return sourceName; 274 } 275 276 /** 277 * Get the name of the workspace in the source to which this projection applies. 278 * 279 * @return the workspace name, or null if the default workspace of the {@link #getSourceName() source} is to be used 280 */ 281 public String getWorkspaceName() { 282 return workspaceName; 283 } 284 285 /** 286 * Get the rules that define this projection. 287 * 288 * @return the unmodifiable list of immutable rules; never null 289 */ 290 public List<Rule> getRules() { 291 return rules; 292 } 293 294 /** 295 * Get the paths in the source that correspond to the supplied path within the repository. This method computes the paths 296 * given all of the rules. In general, most sources will probably project a node onto a single repository node. However, some 297 * sources may be configured such that the same node in the repository is a projection of multiple nodes within the source. 298 * 299 * @param canonicalPathInRepository the canonical path of the node within the repository; may not be null 300 * @param factory the path factory; may not be null 301 * @return the set of unique paths in the source projected from the repository path; never null 302 * @throws IllegalArgumentException if the factory reference is null 303 */ 304 public Set<Path> getPathsInSource( Path canonicalPathInRepository, 305 PathFactory factory ) { 306 CheckArg.isNotNull(factory, "factory"); 307 assert canonicalPathInRepository == null ? true : canonicalPathInRepository.equals(canonicalPathInRepository.getCanonicalPath()); 308 Set<Path> paths = new HashSet<Path>(); 309 if (canonicalPathInRepository != null) { 310 for (Rule rule : getRules()) { 311 Path pathInSource = rule.getPathInSource(canonicalPathInRepository, factory); 312 if (pathInSource != null) paths.add(pathInSource); 313 } 314 } 315 return paths; 316 } 317 318 /** 319 * Get the paths in the repository that correspond to the supplied path within the source. This method computes the paths 320 * given all of the rules. In general, most sources will probably project a node onto a single repository node. However, some 321 * sources may be configured such that the same node in the source is projected into multiple nodes within the repository. 322 * 323 * @param canonicalPathInSource the canonical path of the node within the source; may not be null 324 * @param factory the path factory; may not be null 325 * @return the set of unique paths in the repository projected from the source path; never null 326 * @throws IllegalArgumentException if the factory reference is null 327 */ 328 public Set<Path> getPathsInRepository( Path canonicalPathInSource, 329 PathFactory factory ) { 330 CheckArg.isNotNull(factory, "factory"); 331 assert canonicalPathInSource == null ? true : canonicalPathInSource.equals(canonicalPathInSource.getCanonicalPath()); 332 Set<Path> paths = new HashSet<Path>(); 333 for (Rule rule : getRules()) { 334 Path pathInRepository = rule.getPathInRepository(canonicalPathInSource, factory); 335 if (pathInRepository != null) paths.add(pathInRepository); 336 } 337 return paths; 338 } 339 340 /** 341 * Get the paths in the repository that serve as top-level nodes exposed by this projection. 342 * 343 * @param factory the path factory that can be used to create new paths; may not be null 344 * @return the list of top-level paths, in the proper order and containing no duplicates; never null 345 */ 346 public List<Path> getTopLevelPathsInRepository( PathFactory factory ) { 347 CheckArg.isNotNull(factory, "factory"); 348 List<Rule> rules = getRules(); 349 Set<Path> uniquePaths = new HashSet<Path>(); 350 List<Path> paths = new ArrayList<Path>(rules.size()); 351 for (Rule rule : getRules()) { 352 for (Path path : rule.getTopLevelPathsInRepository(factory)) { 353 if (!uniquePaths.contains(path)) { 354 paths.add(path); 355 uniquePaths.add(path); 356 } 357 } 358 } 359 return paths; 360 } 361 362 /** 363 * Determine whether this project is a simple projection that only involves for any one repository path no more than a single 364 * source path. 365 * 366 * @return true if this projection is a simple projection, or false if the projection is not simple (or it cannot be 367 * determined if it is simple) 368 */ 369 public boolean isSimple() { 370 return simple; 371 } 372 373 protected boolean computeSimpleProjection( List<Rule> rules ) { 374 // Get the set of repository paths for the rules, and see if they overlap ... 375 Set<Path> repositoryPaths = new HashSet<Path>(); 376 for (Rule rule : rules) { 377 if (rule instanceof PathRule) { 378 PathRule pathRule = (PathRule)rule; 379 Path repoPath = pathRule.getPathInRepository(); 380 if (!repositoryPaths.isEmpty()) { 381 if (repositoryPaths.contains(repoPath)) return false; 382 for (Path path : repositoryPaths) { 383 if (path.isAtOrAbove(repoPath)) return false; 384 if (repoPath.isAtOrAbove(path)) return false; 385 } 386 } 387 repositoryPaths.add(repoPath); 388 } else { 389 return false; 390 } 391 } 392 return true; 393 } 394 395 /** 396 * {@inheritDoc} 397 * 398 * @see java.lang.Object#hashCode() 399 */ 400 @Override 401 public int hashCode() { 402 return this.hc; 403 } 404 405 /** 406 * {@inheritDoc} 407 * 408 * @see java.lang.Object#equals(java.lang.Object) 409 */ 410 @Override 411 public boolean equals( Object obj ) { 412 if (obj == this) return true; 413 if (obj instanceof Projection) { 414 Projection that = (Projection)obj; 415 if (this.hashCode() != that.hashCode()) return false; 416 if (!this.getSourceName().equals(that.getSourceName())) return false; 417 if (!this.getWorkspaceName().equals(that.getWorkspaceName())) return false; 418 if (!this.getRules().equals(that.getRules())) return false; 419 return true; 420 } 421 return false; 422 } 423 424 /** 425 * {@inheritDoc} 426 * 427 * @see java.lang.Comparable#compareTo(java.lang.Object) 428 */ 429 public int compareTo( Projection that ) { 430 if (this == that) return 0; 431 int diff = this.getSourceName().compareTo(that.getSourceName()); 432 if (diff != 0) return diff; 433 diff = this.getWorkspaceName().compareTo(that.getWorkspaceName()); 434 if (diff != 0) return diff; 435 Iterator<Rule> thisIter = this.getRules().iterator(); 436 Iterator<Rule> thatIter = that.getRules().iterator(); 437 while (thisIter.hasNext() && thatIter.hasNext()) { 438 diff = thisIter.next().compareTo(thatIter.next()); 439 if (diff != 0) return diff; 440 } 441 if (thisIter.hasNext()) return 1; 442 if (thatIter.hasNext()) return -1; 443 return 0; 444 } 445 446 /** 447 * {@inheritDoc} 448 * 449 * @see java.lang.Object#toString() 450 */ 451 @Override 452 public String toString() { 453 StringBuilder sb = new StringBuilder(); 454 sb.append(this.sourceName); 455 sb.append("::"); 456 sb.append(this.workspaceName); 457 sb.append(" { "); 458 boolean first = true; 459 for (Rule rule : this.getRules()) { 460 if (!first) sb.append(" ; "); 461 sb.append(rule.toString()); 462 first = false; 463 } 464 sb.append(" }"); 465 return sb.toString(); 466 } 467 468 /** 469 * A rule used within a project do define how content within a source is projected into the federated repository. This mapping 470 * is bi-directional, meaning it's possible to determine 471 * <ul> 472 * <li>the path in repository given a path in source; and</li> 473 * <li>the path in source given a path in repository.</li> 474 * </ul> 475 * 476 * @author Randall Hauch 477 */ 478 @Immutable 479 public static abstract class Rule implements Comparable<Rule> { 480 481 /** 482 * Get the paths in the repository that serve as top-level nodes exposed by this rule. 483 * 484 * @param factory the path factory that can be used to create new paths; may not be null 485 * @return the list of top-level paths, which are ordered and which must be unique; never null 486 */ 487 public abstract List<Path> getTopLevelPathsInRepository( PathFactory factory ); 488 489 /** 490 * Get the path in source that is projected from the supplied repository path, or null if the supplied repository path is 491 * not projected into the source. 492 * 493 * @param pathInRepository the path in the repository; may not be null 494 * @param factory the path factory; may not be null 495 * @return the path in source if it is projected by this rule, or null otherwise 496 */ 497 public abstract Path getPathInSource( Path pathInRepository, 498 PathFactory factory ); 499 500 /** 501 * Get the path in repository that is projected from the supplied source path, or null if the supplied source path is not 502 * projected into the repository. 503 * 504 * @param pathInSource the path in the source; may not be null 505 * @param factory the path factory; may not be null 506 * @return the path in repository if it is projected by this rule, or null otherwise 507 */ 508 public abstract Path getPathInRepository( Path pathInSource, 509 PathFactory factory ); 510 511 public abstract String getString( NamespaceRegistry registry, 512 TextEncoder encoder ); 513 514 public abstract String getString( TextEncoder encoder ); 515 516 public abstract String getString(); 517 } 518 519 /** 520 * A rule that is defined with a single {@link #getPathInSource() path in source} and a single {@link #getPathInRepository() 521 * path in repository}, and which has a set of {@link #getExceptionsToRule() path exceptions} (relative paths below the path 522 * in source). 523 * 524 * @author Randall Hauch 525 */ 526 @Immutable 527 public static class PathRule extends Rule { 528 /** The path of the content as known to the source */ 529 private final Path sourcePath; 530 /** The path where the content is to be placed ("projected") into the repository */ 531 private final Path repositoryPath; 532 /** The paths (relative to the source path) that identify exceptions to this rule */ 533 private final List<Path> exceptions; 534 private final int hc; 535 private final List<Path> topLevelRepositoryPaths; 536 537 public PathRule( Path repositoryPath, 538 Path sourcePath ) { 539 this(repositoryPath, sourcePath, (Path[])null); 540 } 541 542 public PathRule( Path repositoryPath, 543 Path sourcePath, 544 Path... exceptions ) { 545 assert sourcePath != null; 546 assert repositoryPath != null; 547 this.sourcePath = sourcePath; 548 this.repositoryPath = repositoryPath; 549 if (exceptions == null || exceptions.length == 0) { 550 this.exceptions = Collections.emptyList(); 551 } else { 552 List<Path> exceptionList = new ArrayList<Path>(); 553 for (Path exception : exceptions) { 554 if (exception != null) exceptionList.add(exception); 555 } 556 this.exceptions = Collections.unmodifiableList(exceptionList); 557 } 558 this.hc = HashCode.compute(sourcePath, repositoryPath, exceptions); 559 assert exceptionPathsAreRelative(); 560 this.topLevelRepositoryPaths = Collections.singletonList(getPathInRepository()); 561 } 562 563 public PathRule( Path repositoryPath, 564 Path sourcePath, 565 List<Path> exceptions ) { 566 assert sourcePath != null; 567 assert repositoryPath != null; 568 this.sourcePath = sourcePath; 569 this.repositoryPath = repositoryPath; 570 if (exceptions == null || exceptions.isEmpty()) { 571 this.exceptions = Collections.emptyList(); 572 } else { 573 this.exceptions = Collections.unmodifiableList(new ArrayList<Path>(exceptions)); 574 } 575 this.hc = HashCode.compute(sourcePath, repositoryPath, exceptions); 576 assert exceptionPathsAreRelative(); 577 this.topLevelRepositoryPaths = Collections.singletonList(getPathInRepository()); 578 } 579 580 private boolean exceptionPathsAreRelative() { 581 if (this.exceptions != null) { 582 for (Path path : this.exceptions) { 583 if (path.isAbsolute()) return false; 584 } 585 } 586 return true; 587 } 588 589 /** 590 * The path where the content is to be placed ("projected") into the repository. 591 * 592 * @return the projected path of the content in the repository; never null 593 */ 594 public Path getPathInRepository() { 595 return repositoryPath; 596 } 597 598 /** 599 * The path of the content as known to the source 600 * 601 * @return the source-specific path of the content; never null 602 */ 603 public Path getPathInSource() { 604 return sourcePath; 605 } 606 607 /** 608 * Get whether this rule has any exceptions. 609 * 610 * @return true if this rule has exceptions, or false if it has none. 611 */ 612 public boolean hasExceptionsToRule() { 613 return exceptions.size() != 0; 614 } 615 616 /** 617 * Get the paths that define the exceptions to this rule. These paths are always relative to the 618 * {@link #getPathInSource() path in source}. 619 * 620 * @return the unmodifiable exception paths; never null but possibly empty 621 */ 622 public List<Path> getExceptionsToRule() { 623 return exceptions; 624 } 625 626 /** 627 * @param pathInSource 628 * @return true if the source path is included by this rule 629 */ 630 protected boolean includes( Path pathInSource ) { 631 // Check whether the path is outside the source-specific path ... 632 if (pathInSource != null && this.sourcePath.isAtOrAbove(pathInSource)) { 633 634 // The path is inside the source-specific region, so check the exceptions ... 635 List<Path> exceptions = getExceptionsToRule(); 636 if (exceptions.size() != 0) { 637 Path subpathInSource = pathInSource.relativeTo(this.sourcePath); 638 if (subpathInSource.size() != 0) { 639 for (Path exception : exceptions) { 640 if (subpathInSource.isAtOrBelow(exception)) return false; 641 } 642 } 643 } 644 return true; 645 } 646 return false; 647 } 648 649 /** 650 * {@inheritDoc} 651 * 652 * @see org.jboss.dna.connector.federation.Projection.Rule#getTopLevelPathsInRepository(org.jboss.dna.graph.property.PathFactory) 653 */ 654 @Override 655 public List<Path> getTopLevelPathsInRepository( PathFactory factory ) { 656 return topLevelRepositoryPaths; 657 } 658 659 /** 660 * {@inheritDoc} 661 * <p> 662 * This method considers a path that is at or below the rule's {@link #getPathInSource() source path} to be included, 663 * except if there are {@link #getExceptionsToRule() exceptions} that explicitly disallow the path. 664 * </p> 665 * 666 * @see org.jboss.dna.connector.federation.Projection.Rule#getPathInSource(Path, PathFactory) 667 */ 668 @Override 669 public Path getPathInSource( Path pathInRepository, 670 PathFactory factory ) { 671 assert pathInRepository.equals(pathInRepository.getCanonicalPath()); 672 // Project the repository path into the equivalent source path ... 673 Path pathInSource = projectPathInRepositoryToPathInSource(pathInRepository, factory); 674 675 // Check whether the source path is included by this rule ... 676 return includes(pathInSource) ? pathInSource : null; 677 } 678 679 /** 680 * {@inheritDoc} 681 * 682 * @see org.jboss.dna.connector.federation.Projection.Rule#getPathInRepository(org.jboss.dna.graph.property.Path, 683 * org.jboss.dna.graph.property.PathFactory) 684 */ 685 @Override 686 public Path getPathInRepository( Path pathInSource, 687 PathFactory factory ) { 688 assert pathInSource.equals(pathInSource.getCanonicalPath()); 689 // Check whether the source path is included by this rule ... 690 if (!includes(pathInSource)) return null; 691 692 // Project the repository path into the equivalent source path ... 693 return projectPathInSourceToPathInRepository(pathInSource, factory); 694 } 695 696 /** 697 * Convert a path defined in the source system into an equivalent path in the repository system. 698 * 699 * @param pathInSource the path in the source system, which may include the {@link #getPathInSource()} 700 * @param factory the path factory; may not be null 701 * @return the path in the repository system, which will be normalized and absolute (including the 702 * {@link #getPathInRepository()}), or null if the path is not at or under the {@link #getPathInSource()} 703 */ 704 protected Path projectPathInSourceToPathInRepository( Path pathInSource, 705 PathFactory factory ) { 706 if (!this.sourcePath.isAtOrAbove(pathInSource)) return null; 707 // Remove the leading source path ... 708 Path relativeSourcePath = pathInSource.relativeTo(this.sourcePath); 709 // Prepend the region's root path ... 710 Path result = factory.create(this.repositoryPath, relativeSourcePath); 711 return result.getNormalizedPath(); 712 } 713 714 /** 715 * Convert a path defined in the repository system into an equivalent path in the source system. 716 * 717 * @param pathInRepository the path in the repository system, which may include the {@link #getPathInRepository()} 718 * @param factory the path factory; may not be null 719 * @return the path in the source system, which will be normalized and absolute (including the {@link #getPathInSource()} 720 * ), or null if the path is not at or under the {@link #getPathInRepository()} 721 */ 722 protected Path projectPathInRepositoryToPathInSource( Path pathInRepository, 723 PathFactory factory ) { 724 if (!this.repositoryPath.isAtOrAbove(pathInRepository)) return null; 725 // Find the relative path from the root of this region ... 726 Path pathInRegion = pathInRepository.relativeTo(this.repositoryPath); 727 // Prepend the path in source ... 728 Path result = factory.create(this.sourcePath, pathInRegion); 729 return result.getNormalizedPath(); 730 } 731 732 @Override 733 public String getString( NamespaceRegistry registry, 734 TextEncoder encoder ) { 735 StringBuilder sb = new StringBuilder(); 736 sb.append(this.getPathInRepository().getString(registry, encoder)); 737 sb.append(" => "); 738 sb.append(this.getPathInSource().getString(registry, encoder)); 739 if (this.getExceptionsToRule().size() != 0) { 740 for (Path exception : this.getExceptionsToRule()) { 741 sb.append(" $ "); 742 sb.append(exception.getString(registry, encoder)); 743 } 744 } 745 return sb.toString(); 746 } 747 748 /** 749 * {@inheritDoc} 750 * 751 * @see org.jboss.dna.connector.federation.Projection.Rule#getString(org.jboss.dna.common.text.TextEncoder) 752 */ 753 @Override 754 public String getString( TextEncoder encoder ) { 755 StringBuilder sb = new StringBuilder(); 756 sb.append(this.getPathInRepository().getString(encoder)); 757 sb.append(" => "); 758 sb.append(this.getPathInSource().getString(encoder)); 759 if (this.getExceptionsToRule().size() != 0) { 760 for (Path exception : this.getExceptionsToRule()) { 761 sb.append(" $ "); 762 sb.append(exception.getString(encoder)); 763 } 764 } 765 return sb.toString(); 766 } 767 768 /** 769 * {@inheritDoc} 770 * 771 * @see org.jboss.dna.connector.federation.Projection.Rule#getString() 772 */ 773 @Override 774 public String getString() { 775 return getString(Path.JSR283_ENCODER); 776 } 777 778 /** 779 * {@inheritDoc} 780 * 781 * @see java.lang.Object#hashCode() 782 */ 783 @Override 784 public int hashCode() { 785 return hc; 786 } 787 788 /** 789 * {@inheritDoc} 790 * 791 * @see java.lang.Object#equals(java.lang.Object) 792 */ 793 @Override 794 public boolean equals( Object obj ) { 795 if (obj == this) return true; 796 if (obj instanceof PathRule) { 797 PathRule that = (PathRule)obj; 798 if (!this.getPathInRepository().equals(that.getPathInRepository())) return false; 799 if (!this.getPathInSource().equals(that.getPathInSource())) return false; 800 if (!this.getExceptionsToRule().equals(that.getExceptionsToRule())) return false; 801 return true; 802 } 803 return false; 804 } 805 806 /** 807 * {@inheritDoc} 808 * 809 * @see java.lang.Comparable#compareTo(java.lang.Object) 810 */ 811 public int compareTo( Rule other ) { 812 if (other == this) return 0; 813 if (other instanceof PathRule) { 814 PathRule that = (PathRule)other; 815 int diff = this.getPathInRepository().compareTo(that.getPathInRepository()); 816 if (diff != 0) return diff; 817 diff = this.getPathInSource().compareTo(that.getPathInSource()); 818 if (diff != 0) return diff; 819 Iterator<Path> thisIter = this.getExceptionsToRule().iterator(); 820 Iterator<Path> thatIter = that.getExceptionsToRule().iterator(); 821 while (thisIter.hasNext() && thatIter.hasNext()) { 822 diff = thisIter.next().compareTo(thatIter.next()); 823 if (diff != 0) return diff; 824 } 825 if (thisIter.hasNext()) return 1; 826 if (thatIter.hasNext()) return -1; 827 return 0; 828 } 829 return other.getClass().getName().compareTo(this.getClass().getName()); 830 } 831 832 /** 833 * {@inheritDoc} 834 * 835 * @see java.lang.Object#toString() 836 */ 837 @Override 838 public String toString() { 839 return getString(); 840 } 841 } 842 }