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