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