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