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