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 025 import java.net.MalformedURLException; 026 import java.net.URI; 027 import java.net.URISyntaxException; 028 import java.net.URL; 029 030 /** 031 * A utility class the handles validation of <code>artifact</code> style uri 032 * strings. 033 * 034 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 035 * @version 1.0.3 036 */ 037 public final class Artifact implements Serializable, Comparable 038 { 039 /** 040 * Constant scheme name for the artifact protocol. 041 */ 042 public static final String ARTIFACT = "artifact"; 043 044 /** 045 * Constant scheme name for the link protocol. 046 */ 047 public static final String LINK = "link"; 048 049 /** 050 * Constant scheme name for the local protocol. 051 */ 052 public static final String LOCAL = "local"; 053 054 static final long serialVersionUID = 1L; 055 056 // ------------------------------------------------------------------------ 057 // static 058 // ------------------------------------------------------------------------ 059 060 /** 061 * Creation of a new artifact instance using a supplied uri specification. 062 * An artifact uri contains the protocol identifier, a type, a group 063 * designator, a name, and an optional version identifier. 064 * <p>The following represent valid artifact uri examples:</p> 065 * 066 * <ul> 067 * <li>artifact:jar:dpml/transit/dpml-transit-main#1234</li> 068 * <li>artifact:jar:dpml/transit/dpml-transit-main</li> 069 * <li>link:jar:dpml/transit/dpml-transit-main#1.0</li> 070 * </ul> 071 * 072 * <p> 073 * If there is a internal reference identifier which is marked by the 074 * exclamation mark followed by slash (!/) it will be stripped. The 075 * version part can be either before or after such identifier. Example; 076 * <pre> 077 * artifact:war:jmx-html/jmx-html#1.3!/images/abc.png 078 * artifact:war:jmx-html/jmx-html!/images/abc.png#1.3 079 * </pre> 080 * The above uris will both be referencing 081 * <code>artifact:war:jmx-html/jmx-html#1.3</code> 082 * </p> 083 * @param uri the artifact uri 084 * @return the new artifact 085 * @exception java.net.URISyntaxException if the supplied uri is not valid. 086 * @exception UnsupportedSchemeException if the URI does not have "artifact" 087 * or "link" as its <strong>scheme</strong>. 088 */ 089 public static final Artifact createArtifact( String uri ) 090 throws URISyntaxException, UnsupportedSchemeException 091 { 092 if( null == uri ) 093 { 094 throw new NullArgumentException( "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 */ 134 public static final Artifact createArtifact( URI uri ) 135 throws UnsupportedSchemeException 136 { 137 if( null == uri ) 138 { 139 throw new NullArgumentException( "uri" ); 140 } 141 String scheme = uri.getScheme(); 142 if( null == scheme ) 143 { 144 final String error = 145 "URI does not declare a scheme: " + uri; 146 throw new UnsupportedSchemeException( error ); 147 } 148 //if( !scheme.equals( ARTIFACT ) && !scheme.equals( LINK ) && !scheme.equals( LOCAL ) ) 149 //{ 150 // final String error = 151 // "URI contains a scheme that is not recognized: " + uri; 152 // throw new UnsupportedSchemeException( error ); 153 //} 154 return new Artifact( uri ); 155 } 156 157 /** 158 * Creation of a new artifact instance using a supplied group, name, 159 * version and type arguments. 160 * 161 * @param group the artifact group identifier 162 * @param name the artifact name 163 * @param version the version 164 * @param type the type 165 * @return the new artifact 166 * @exception NullArgumentException if any of the <code>group</code>, 167 * <code>name</code> or <code>type</code> arguments are 168 * <code>null</code>. 169 */ 170 public static Artifact createArtifact( String group, String name, String version, String type ) 171 throws NullArgumentException 172 { 173 return createArtifact( ARTIFACT, group, name, version, type ); 174 } 175 176 /** 177 * Creation of a new artifact instance using a supplied group, name, 178 * version and type arguments. 179 * 180 * @param scheme the artifact scheme 181 * @param group the artifact group identifier 182 * @param name the artifact name 183 * @param version the version 184 * @param type the type 185 * @return the new artifact 186 * @exception NullArgumentException if any of the <code>group</code>, 187 * <code>name</code> or <code>type</code> arguments are 188 * <code>null</code>. 189 */ 190 public static Artifact createArtifact( String scheme, String group, String name, String version, String type ) 191 throws NullArgumentException 192 { 193 if( name == null ) 194 { 195 throw new NullArgumentException( "name" ); 196 } 197 if( type == null ) 198 { 199 throw new NullArgumentException( "type" ); 200 } 201 if( scheme == null ) 202 { 203 throw new NullArgumentException( "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 != query ) 237 //{ 238 // buffer.append( "?" ); 239 // buffer.append( query ); 240 //} 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( 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 ) 347 throws IllegalArgumentException 348 { 349 m_uri = uri; 350 String ssp = uri.getSchemeSpecificPart(); 351 352 if( ssp.indexOf( "//" ) > -1 353 || ssp.indexOf( ":/" ) > -1 354 || ssp.endsWith( "/" ) ) 355 { 356 final String error = 357 "Invalid character sequence in uri [" 358 + uri + "]."; 359 throw new IllegalArgumentException( error ); 360 } 361 362 int typeIndex = ssp.indexOf( ':' ); 363 if( typeIndex > -1 ) 364 { 365 String type = ssp.substring( 0, typeIndex ); 366 m_type = type; 367 ssp = ssp.substring( typeIndex + 1 ); 368 } 369 else 370 { 371 final String error = "Supplied artifact specification [" 372 + uri + "] does not contain a type."; 373 throw new IllegalArgumentException( error ); 374 } 375 376 // ssp now contains group, name and version 377 378 int groupIndex = ssp.lastIndexOf( '/' ); 379 if( groupIndex > -1 ) 380 { 381 String group = ssp.substring( 0, groupIndex ); 382 m_group = group; 383 m_name = ssp.substring( groupIndex + 1 ); 384 } 385 else 386 { 387 m_group = null; 388 m_name = ssp; 389 } 390 391 String ver = uri.getFragment(); 392 if( ver != null ) 393 { 394 if( ver.indexOf( '/' ) >= 0 395 || ver.indexOf( '%' ) >= 0 396 || ver.indexOf( '\\' ) >= 0 397 || ver.indexOf( '*' ) >= 0 398 || 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 ) 413 { 414 final String error = 415 "Supplied artifact specification [" 416 + uri 417 + "] contains illegal characters in the Version part."; 418 throw new IllegalArgumentException( error ); 419 } 420 } 421 } 422 423 // ------------------------------------------------------------------------ 424 // public 425 // ------------------------------------------------------------------------ 426 427 /** 428 * Return the protocol for the artifact. 429 * 430 * @return the protocol scheme 431 */ 432 public final String getScheme() 433 { 434 return m_uri.getScheme(); 435 } 436 437 /** 438 * Return the group identifier for the artifact. The group identifier 439 * is composed of a sequence of named separated by the '/' character. 440 * 441 * @return the group identifier 442 */ 443 public final String getGroup() 444 { 445 return m_group; 446 } 447 448 /** 449 * Return the name of the artifact. 450 * 451 * @return the artifact name 452 */ 453 public final String getName() 454 { 455 return m_name; 456 } 457 458 /** 459 * Return the type of the artifact. 460 * 461 * @return the artifact type 462 */ 463 public final String getType() 464 { 465 return m_type; 466 } 467 468 /** 469 * Return the posssibly null version identifier. The value of the version 470 * is an opaque string. 471 * @return the artifact version 472 */ 473 public final String getVersion() 474 { 475 String ver = m_uri.getFragment(); 476 if( ver == null ) 477 { 478 return null; 479 } 480 else if( ver.length() == 0 ) 481 { 482 return null; 483 } 484 else 485 { 486 return ver; 487 } 488 } 489 490 /** 491 * Test if the artifact scheme is recognized. Specificially 492 * the test validates that the artifact scheme corresponding to 493 * 'artifact', link', or 'local'. 494 * @return true if the uri scheme is recognized 495 */ 496 public boolean isRecognized() 497 { 498 return isRecognized( m_uri ); 499 } 500 501 /** 502 * Create an artifact url backed by the repository. 503 * 504 * @return the artifact url 505 */ 506 public URL toURL() 507 { 508 String scheme = getScheme(); 509 if( ARTIFACT.equals( scheme ) ) 510 { 511 return toURL( new net.dpml.transit.artifact.Handler() ); 512 } 513 else if( LINK.equals( scheme ) ) 514 { 515 return toURL( new net.dpml.transit.link.Handler() ); 516 } 517 else if( LOCAL.equals( scheme ) ) 518 { 519 return toURL( new net.dpml.transit.local.Handler() ); 520 } 521 else 522 { 523 final String error = 524 "URI scheme not recognized: " + m_uri; 525 throw new UnsupportedSchemeException( error ); 526 } 527 } 528 529 /** 530 * Create an artifact url backed by the repository. 531 * @param handler the protocol handler 532 * @return the artifact url 533 */ 534 public URL toURL( URLStreamHandler handler ) 535 { 536 try 537 { 538 return new URL( null, m_uri.toASCIIString(), handler ); 539 } 540 catch( MalformedURLException e ) 541 { 542 // Can not happen! 543 final String error = 544 "An artifact URI could not be converted to a URL [" 545 + m_uri 546 + "]."; 547 throw new TransitRuntimeException( error ); 548 } 549 } 550 551 /** 552 * Create an artifact url backed by the repository. 553 * 554 * @return the artifact url 555 */ 556 public URI toURI() 557 { 558 return m_uri; 559 } 560 561 // ------------------------------------------------------------------------ 562 // Comparable 563 // ------------------------------------------------------------------------ 564 565 /** 566 * Compare this artifact with another artifact. Artifact comparisom is 567 * based on a comparison of the string representation of the artifact with 568 * the string representation of the supplied object. 569 * 570 * @param object the object to compare with this instance 571 * @return the comparative order of the supplied object relative to this 572 * artifact 573 * @exception NullArgumentException if the supplied object argument is null. 574 * @exception ClassCastException if the supplied object is not an Artifact. 575 */ 576 public int compareTo( Object object ) 577 throws NullArgumentException, ClassCastException 578 { 579 if( object instanceof Artifact ) 580 { 581 String name = this.toString(); 582 return name.compareTo( object.toString() ); 583 } 584 else if( null == object ) 585 { 586 throw new NullArgumentException( "object" ); 587 } 588 else 589 { 590 final String error = 591 "Object [" 592 + object.getClass().getName() 593 + "] does not implement [" 594 + this.getClass().getName() + "]."; 595 throw new ClassCastException( error ); 596 } 597 } 598 599 // ------------------------------------------------------------------------ 600 // Object 601 // ------------------------------------------------------------------------ 602 603 /** 604 * Return a string representation of the artifact. 605 * @return the artifact as a uri 606 */ 607 public String toString() 608 { 609 return m_uri.toString(); 610 } 611 612 /** 613 * Compare this artifact with the supplied object for equality. This method 614 * will return true if the supplied object is an Artifact and has an equal 615 * uri. 616 * 617 * @param other the object to compare with this instance 618 * @return TRUE if this artifact is equal to the supplied object 619 */ 620 public boolean equals( Object other ) 621 { 622 if( null == other ) 623 { 624 return false; 625 } 626 else if( this == other ) 627 { 628 return true; 629 } 630 else if( other instanceof Artifact ) 631 { 632 Artifact art = (Artifact) other; 633 return m_uri.equals( art.m_uri ); 634 } 635 else 636 { 637 return false; 638 } 639 } 640 641 /** 642 * Return the hashcode for the artifact. 643 * @return the hashcode value 644 */ 645 public int hashCode() 646 { 647 return m_uri.hashCode(); 648 } 649 } 650