001 /* 002 * Copyright 2004-2005 Stephen J. McConnell. 003 * Copyright 2004 Niclas Hedhman. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. 015 * 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 package net.dpml.transit; 021 022 import java.io.Serializable; 023 import java.net.URLStreamHandler; 024 import java.net.MalformedURLException; 025 import java.net.URI; 026 import java.net.URISyntaxException; 027 import java.net.URL; 028 029 /** 030 * A utility class the handles validation of <code>artifact</code> style uri 031 * strings. 032 * 033 * @author <a href="http://www.dpml.net">Digital Product Management Library</a> 034 * @version 2.1.1 035 */ 036 public final class Artifact implements Serializable, Comparable 037 { 038 /** 039 * Constant scheme name for the artifact protocol. 040 */ 041 public static final String ARTIFACT = "artifact"; 042 043 /** 044 * Constant scheme name for the link protocol. 045 */ 046 public static final String LINK = "link"; 047 048 /** 049 * Constant scheme name for the local protocol. 050 */ 051 public static final String LOCAL = "local"; 052 053 /** 054 * Constant scheme name for the configuration protocol. 055 */ 056 public static final String CONFIGURATION = "configuration"; 057 058 static final long serialVersionUID = 2L; 059 060 // ------------------------------------------------------------------------ 061 // static 062 // ------------------------------------------------------------------------ 063 064 /** 065 * Creation of a new artifact instance using a supplied uri specification. 066 * An artifact uri contains the protocol identifier, a type, a group 067 * designator, a name, and an optional version identifier. 068 * <p>The following represent valid artifact uri examples:</p> 069 * 070 * <ul> 071 * <li>artifact:jar:dpml/transit/dpml-transit-main#1234</li> 072 * <li>artifact:jar:dpml/transit/dpml-transit-main</li> 073 * <li>link:jar:dpml/transit/dpml-transit-main#1.0</li> 074 * </ul> 075 * 076 * <p> 077 * If there is a internal reference identifier which is marked by the 078 * exclamation mark followed by slash (!/) it will be stripped. The 079 * version part can be either before or after such identifier. Example; 080 * <pre> 081 * artifact:war:jmx-html/jmx-html#1.3!/images/abc.png 082 * artifact:war:jmx-html/jmx-html!/images/abc.png#1.3 083 * </pre> 084 * The above uris will both be referencing 085 * <code>artifact:war:jmx-html/jmx-html#1.3</code> 086 * </p> 087 * @param uri the artifact uri 088 * @return the new artifact 089 * @exception java.net.URISyntaxException if the supplied uri is not valid. 090 * @exception UnsupportedSchemeException if the URI does not have "artifact" 091 * or "link" as its <strong>scheme</strong>. 092 * @exception NullPointerException if the supplied uri argument is null 093 */ 094 public static final Artifact createArtifact( String uri ) 095 throws URISyntaxException, UnsupportedSchemeException, NullPointerException 096 { 097 if( null == uri ) 098 { 099 throw new NullPointerException( "uri" ); 100 } 101 int asterix = uri.indexOf( "!" ); 102 if( asterix == -1 ) 103 { 104 return createArtifact( new URI( uri ) ); 105 } 106 else 107 { 108 String path = uri.substring( 0, asterix ); 109 int versionPos = uri.indexOf( "#" ); 110 if( versionPos < asterix ) 111 { 112 return createArtifact( path ); 113 } 114 else 115 { 116 path = path + uri.substring( versionPos ); 117 return createArtifact( path ); 118 } 119 } 120 } 121 122 /** 123 * Creation of a new artifact instance using a supplied uri specification. An 124 * artifact uri contains the protocol identifier, an optional type, a group 125 * designator, a name, and an optional version identifier. 126 * <p>The following represent valid artifact uri examples:</p> 127 * 128 * <ul> 129 * <li>artifact:jar:metro/cache/dpml-cache-main#1.0.0</li> 130 * <li>artifact:metro/cache/dpml-cache-main#1.0.0</li> 131 * <li>artifact:metro/cache/dpml-cache-main</li> 132 * </ul> 133 * 134 * @param uri the artifact uri 135 * @return the new artifact 136 * @exception UnsupportedSchemeException if the URI does not have "artifact" 137 * or "link" as its <strong>scheme</strong>. 138 * @exception NullPointerException if the supplied uri argument is null 139 */ 140 public static final Artifact createArtifact( URI uri ) 141 throws UnsupportedSchemeException, NullPointerException 142 { 143 if( null == uri ) 144 { 145 throw new NullPointerException( "uri" ); 146 } 147 String scheme = uri.getScheme(); 148 if( null == scheme ) 149 { 150 final String error = 151 "URI does not declare a scheme: " + uri; 152 throw new UnsupportedSchemeException( error ); 153 } 154 else 155 { 156 return new Artifact( uri ); 157 } 158 } 159 160 /** 161 * Creation of a new artifact instance using a supplied group, name, 162 * version and type arguments. 163 * 164 * @param group the artifact group identifier 165 * @param name the artifact name 166 * @param version the version 167 * @param type the type 168 * @return the new artifact 169 * @exception NullPointerException if any of the <code>group</code>, 170 * <code>name</code> or <code>type</code> arguments are 171 * <code>null</code>. 172 */ 173 public static Artifact createArtifact( 174 String group, String name, String version, String type ) 175 throws NullPointerException 176 { 177 return createArtifact( ARTIFACT, group, name, version, type ); 178 } 179 180 /** 181 * Creation of a new artifact instance using a supplied group, name, 182 * version and type arguments. 183 * 184 * @param scheme the artifact scheme 185 * @param group the artifact group identifier 186 * @param name the artifact name 187 * @param version the version 188 * @param type the type 189 * @return the new artifact 190 * @exception NullPointerException if any of the <code>group</code>, 191 * <code>name</code> or <code>type</code> arguments are 192 * <code>null</code>. 193 */ 194 public static Artifact createArtifact( 195 String scheme, String group, String name, String version, String type ) 196 throws NullPointerException 197 { 198 if( name == null ) 199 { 200 throw new NullPointerException( "name" ); 201 } 202 if( type == null ) 203 { 204 throw new NullPointerException( "type" ); 205 } 206 if( scheme == null ) 207 { 208 throw new NullPointerException( "scheme" ); 209 } 210 String composite = buildComposite( scheme, group, name, version, type ); 211 try 212 { 213 URI uri = new URI( composite ); 214 return new Artifact( uri ); 215 } 216 catch( URISyntaxException e ) 217 { 218 // Can not happen. 219 final String error = 220 "An internal error has occurred. " 221 + "The following URI could not be constructed: " + composite; 222 throw new TransitRuntimeException( error ); 223 } 224 } 225 226 private static String buildComposite( 227 final String scheme, final String group, final String name, 228 final String version, final String type ) 229 { 230 StringBuffer buffer = new StringBuffer(); 231 buffer.append( scheme ); 232 buffer.append( ":" ); 233 buffer.append( type ); 234 buffer.append( ":" ); 235 if( null != group ) 236 { 237 buffer.append( group ); 238 buffer.append( "/" ); 239 } 240 buffer.append( name ); 241 if( null != version ) 242 { 243 buffer.append( "#" ); 244 buffer.append( version ); 245 } 246 String spec = buffer.toString(); 247 return spec; 248 } 249 250 /** 251 * Construct a new URL form a given URI. If the URI is a Transit URI the 252 * returned URL will be associated with the appropriate handler. 253 * @param uri the uri to convert 254 * @return the converted url 255 * @exception MalformedURLException if the url could not be created 256 */ 257 public static URL toURL( URI uri ) throws MalformedURLException 258 { 259 try 260 { 261 Artifact artifact = Artifact.createArtifact( uri ); 262 return artifact.toURL(); 263 } 264 catch( UnsupportedSchemeException e ) 265 { 266 } 267 catch( IllegalArgumentException e ) 268 { 269 } 270 271 try 272 { 273 return uri.toURL(); 274 } 275 catch( IllegalArgumentException iae ) 276 { 277 throw new InvalidArtifactException( iae.getMessage() ); 278 } 279 280 catch( MalformedURLException mue ) 281 { 282 throw mue; 283 } 284 catch( Throwable t ) 285 { 286 final String error = 287 "Unexpected error while attempting to convert a uri to a url." 288 + "\n URI: " 289 + uri; 290 throw new TransitRuntimeException( error, t ); 291 } 292 } 293 294 /** 295 * Test if the supplied uri is from the artifact family. Specificially 296 * the test validates that the supplied uri has a scheme corresponding to 297 * 'artifact', link', or 'local'. 298 * @param uri the uri to check 299 * @return true if thie uri is artifact based 300 */ 301 public static boolean isRecognized( URI uri ) 302 { 303 String scheme = uri.getScheme(); 304 if( ARTIFACT.equals( scheme ) ) 305 { 306 return true; 307 } 308 else if( LINK.equals( scheme ) ) 309 { 310 return true; 311 } 312 else if( CONFIGURATION.equals( scheme ) ) 313 { 314 return true; 315 } 316 else 317 { 318 return LOCAL.equals( scheme ); 319 } 320 } 321 322 // ------------------------------------------------------------------------ 323 // state 324 // ------------------------------------------------------------------------ 325 326 /** 327 * The artifact uri. 328 */ 329 private final URI m_uri; 330 331 /** 332 * The artifact group. 333 */ 334 private final String m_group; 335 336 /** 337 * The artifact name. 338 */ 339 private final String m_name; 340 341 /** 342 * The artifact type. 343 */ 344 private final String m_type; 345 346 // ------------------------------------------------------------------------ 347 // constructor 348 // ------------------------------------------------------------------------ 349 350 /** 351 * Creation of a new Artifact using a supplied uri. 352 * @param uri a uri of the form [scheme]:[type]:[group]/[name]#[version] 353 * where [scheme] is one of 'link', 'artifact' or 'local'. 354 */ 355 private Artifact( URI uri ) throws InvalidArtifactException 356 { 357 m_uri = reconstructURI( uri ); 358 String ssp = m_uri.getSchemeSpecificPart(); 359 360 if( ssp.indexOf( '?' ) > -1 ) 361 { 362 ssp = ssp.substring( 0, ssp.indexOf( '?' ) ); 363 } 364 365 if( ssp.indexOf( "//" ) > -1 366 || ssp.indexOf( ":/" ) > -1 367 || ssp.endsWith( "/" ) ) 368 { 369 final String error = 370 "Invalid character sequence in uri [" 371 + uri + "]."; 372 throw new InvalidArtifactException( error ); 373 } 374 375 int typeIndex = ssp.indexOf( ':' ); 376 if( typeIndex > -1 ) 377 { 378 String type = ssp.substring( 0, typeIndex ); 379 m_type = type; 380 ssp = ssp.substring( typeIndex + 1 ); 381 } 382 else 383 { 384 final String error = "Supplied artifact specification [" 385 + uri + "] does not contain a type."; 386 throw new InvalidArtifactException( error ); 387 } 388 389 // ssp now contains group and name 390 391 int groupIndex = ssp.lastIndexOf( '/' ); 392 if( groupIndex > -1 ) 393 { 394 String group = ssp.substring( 0, groupIndex ); 395 m_group = group; 396 m_name = ssp.substring( groupIndex + 1 ); 397 } 398 else 399 { 400 m_group = null; 401 m_name = ssp; 402 } 403 404 String ver = m_uri.getFragment(); 405 if( ver != null ) 406 { 407 if( ver.indexOf( '/' ) >= 0 408 || ver.indexOf( '%' ) >= 0 409 || ver.indexOf( '\\' ) >= 0 410 || ver.indexOf( '*' ) >= 0 411 || ver.indexOf( '!' ) >= 0 412 || ver.indexOf( '(' ) >= 0 413 || ver.indexOf( '@' ) >= 0 414 || ver.indexOf( ')' ) >= 0 415 || ver.indexOf( '+' ) >= 0 416 || ver.indexOf( '\'' ) >= 0 417 || ver.indexOf( '{' ) >= 0 418 || ver.indexOf( '}' ) >= 0 419 || ver.indexOf( '[' ) >= 0 420 || ver.indexOf( '}' ) >= 0 421 || ver.indexOf( '?' ) >= 0 422 || ver.indexOf( ',' ) >= 0 423 || ver.indexOf( '#' ) >= 0 424 || ver.indexOf( '=' ) >= 0 425 ) 426 { 427 final String error = 428 "Supplied artifact specification [" 429 + m_uri 430 + "] contains illegal characters in the Version part."; 431 throw new InvalidArtifactException( error ); 432 } 433 } 434 } 435 436 private URI reconstructURI( URI uri ) 437 { 438 String fragment = uri.getFragment(); 439 440 // if the fragment cointains a '?' character then reconstruct the uri 441 // such that the query is in the ssp 442 443 if( null != fragment ) 444 { 445 int n = fragment.indexOf( '?' ); 446 if( n > -1 ) 447 { 448 try 449 { 450 String version = fragment.substring( 0, n ); 451 String query = fragment.substring( n + 1 ); 452 String scheme = uri.getScheme(); 453 String ssp = uri.getSchemeSpecificPart(); 454 return new URI( scheme, ssp + "?" + query, version ); 455 } 456 catch( Exception e ) 457 { 458 throw new InvalidArtifactException( e.getMessage() ); 459 } 460 } 461 } 462 return uri; 463 464 /* 465 String ssp = uri.getSchemeSpecificPart(); 466 int n = ssp.indexOf( '?' ); 467 if( n > -1 ) 468 { 469 String body = ssp.substring( 0, n ); 470 String query = ssp.substring( n+1 ); 471 System.out.println( " SSP: " + ssp ); 472 System.out.println( " BODY: " + body ); 473 System.out.println( "QUERY: " + query ); 474 } 475 return uri; 476 */ 477 } 478 479 480 // ------------------------------------------------------------------------ 481 // public 482 // ------------------------------------------------------------------------ 483 484 /** 485 * Return the protocol for the artifact. 486 * 487 * @return the protocol scheme 488 */ 489 public final String getScheme() 490 { 491 return m_uri.getScheme(); 492 } 493 494 /** 495 * Return the group identifier for the artifact. The group identifier 496 * is composed of a sequence of named separated by the '/' character. 497 * 498 * @return the group identifier 499 */ 500 public final String getGroup() 501 { 502 return m_group; 503 } 504 505 /** 506 * Return the name of the artifact. 507 * 508 * @return the artifact name 509 */ 510 public final String getName() 511 { 512 return m_name; 513 } 514 515 /** 516 * Return the type of the artifact. 517 * 518 * @return the artifact type 519 */ 520 public final String getType() 521 { 522 return m_type; 523 } 524 525 /** 526 * Return the posssibly null version identifier. The value of the version 527 * is an opaque string. 528 * @return the artifact version 529 */ 530 public final String getVersion() 531 { 532 String ver = m_uri.getFragment(); 533 if( ver == null ) 534 { 535 return null; 536 } 537 else if( ver.length() == 0 ) 538 { 539 return null; 540 } 541 else 542 { 543 return ver; 544 } 545 } 546 547 /** 548 * Test if the artifact scheme is recognized. Specificially 549 * the test validates that the artifact scheme corresponding to 550 * 'artifact', link', or 'local'. 551 * @return true if the uri scheme is recognized 552 */ 553 public boolean isRecognized() 554 { 555 return isRecognized( m_uri ); 556 } 557 558 /** 559 * Create an artifact url backed by the repository. 560 * 561 * @return the artifact url 562 */ 563 public URL toURL() 564 { 565 String scheme = getScheme(); 566 if( ARTIFACT.equals( scheme ) ) 567 { 568 return toURL( new dpml.transit.artifact.Handler() ); 569 } 570 else if( LINK.equals( scheme ) ) 571 { 572 return toURL( new dpml.transit.link.Handler() ); 573 } 574 else if( LOCAL.equals( scheme ) ) 575 { 576 return toURL( new dpml.transit.local.Handler() ); 577 } 578 else if( CONFIGURATION.equals( scheme ) ) 579 { 580 return toURL( new dpml.transit.configuration.Handler() ); 581 } 582 else 583 { 584 final String error = 585 "URI scheme not recognized: " + m_uri; 586 throw new UnsupportedSchemeException( error ); 587 } 588 } 589 590 /** 591 * Create an artifact url backed by the repository. 592 * @param handler the protocol handler 593 * @return the artifact url 594 */ 595 public URL toURL( URLStreamHandler handler ) 596 { 597 try 598 { 599 return new URL( null, m_uri.toASCIIString(), handler ); 600 } 601 catch( MalformedURLException e ) 602 { 603 // Can not happen! 604 final String error = 605 "An artifact URI could not be converted to a URL [" 606 + m_uri 607 + "]."; 608 throw new TransitRuntimeException( error ); 609 } 610 } 611 612 /** 613 * Create an artifact url backed by the repository. 614 * 615 * @return the artifact url 616 */ 617 public URI toURI() 618 { 619 return m_uri; 620 } 621 622 // ------------------------------------------------------------------------ 623 // Comparable 624 // ------------------------------------------------------------------------ 625 626 /** 627 * Compare this artifact with another artifact. Artifact comparisom is 628 * based on a comparison of the string representation of the artifact with 629 * the string representation of the supplied object. 630 * 631 * @param object the object to compare with this instance 632 * @return the comparative order of the supplied object relative to this 633 * artifact 634 * @exception NullPointerException if the supplied object argument is null. 635 * @exception ClassCastException if the supplied object is not an Artifact. 636 */ 637 public int compareTo( Object object ) 638 throws NullPointerException, ClassCastException 639 { 640 if( object instanceof Artifact ) 641 { 642 String name = this.toString(); 643 return name.compareTo( object.toString() ); 644 } 645 else if( null == object ) 646 { 647 throw new NullPointerException( "object" ); 648 } 649 else 650 { 651 final String error = 652 "Object [" 653 + object.getClass().getName() 654 + "] does not implement [" 655 + this.getClass().getName() + "]."; 656 throw new ClassCastException( error ); 657 } 658 } 659 660 // ------------------------------------------------------------------------ 661 // Object 662 // ------------------------------------------------------------------------ 663 664 /** 665 * Return a string representation of the artifact. 666 * @return the artifact as a uri 667 */ 668 public String toString() 669 { 670 return m_uri.toString(); 671 } 672 673 /** 674 * Compare this artifact with the supplied object for equality. This method 675 * will return true if the supplied object is an Artifact and has an equal 676 * uri. 677 * 678 * @param other the object to compare with this instance 679 * @return TRUE if this artifact is equal to the supplied object 680 */ 681 public boolean equals( Object other ) 682 { 683 if( null == other ) 684 { 685 return false; 686 } 687 else if( this == other ) 688 { 689 return true; 690 } 691 else if( other instanceof Artifact ) 692 { 693 Artifact art = (Artifact) other; 694 return m_uri.equals( art.m_uri ); 695 } 696 else 697 { 698 return false; 699 } 700 } 701 702 /** 703 * Return the hashcode for the artifact. 704 * @return the hashcode value 705 */ 706 public int hashCode() 707 { 708 return m_uri.hashCode(); 709 } 710 } 711