Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Sievers2012-11-12 15:29:06 -0500
committerJan Sievers2012-11-12 15:33:31 -0500
commit98c0a890ba9798bec3872cdfcf78b8f62da236cb (patch)
tree1516312f607fbe9e3d461934da7449965d50991b
downloadorg.eclipse.tycho.nexus-98c0a890ba9798bec3872cdfcf78b8f62da236cb.tar.gz
org.eclipse.tycho.nexus-98c0a890ba9798bec3872cdfcf78b8f62da236cb.tar.xz
org.eclipse.tycho.nexus-98c0a890ba9798bec3872cdfcf78b8f62da236cb.zip
CQ6851 initial contribution
-rw-r--r--.gitignore6
-rw-r--r--.settings/org.eclipse.jdt.core.prefs282
-rw-r--r--.settings/org.eclipse.jdt.ui.prefs56
-rw-r--r--nexus_examples.txt32
-rw-r--r--pom.xml172
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepository.java374
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/RequestTimeTrace.java31
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepository.java21
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfiguration.java31
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfigurator.java36
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryHtmlCustomizer.java41
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryResourceBundle.java35
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplate.java59
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplateProvider.java50
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ConversionResult.java140
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestReleaseRequest.java57
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionRequest.java93
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ParsedRequest.java115
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLock.java91
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverter.java104
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/SnapshotRequest.java68
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnchangedRequest.java27
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCache.java155
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/Util.java35
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItem.java90
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItem.java371
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItem.java51
-rw-r--r--src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageFileItem.java66
-rw-r--r--src/main/resources/epl-v10.html328
-rw-r--r--src/main/resources/resources/js/unzip-repo.js46
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstHostedRepositoryTest.java95
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstProxyRepositoryTest.java39
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryTest.java200
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionConverterTest.java172
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLockTest.java128
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverterTest.java330
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCacheTest.java295
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItemTest.java103
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItemTest.java180
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItemTest.java90
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/FSLocalRepositoryStorageMock.java48
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/RepositoryMock.java205
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/TestUtil.java85
-rw-r--r--src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/UnzipRepositoryMock.java71
-rw-r--r--src/test/resources/0.6.1-SNAPSHOT/maven-metadata.xml37
-rw-r--r--src/test/resources/0.7.1-SNAPSHOT/maven-metadata.xml37
-rw-r--r--src/test/resources/emptyArchive.zip-unzipped/create.txt0
-rw-r--r--src/test/resources/masterRepo/dir/a.txt1
-rw-r--r--src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/subdir/a.txt1
-rw-r--r--src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/test.txt1
-rw-r--r--src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/test.txt1
-rw-r--r--src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/subdir/a.txt1
-rw-r--r--src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/test.txt1
-rw-r--r--src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/test.txt1
-rw-r--r--src/test/resources/maven-metadata-latest-is-not-a-snapshot.xml23
-rw-r--r--src/test/resources/maven-metadata.xml14
-rw-r--r--src/test/resources/missingSnapshot-maven-metadata.xml10
-rw-r--r--src/test/resources/missingVersioning-maven-metadata.xml7
-rw-r--r--src/test/resources/outer-maven-metadata-without-release.xml11
-rw-r--r--src/test/resources/outer-maven-metadata.xml24
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/subdir/a.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/subdir/a.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/subdir/a.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/subdir/a.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/test.txt1
-rw-r--r--src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/maven-metadata.xml26
73 files changed, 5312 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5d385ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+/target
+.project
+.classpath
+.settings/*
+!/.settings/org.eclipse.jdt.core.prefs
+!/.settings/org.eclipse.jdt.ui.prefs \ No newline at end of file
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..75b211d
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,282 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=false
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=100
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..8a73ade
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,56 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_Tycho
+formatter_settings_version=12
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=false
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/nexus_examples.txt b/nexus_examples.txt
new file mode 100644
index 0000000..8bbfa43
--- /dev/null
+++ b/nexus_examples.txt
@@ -0,0 +1,32 @@
+example:
+
+master repository contains
+/dir/
+/dir/a.txt
+/dir/subdir/
+/dir/subdir/archive.zip
+ |
+ -> a.txt
+ -> dir/
+ -> dir/b.txt
+
+
+shadow repository:
+/dir/ -> master:/dir/
+/dir/a.txt -> master:/dir/a.txt
+/dir/subdir/ -> master:/dir/subdir/
+/dir/subdir/archive.zip -> master:/dir/subdir/archive.zip
+/dir/subdir/archive.zip/ -> collection
+ |
+ -> a.txt
+ -> dir/
+/dir/subdir/archive.zip/a.txt -> extracted content of file a.txt from /dir/subdir/archive.zip
+/dir/subdir/archive.zip/dir -> collection
+ |
+ -> b.txt
+/dir/subdir/archive.zip/dir/b.txt -> extracted content of file dir/b.txt from /dir/subdir/archive.zip
+/dir/subdir/archive.zip/x.txt -> NOT FOUND
+/dir/subdir/archive.zip/xDir -> NOT FOUND
+/x.txt -> NOT FOUND
+/xDir -> NOT FOUND
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c7f1312
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright (c) 2010, 2012 SAP AG and others.
+ - All rights reserved. This program and the accompanying materials
+ - are made available under the terms of the Eclipse Public License v1.0
+ - which accompanies this distribution, and is available at
+ - http://www.eclipse.org/legal/epl-v10.html
+ -
+ - Contributors:
+ - SAP AG - initial API and implementation
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.eclipse.tycho.nexus</groupId>
+ <artifactId>unzip-repository-plugin</artifactId>
+ <version>0.10.0-SNAPSHOT</version>
+
+ <inceptionYear>2010</inceptionYear>
+ <url>http://www.eclipse.org/tycho/</url>
+ <ciManagement>
+ <system>hudson</system>
+ <url>https://hudson.eclipse.org/hudson</url>
+ </ciManagement>
+ <licenses>
+ <license>
+ <name>Eclipse Public License</name>
+ <url>http://www.eclipse.org/legal/epl-v10.html</url>
+ </license>
+ </licenses>
+ <organization>
+ <name>Eclipse Foundation</name>
+ <url>http://www.eclipse.org/</url>
+ </organization>
+ <issueManagement>
+ <system>Bugzilla</system>
+ <url>https://bugs.eclipse.org/bugs/buglist.cgi?product=Tycho</url>
+ </issueManagement>
+
+ <packaging>nexus-plugin</packaging>
+
+ <name>Unzip Repository Nexus Plugin (Incubation)</name>
+ <description>
+ The Unzip Repository is a Nexus repository type that shadows the build results of a standard
+ Maven 2 repository and allows to browse into zip and jar artifacts.
+ The use case for Tycho is to to offer p2 repositories that were build with Tycho and deployed
+ to Nexus as zip so that other Tycho projects can reference them.
+ </description>
+
+ <properties>
+ <nexus-version>2.0.3</nexus-version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- plexus-utils are provided by Nexus -> mark as "provided", otherwise they will be
+ packaged into the plugin archive and this can lead to runtime version mismatches! -->
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ <version>2.0.5</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ <version>3.0.4</version>
+ </dependency>
+ <!-- Main plugin API, pulling in what is needed -->
+ <dependency>
+ <groupId>org.sonatype.nexus</groupId>
+ <artifactId>nexus-plugin-api</artifactId>
+ <version>${nexus-version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- This dependency is only needed if you want to include REST Resources -->
+ <dependency>
+ <groupId>org.sonatype.nexus</groupId>
+ <artifactId>nexus-rest-api</artifactId>
+ <version>${nexus-version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Basic testing -->
+ <dependency>
+ <groupId>org.sonatype.nexus</groupId>
+ <artifactId>nexus-plugin-test-api</artifactId>
+ <version>${nexus-version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>3.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Needed for custom packaging -->
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>app-lifecycle-maven-plugin</artifactId>
+ <version>1.4</version>
+ <extensions>true</extensions>
+ <dependencies>
+ <dependency>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>app-lifecycle-nexus</artifactId>
+ <version>1.4</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.1</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Generate components.xml to use Plexus style of dependency injection -->
+ <plugin>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-metadata</artifactId>
+ <version>1.5.5</version>
+ <executions>
+ <execution>
+ <id>process-classes</id>
+ <goals>
+ <goal>generate-metadata</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>process-test-classes</id>
+ <goals>
+ <goal>generate-test-metadata</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+
+</project>
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepository.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepository.java
new file mode 100644
index 0000000..5f4ed9b
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepository.java
@@ -0,0 +1,374 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import java.io.File;
+import java.util.Arrays;
+
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.eclipse.tycho.nexus.internal.plugin.cache.ConversionResult;
+import org.eclipse.tycho.nexus.internal.plugin.cache.RequestPathConverter;
+import org.eclipse.tycho.nexus.internal.plugin.cache.UnzipCache;
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZipAwareStorageCollectionItem;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZippedItem;
+import org.sonatype.nexus.configuration.Configurator;
+import org.sonatype.nexus.configuration.model.CRepository;
+import org.sonatype.nexus.configuration.model.CRepositoryExternalConfigurationHolderFactory;
+import org.sonatype.nexus.plugins.RepositoryType;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.NoSuchRepositoryException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.events.NexusStartedEvent;
+import org.sonatype.nexus.proxy.events.RepositoryRegistryEventAdd;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.proxy.item.StorageLinkItem;
+import org.sonatype.nexus.proxy.registry.ContentClass;
+import org.sonatype.nexus.proxy.repository.AbstractShadowRepository;
+import org.sonatype.nexus.proxy.repository.DefaultRepositoryKind;
+import org.sonatype.nexus.proxy.repository.IncompatibleMasterRepositoryException;
+import org.sonatype.nexus.proxy.repository.LocalStatus;
+import org.sonatype.nexus.proxy.repository.Repository;
+import org.sonatype.nexus.proxy.repository.RepositoryKind;
+import org.sonatype.nexus.proxy.repository.ShadowRepository;
+import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
+import org.sonatype.nexus.proxy.storage.local.LocalRepositoryStorage;
+import org.sonatype.nexus.proxy.storage.local.fs.DefaultFSLocalRepositoryStorage;
+import org.sonatype.plexus.appevents.Event;
+
+/**
+ * Shadow repository that allows to directly browse and access the content of archive files (e.g.
+ * zip, jar files) that are stored in the master repository. In the shadow repository all files and
+ * folders of the master repository can be accessed in the same way as in the master repository. The
+ * additional functionality is that <br>
+ * 1. archives can be browsed under their path + trailing slash.<br>
+ * 2. files and folders in an archive can be browsed under the path of the archive + slash + path
+ * within archive
+ */
+/*
+ * Within Nexus 1.6 the plugin worked without the annotation. Within Nexus 1.7 is still working, but
+ * a warning is logged that the plugin misses to register it's type correctly.
+ *
+ * Reference: Nexus Book - Chapter 18. Developing Nexus Plugins -
+ * http://www.sonatype.com/books/nexus-book/reference/plugdev.html The documentation describes to
+ * tag the repository interface (UnzipRepository) with the RepositoryType annotation. This does not
+ * work as described. (Neither with 1.6, nor with 1.7) Tagging the implementation class works in
+ * Nexus 1.7 as described. (incl. the promised repository appearance in .../nexus/content..) Within
+ * Nexus 1.6 an exception will be logged during startup, but the plugin still works functional
+ * correct.
+ */
+@RepositoryType(pathPrefix = "unzip")
+@Component(role = UnzipRepository.class, hint = DefaultUnzipRepository.REPOSITORY_HINT, instantiationStrategy = "per-lookup", description = "Unzip Repository")
+public class DefaultUnzipRepository extends AbstractShadowRepository implements UnzipRepository {
+ static final String REPOSITORY_HINT = "org.eclipse.tycho.nexus.plugin.DefaultUnzipRepository";
+
+ @Requirement
+ private UnzipRepositoryConfigurator configurator;
+
+ @Requirement(hint = "maven2")
+ private ContentClass contentClass;
+
+ @Requirement(hint = "maven2")
+ private ContentClass masterContentClass;
+
+ private RepositoryKind repositoryKind;
+
+ private boolean isNexusStarted = false;
+
+ private boolean isMasterAvailable = false;
+
+ private UnzipCache cache;
+
+ @Override
+ protected Configurator getConfigurator() {
+ return configurator;
+ }
+
+ @Override
+ public ContentClass getRepositoryContentClass() {
+ return contentClass;
+ }
+
+ @Override
+ public ContentClass getMasterRepositoryContentClass() {
+ return masterContentClass;
+ }
+
+ @Override
+ public RepositoryKind getRepositoryKind() {
+ if (repositoryKind == null) {
+ repositoryKind = new DefaultRepositoryKind(UnzipRepository.class,
+ Arrays.asList(new Class<?>[] { ShadowRepository.class }));
+ }
+
+ return repositoryKind;
+ }
+
+ @Override
+ protected CRepositoryExternalConfigurationHolderFactory<?> getExternalConfigurationHolderFactory() {
+ return new CRepositoryExternalConfigurationHolderFactory<UnzipRepositoryConfiguration>() {
+ @Override
+ public UnzipRepositoryConfiguration createExternalConfigurationHolder(final CRepository config) {
+ return new UnzipRepositoryConfiguration((Xpp3Dom) config.getExternalConfiguration());
+ }
+ };
+ }
+
+ /*
+ * Need to overwrite setMasterRepositoryId(String id) getMasterRepository() getLocalStatus()
+ * onEvent(Event<?> evt) in order to allow Unzip repositories in front of repository groups.
+ * During Nexus startup repository creation repository groups are explicitly created AFTER all
+ * other repositories. see
+ * org.sonatype.nexus.configuration.application.DefaultNexusConfiguration#createRepositories()
+ * As a result at creation time of this repository the master is not yet available in case the
+ * master is a group. After Nexus startup is complete all methods behave like default. In the
+ * meantime the master repository id will be stored without availability, compatibility checks
+ * avoiding error logs.
+ */
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setMasterRepositoryId(final String id) throws NoSuchRepositoryException,
+ IncompatibleMasterRepositoryException {
+ try {
+ super.setMasterRepositoryId(id);
+ isMasterAvailable = true;
+ } catch (final NoSuchRepositoryException e) {
+ if (isNexusStarted) {
+ throw e;
+ } else {
+ // NoSuchRepositoryException yet. Just remember the id
+ getExternalConfiguration(true).setMasterRepositoryId(id);
+ }
+ }
+ }
+
+ // see comment at setMasterRepositoryId(String id)
+ @Override
+ public void setMasterRepository(final Repository masterRepository) throws IncompatibleMasterRepositoryException {
+ isMasterAvailable = true;
+ super.setMasterRepository(masterRepository);
+ }
+
+ // see comment at setMasterRepositoryId(String id)
+ @Override
+ public Repository getMasterRepository() {
+ if (isNexusStarted || isMasterAvailable) {
+ return super.getMasterRepository();
+ } else {
+ return null;
+ }
+ }
+
+ // see comment at setMasterRepositoryId(String id)
+ @Override
+ public LocalStatus getLocalStatus() {
+ if (isNexusStarted || isMasterAvailable) {
+ return super.getLocalStatus();
+ } else {
+ if (getCurrentConfiguration(false).getLocalStatus() == null) {
+ return LocalStatus.OUT_OF_SERVICE;
+ }
+ return LocalStatus.valueOf(getCurrentConfiguration(false).getLocalStatus());
+ }
+ }
+
+ // see comment at setMasterRepositoryId(String id)
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onEvent(final Event<?> evt) {
+ if (evt instanceof RepositoryRegistryEventAdd) {
+ final RepositoryRegistryEventAdd repoAddEvent = (RepositoryRegistryEventAdd) evt;
+ if (repoAddEvent.getRepository().getId().equals(getMasterRepositoryId())) {
+ try {
+ setMasterRepositoryId(repoAddEvent.getRepository().getId());
+ } catch (final NoSuchRepositoryException e) {
+ getLogger().warn("Master Repository not available", e);
+ } catch (final IncompatibleMasterRepositoryException e) {
+ getLogger().warn("Master Repository incompatible", e);
+ }
+ }
+ } else if (evt instanceof NexusStartedEvent) {
+ isNexusStarted = true;
+ }
+ super.onEvent(evt);
+ }
+
+ /**
+ * Retrieves an item from the master repository. The method includes some workaround logic to
+ * access folders (some folders are only accessible if the request URL end with a double slash).
+ * In addition this method also finds file items if the request URL ends with a slash.
+ *
+ * @param request
+ * the request that defines which item be retrieved
+ * @return the item from the master repository
+ * @throws ItemNotFoundException
+ * is thrown if there is no item under the specified request path in the master
+ * repository
+ * @throws LocalStorageException
+ */
+ StorageItem retrieveItemFromMaster(final String requestPath) throws ItemNotFoundException, LocalStorageException {
+ try {
+ final ResourceStoreRequest request = new ResourceStoreRequest(requestPath);
+ return doRetrieveItemFromMaster(request);
+ } catch (final IllegalOperationException e) {
+ throw new LocalStorageException(e);
+ } catch (@SuppressWarnings("deprecation") final org.sonatype.nexus.proxy.StorageException e) {
+ throw new LocalStorageException(e);
+ }
+ }
+
+ @Override
+ protected StorageItem doRetrieveItem(final ResourceStoreRequest request) throws IllegalOperationException,
+ ItemNotFoundException, LocalStorageException {
+
+ final RequestTimeTrace timeTrace = new RequestTimeTrace(request.getRequestPath());
+
+ final ConversionResult conversionResult = RequestPathConverter.convert(getMasterRepository(), request,
+ isUseVirtualVersion());
+
+ if (conversionResult.isPathConverted()) {
+ getLogger().debug(
+ "Resolved dynamic request: " + request.getRequestUrl() + ". Resolved request path: "
+ + conversionResult.getConvertedPath());
+ }
+
+ // First check for zip content to avoid unnecessary and expensive backend calls for zip content requests.
+ // Due to naming conventions zippedItem creation will normally only call the backend in case it is a zip content request.
+ // a) path does not point to zip content (-> null)
+ // b) a path to a file/folder inside a zip file (-> ZippedItem is created and returned)
+ // c) a non-existing path under an existing zip file (-> retrieving ZippedItem fails with ItemNotFoundException)
+ final ZippedItem zippedItem = getZippedItem(conversionResult);
+ if (zippedItem != null) {
+ final StorageItem zippedStorageItem = zippedItem.getZippedStorageItem();
+ getLogger().debug(timeTrace.getMessage());
+ return zippedStorageItem;
+ }
+
+ // check if item exists in master repository
+ // this call will fail with ItemNotFoundException if the item does not exist in the master repository
+ final StorageItem masterItem = retrieveItemFromMaster(conversionResult.getConvertedPath());
+
+ if (masterItem instanceof StorageCollectionItem) {
+ // item is non-zip folder
+ final ZipAwareStorageCollectionItem zipAwareStorageCollectionItem = new ZipAwareStorageCollectionItem(this,
+ (StorageCollectionItem) masterItem, getLogger());
+ getLogger().debug(timeTrace.getMessage());
+ return zipAwareStorageCollectionItem;
+ } else {
+ getLogger().debug(timeTrace.getMessage());
+ // if item is a non-zip file we simply return it as it is
+ return masterItem;
+ }
+
+ }
+
+ /**
+ * Checks if the request path represents a zipped item (a file or directory within a zip file)
+ * and if yes returns it. If the request path does not represent a zipped item <code>null</code>
+ * is returned
+ *
+ * @param conversionResult
+ * the result of the snapshot path conversion, containing the converted path
+ * @return item that represents a file or folder within a zip file, <code>null</code> if the
+ * requested path does not point to zip content
+ * @throws LocalStorageException
+ * @throws ItemNotFoundException
+ * is thrown if for non-existing or invalid request path
+ */
+ private ZippedItem getZippedItem(final ConversionResult conversionResult) throws LocalStorageException,
+ ItemNotFoundException {
+ final StringBuilder pathInZip = new StringBuilder();
+ final String[] pathSegments = conversionResult.getConvertedPath().split("/");
+ String zipFilePath = "";
+ String zipItemPath = null;
+ long zipLastModified = 0L;
+
+ for (final String pathSegment : pathSegments) {
+ if (zipItemPath == null) {
+ if (!zipFilePath.toString().endsWith("/")) {
+ zipFilePath = zipFilePath + "/";
+ }
+ zipFilePath = zipFilePath + pathSegment;
+ if (zipFilePath.endsWith(Util.UNZIP_TYPE_EXTENSION)) {
+ final String zipFilePathWithoutExtension = zipFilePath.substring(0, zipFilePath.length()
+ - Util.UNZIP_TYPE_EXTENSION.length());
+ getCache().cleanSnapshots(conversionResult);
+ final File zipFile = getCache().getArchive(zipFilePathWithoutExtension);
+ if (zipFile != null) {
+ zipLastModified = zipFile.lastModified();
+ zipItemPath = zipFilePathWithoutExtension;
+ }
+ }
+ } else {
+ if (pathInZip.length() > 0) {
+ pathInZip.append("/");
+ }
+ pathInZip.append(pathSegment);
+ }
+ }
+ if (zipItemPath != null) {
+ // creating a new ZippedItem fails with ItemNotFoundException if a non-existing file or folder
+ // inside the (existing) zip file is accessed
+ getLogger().debug(conversionResult.getConvertedPath() + " points into a zip file.");
+ final ZippedItem zippedItem = new ZippedItem(this, zipItemPath, pathInZip.toString(), zipLastModified,
+ getLogger());
+ return zippedItem;
+ }
+ return null;
+ }
+
+ public synchronized UnzipCache getCache() {
+ if (cache == null) {
+ cache = new UnzipCache(this, getLogger());
+ }
+ return cache;
+ }
+
+ @Override
+ protected StorageLinkItem createLink(final StorageItem item) throws UnsupportedStorageOperationException,
+ IllegalOperationException, LocalStorageException {
+ // abstract super methods not documented.
+ // called during #onEvent processing.
+ // any thrown Exception will be logged polluting the nexus log with Exception stacks.
+ // So we interpret the method as automated hook allowing, but not forcing LinkItem creation.
+ return null;
+ }
+
+ @Override
+ protected void deleteLink(final StorageItem item) throws UnsupportedStorageOperationException,
+ IllegalOperationException, ItemNotFoundException, LocalStorageException {
+ // nothing created. Nothing to be deleted
+ }
+
+ @Override
+ public void setLocalStorage(final LocalRepositoryStorage localStorage) {
+ if (localStorage instanceof DefaultFSLocalRepositoryStorage) {
+ super.setLocalStorage(localStorage);
+ } else {
+ throw new RuntimeException(localStorage + " is not an instance of DefaultFSLocalRepositoryStorage");
+ }
+ }
+
+ @Override
+ public boolean isUseVirtualVersion() {
+ return ((UnzipRepositoryConfiguration) getExternalConfiguration(false)).isUseVirtualVersion();
+ }
+
+ @Override
+ public void setUseVirtualVersion(final boolean val) {
+ ((UnzipRepositoryConfiguration) getExternalConfiguration(true)).setUseVirtualVersion(val);
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/RequestTimeTrace.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/RequestTimeTrace.java
new file mode 100644
index 0000000..d1afb62
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/RequestTimeTrace.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+public class RequestTimeTrace {
+
+ private final long startTime;
+
+ private final String requestPath;
+
+ public RequestTimeTrace(final String requestPath) {
+ this.startTime = System.currentTimeMillis();
+ this.requestPath = requestPath;
+ }
+
+ public long getTimeSpent() {
+ return System.currentTimeMillis() - startTime;
+ }
+
+ public String getMessage() {
+ return "Served request in " + getTimeSpent() + "ms: " + requestPath;
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepository.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepository.java
new file mode 100644
index 0000000..19313f9
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepository.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.sonatype.nexus.proxy.repository.ShadowRepository;
+
+public interface UnzipRepository extends ShadowRepository {
+
+ boolean isUseVirtualVersion();
+
+ void setUseVirtualVersion(boolean useVirtualVersion);
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfiguration.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfiguration.java
new file mode 100644
index 0000000..b9600b7
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfiguration.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.sonatype.nexus.proxy.repository.AbstractShadowRepositoryConfiguration;
+
+public class UnzipRepositoryConfiguration extends AbstractShadowRepositoryConfiguration {
+
+ private static final String USE_VIRTUAL_VERSION = "useVirtualVersion";
+
+ public UnzipRepositoryConfiguration(final Xpp3Dom configuration) {
+ super(configuration);
+ }
+
+ public boolean isUseVirtualVersion() {
+ return Boolean.parseBoolean(getNodeValue(getRootNode(), USE_VIRTUAL_VERSION, Boolean.FALSE.toString()));
+ }
+
+ public void setUseVirtualVersion(final boolean val) {
+ setNodeValue(getRootNode(), USE_VIRTUAL_VERSION, Boolean.toString(val));
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfigurator.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfigurator.java
new file mode 100644
index 0000000..d7cf745
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryConfigurator.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.codehaus.plexus.component.annotations.Component;
+import org.sonatype.configuration.ConfigurationException;
+import org.sonatype.nexus.configuration.Configurator;
+import org.sonatype.nexus.configuration.ExternalConfiguration;
+import org.sonatype.nexus.configuration.application.ApplicationConfiguration;
+import org.sonatype.nexus.configuration.model.CRepositoryCoreConfiguration;
+import org.sonatype.nexus.proxy.repository.AbstractShadowRepositoryConfigurator;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+@Component(role = UnzipRepositoryConfigurator.class)
+public class UnzipRepositoryConfigurator extends AbstractShadowRepositoryConfigurator implements Configurator {
+
+ @Override
+ public void doApplyConfiguration(final Repository repository, final ApplicationConfiguration configuration,
+ final CRepositoryCoreConfiguration coreConfig) throws ConfigurationException {
+ super.doApplyConfiguration(repository, configuration, coreConfig);
+
+ final ExternalConfiguration<?> externalConfiguration = coreConfig.getExternalConfiguration();
+ final UnzipRepositoryConfiguration unzipRepoConfig = (UnzipRepositoryConfiguration) externalConfiguration
+ .getConfiguration(false);
+
+ repository.adaptToFacet(UnzipRepository.class).setUseVirtualVersion(unzipRepoConfig.isUseVirtualVersion());
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryHtmlCustomizer.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryHtmlCustomizer.java
new file mode 100644
index 0000000..d2ef5a7
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryHtmlCustomizer.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.codehaus.plexus.component.annotations.Component;
+import org.sonatype.nexus.plugins.rest.AbstractNexusIndexHtmlCustomizer;
+import org.sonatype.nexus.plugins.rest.NexusIndexHtmlCustomizer;
+
+@Component(role = NexusIndexHtmlCustomizer.class, hint = "UnzipRepositoryHtmlCustomizer")
+public class UnzipRepositoryHtmlCustomizer extends AbstractNexusIndexHtmlCustomizer {
+ /*
+ * Normally this script tag should be inserted into the head of the page using
+ * getPostHeadContribution, BUT as with the current nexus plugin system it is possible that
+ * other plug-ins overwrite the already overwritten JS function with code only fitting to their
+ * usage type, e.g. the OBR plug-in of Nexus Pro enhances the Virtual Repository Editor.
+ *
+ * By writing it in the first part of the Body we ensure for now, that our script is loaded as
+ * last one. Works only as long as we are the only one doing this so. The script provided with
+ * this ensures that other repository type can still select all available repositories without
+ * filtering.
+ *
+ * Overall this can't be the final solution, possibly it is neccessary to update the way users
+ * enhance the Nexus UI in general. In particular useful would be if you can define filtering of
+ * choosable repositories for a specific repository type already in the nexus repository model.
+ */
+ @Override
+ public String getPreBodyContribution(final java.util.Map<String, Object> context) {
+ final String version = getVersionFromJarFile("/META-INF/maven/org.eclipse.tycho.nexus/unzip-repository-plugin/pom.properties");
+
+ return "<script src=\"" + UnzipRepositoryResourceBundle.JS_SCRIPT_PATH + (version == null ? "" : "?" + version)
+ + "\" type=\"text/javascript\" charset=\"utf-8\"></script>";
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryResourceBundle.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryResourceBundle.java
new file mode 100644
index 0000000..4e766cd
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryResourceBundle.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.codehaus.plexus.component.annotations.Component;
+import org.sonatype.nexus.plugins.rest.AbstractNexusResourceBundle;
+import org.sonatype.nexus.plugins.rest.DefaultStaticResource;
+import org.sonatype.nexus.plugins.rest.NexusResourceBundle;
+import org.sonatype.nexus.plugins.rest.StaticResource;
+
+@Component(role = NexusResourceBundle.class, hint = "UnzipRepositoryResourceBundle")
+public class UnzipRepositoryResourceBundle extends AbstractNexusResourceBundle {
+ public static final String JS_SCRIPT_PATH = "js/unzip/unzip-repo.js";
+
+ @Override
+ public List<StaticResource> getContributedResouces() {
+ final List<StaticResource> result = new ArrayList<StaticResource>();
+
+ result.add(new DefaultStaticResource(getClass().getResource("/resources/js/unzip-repo.js"), "/"
+ + JS_SCRIPT_PATH, "application/x-javascript"));
+
+ return result;
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplate.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplate.java
new file mode 100644
index 0000000..c8cc979
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplate.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.sonatype.nexus.configuration.model.CRepository;
+import org.sonatype.nexus.configuration.model.CRepositoryCoreConfiguration;
+import org.sonatype.nexus.configuration.model.CRepositoryExternalConfigurationHolderFactory;
+import org.sonatype.nexus.configuration.model.DefaultCRepository;
+import org.sonatype.nexus.proxy.maven.maven2.Maven2ContentClass;
+import org.sonatype.nexus.proxy.repository.RepositoryWritePolicy;
+import org.sonatype.nexus.templates.repository.AbstractRepositoryTemplate;
+import org.sonatype.nexus.templates.repository.AbstractRepositoryTemplateProvider;
+
+//Alike org.sonatype.nexus.templates.repository.maven.Maven1Maven2ShadowRepositoryTemplate
+public class UnzipRepositoryTemplate extends AbstractRepositoryTemplate {
+
+ public UnzipRepositoryTemplate(final AbstractRepositoryTemplateProvider provider, final String id,
+ final String description) {
+ super(provider, id, description, new Maven2ContentClass(), UnzipRepository.class);
+ }
+
+ @Override
+ protected CRepositoryCoreConfiguration initCoreConfiguration() {
+ final CRepository repo = new DefaultCRepository();
+
+ repo.setId("");
+ repo.setName("");
+ repo.setProviderRole(UnzipRepository.class.getName());
+ repo.setProviderHint(DefaultUnzipRepository.REPOSITORY_HINT);
+
+ final Xpp3Dom ex = new Xpp3Dom(DefaultCRepository.EXTERNAL_CONFIGURATION_NODE_NAME);
+ repo.setExternalConfiguration(ex);
+
+ final UnzipRepositoryConfiguration exConf = new UnzipRepositoryConfiguration(ex);
+ repo.externalConfigurationImple = exConf;
+
+ repo.setWritePolicy(RepositoryWritePolicy.READ_ONLY.name());
+
+ final CRepositoryCoreConfiguration result = new CRepositoryCoreConfiguration(getTemplateProvider()
+ .getApplicationConfiguration(), repo,
+ new CRepositoryExternalConfigurationHolderFactory<UnzipRepositoryConfiguration>() {
+ public UnzipRepositoryConfiguration createExternalConfigurationHolder(final CRepository config) {
+ return new UnzipRepositoryConfiguration((Xpp3Dom) config.getExternalConfiguration());
+ }
+ });
+
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplateProvider.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplateProvider.java
new file mode 100644
index 0000000..568569c
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/UnzipRepositoryTemplateProvider.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
+import org.sonatype.nexus.plugins.RepositoryType;
+import org.sonatype.nexus.proxy.registry.RepositoryTypeDescriptor;
+import org.sonatype.nexus.proxy.registry.RepositoryTypeRegistry;
+import org.sonatype.nexus.templates.TemplateProvider;
+import org.sonatype.nexus.templates.TemplateSet;
+import org.sonatype.nexus.templates.repository.AbstractRepositoryTemplateProvider;
+
+// Alike org.sonatype.nexus.templates.repository.DefaultRepositoryTemplateProvider
+@Component(role = TemplateProvider.class, hint = "unzipRepo-templates")
+public class UnzipRepositoryTemplateProvider extends AbstractRepositoryTemplateProvider implements Initializable {
+
+ private static final String UNZIP_REPOSITORY_PREFIX = "unzip";
+
+ @Requirement
+ private RepositoryTypeRegistry repositoryTypeRegistry;
+
+ @Override
+ public TemplateSet getTemplates() {
+ final TemplateSet templates = new TemplateSet(null);
+
+ final String templateId = "unzipRepo-template";
+ final String templateDescription = "Unzip Repository Template";
+ templates.add(new UnzipRepositoryTemplate(this, templateId, templateDescription));
+
+ return templates;
+ }
+
+ @Override
+ public void initialize() throws InitializationException {
+ final RepositoryTypeDescriptor descriptor = new RepositoryTypeDescriptor(UnzipRepository.class,
+ DefaultUnzipRepository.REPOSITORY_HINT, UNZIP_REPOSITORY_PREFIX, RepositoryType.UNLIMITED_INSTANCES);
+ repositoryTypeRegistry.registerRepositoryTypeDescriptors(descriptor);
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ConversionResult.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ConversionResult.java
new file mode 100644
index 0000000..d6e3f9b
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ConversionResult.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+/**
+ * Result of a path conversion, that contains all relevant outcome of one conversion.
+ *
+ */
+public class ConversionResult {
+
+ private final String originalPath;
+ private final String convertedPath;
+ private final String latestVersion;
+ private final String pathUpToVersion;
+ private final boolean pathConverted;
+ private final boolean snapshotAvailable;
+
+ private ConversionResult(final String originalPath, final String convertedPath, final String latestVersion,
+ final String pathUpToVersion, final boolean pathConverted, final boolean snapshotAvailable) {
+ this.originalPath = originalPath;
+ this.convertedPath = convertedPath;
+ this.latestVersion = latestVersion;
+ this.pathUpToVersion = pathUpToVersion;
+ this.pathConverted = pathConverted;
+ this.snapshotAvailable = snapshotAvailable;
+ }
+
+ /**
+ * Constructor that is called if a path was successfully converted.
+ *
+ * @param originalPath
+ * the path that was requested
+ * @param convertedPath
+ * the converted path, where keywords were successfully replaced
+ * @param latestVersion
+ * the latest version
+ * @param pathUpToVersion
+ * the path up to the index where the version starts
+ */
+ public ConversionResult(final String originalPath, final String convertedPath, final String latestVersion,
+ final String pathUpToVersion) {
+ this(originalPath, convertedPath, latestVersion, pathUpToVersion, !originalPath.equals(convertedPath), true);
+ }
+
+ /**
+ * Constructor that is called if no path was converted.
+ *
+ * <li/><code>getOriginalPath</code> and <code>getConvertedPath</code> will return the given
+ * <code>originalPath</code>. <li/><code>getLatestVersion</code> and
+ * <code>getPathUpToVersion</code> will return <code>null</code>. <li/>
+ * <code>isPathConverted</code> will be <code>false</code>. <li/>
+ * <code>isASnapshotAvailable</code> will be <code>true</code>.
+ *
+ * @param originalPath
+ * the path that was requested
+ */
+ public ConversionResult(final String originalPath) {
+ this(originalPath, originalPath, null, null, false, true);
+ }
+
+ /**
+ * Constructor that is called if no snapshots are available.
+ *
+ * <li/><code>getOriginalPath</code> and <code>getConvertedPath</code> will return the given
+ * <code>originalPath</code>. <li/><code>getLatestVersion</code> will return <code>null</code>.
+ * <li/><code>isPathConverted</code> will be <code>false</code>.
+ *
+ * @param originalPath
+ * the path that was requested
+ * @param pathUpToVersion
+ * the path up to the index where the version starts
+ * @param snapshotAvailable
+ * flag that reflects if the <code>maven-metadata.xml</code> file was found and thus
+ * let us assume if there are any snapshots at all
+ */
+ public ConversionResult(final String originalPath, final String pathUpToVersion, final boolean snapshotAvailable) {
+ this(originalPath, originalPath, null, pathUpToVersion, false, snapshotAvailable);
+ }
+
+ /**
+ *
+ * @return <code>true</code> if the <code>maven-metadata.xml</code> file was found and thus let
+ * us assume if there are any snapshots at all, <code>false</code> otherwise
+ */
+ public boolean isASnapshotAvailable() {
+ return snapshotAvailable;
+ }
+
+ /**
+ *
+ * @return <code>true</code> if the requested path was converted, <code>false</code> if no
+ * conversion was performed
+ */
+ public boolean isPathConverted() {
+ return pathConverted;
+ }
+
+ /**
+ *
+ * @return the original request path
+ */
+ public String getOriginalPath() {
+ return originalPath;
+ }
+
+ /**
+ *
+ * @return the converted path, where keywords were successfully replaced or the original request
+ * path, otherwise
+ */
+ public String getConvertedPath() {
+ return convertedPath;
+ }
+
+ /**
+ *
+ * @return the latest snapshot version or <code>null</code> if no conversion took place
+ */
+ public String getLatestVersion() {
+ return latestVersion;
+ }
+
+ /**
+ *
+ * @return the path up to the index where the version starts or <code>null</code> if no
+ * conversion took place but there are snapshots available
+ */
+ public String getPathUpToVersion() {
+ return pathUpToVersion;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestReleaseRequest.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestReleaseRequest.java
new file mode 100644
index 0000000..3da8293
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestReleaseRequest.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import org.apache.maven.artifact.repository.metadata.Versioning;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+public class LatestReleaseRequest extends ParsedRequest {
+
+ private final String requestPath;
+ private final String groupArtifactPath;
+ private final String artifactNameStart;
+ private final String artifactNameEnd;
+ private final VersionRange versionRange;
+
+ public LatestReleaseRequest(final String requestPath, final String groupArtifactPath,
+ final String artifactNameStart, final String artifactNameEnd, final VersionRange versionRange) {
+ this.requestPath = requestPath;
+ this.groupArtifactPath = groupArtifactPath;
+ this.artifactNameStart = artifactNameStart;
+ this.artifactNameEnd = artifactNameEnd;
+ this.versionRange = versionRange;
+ }
+
+ @Override
+ ConversionResult resolve(final Repository repository) throws LocalStorageException {
+ try {
+ final Versioning versioning = getVersioning(repository, metadataPath(groupArtifactPath + "/"));
+ final String releaseVersion = versioning.getRelease();
+ if (releaseVersion == null) {
+ return new ConversionResult(requestPath);
+ }
+
+ final String selectedVersion = selectVersion(versioning, versionRange, false);
+
+ final String releaseVersionDirectory = groupArtifactPath + "/" + selectedVersion + "/";
+
+ final String pathUpToVersion = releaseVersionDirectory + artifactNameStart + "-" + selectedVersion;
+ final String convertedPath = pathUpToVersion + artifactNameEnd;
+ return new ConversionResult(requestPath, convertedPath, selectedVersion, pathUpToVersion);
+ } catch (final ItemNotFoundException e) {
+ return new ConversionResult(requestPath);
+ }
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionRequest.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionRequest.java
new file mode 100644
index 0000000..047c731
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionRequest.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import org.apache.maven.artifact.repository.metadata.Snapshot;
+import org.apache.maven.artifact.repository.metadata.Versioning;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+public class LatestVersionRequest extends ParsedRequest {
+
+ /*
+ * Example request path:
+ *
+ * /org/eclipse/tycho/example/org.eclipse.tycho.example.p2repo/SNAPSHOT/org.eclipse.tycho.example
+ * .p2repo-SNAPHOT-assembly.zip-unzip/
+ *
+ * Example result for a snapshot:
+ *
+ * /org/eclipse/tycho/example/org.eclipse.tycho.example.p2repo/0.1.0-SNAPSHOT/org.eclipse.tycho.
+ * example.p2repo-0.1.0-20100505.133931-1-assembly.zip-unzip/
+ *
+ *
+ * Example result for a release:
+ *
+ * /org/eclipse/tycho/example/org.eclipse.tycho.example.p2repo/0.1.0/org.eclipse.tycho.example.
+ * p2repo-0.1.0-assembly.zip-unzip/
+ */
+ private final String requestPath;
+ private final String groupArtifactPath;
+ private final String artifactNameStart;
+ private final String artifactNameEnd;
+ private final VersionRange versionRange;
+
+ public LatestVersionRequest(final String requestPath, final String groupArtifactPath,
+ final String artifactNameStart, final String artifactNameEnd, final VersionRange versionRange) {
+ this.requestPath = requestPath;
+ this.groupArtifactPath = groupArtifactPath;
+ this.artifactNameStart = artifactNameStart;
+ this.artifactNameEnd = artifactNameEnd;
+ this.versionRange = versionRange;
+ }
+
+ @Override
+ ConversionResult resolve(final Repository repository) throws LocalStorageException {
+ try {
+ final Versioning versioning = getVersioning(repository, metadataPath(groupArtifactPath + "/"));
+
+ final String selectedVersion = selectVersion(versioning, versionRange, true);
+ final String latestVersionDirectory = groupArtifactPath + "/" + selectedVersion + "/";
+
+ if (selectedVersion.endsWith("-SNAPSHOT")) {
+ return resolveSnapshot(repository, selectedVersion, latestVersionDirectory);
+ }
+
+ final String pathUpToVersion = latestVersionDirectory + artifactNameStart + "-" + selectedVersion;
+ final String convertedPath = pathUpToVersion + artifactNameEnd;
+ return new ConversionResult(requestPath, convertedPath, selectedVersion, pathUpToVersion);
+ } catch (final ItemNotFoundException e) {
+ return new ConversionResult(requestPath);
+ }
+ }
+
+ private ConversionResult resolveSnapshot(final Repository repository, final String latestVersion,
+ final String latestVersionDirectory) throws LocalStorageException, ItemNotFoundException {
+ final Versioning snapshotVersioning = getVersioning(repository, metadataPath(latestVersionDirectory));
+ final Snapshot current = snapshotVersioning.getSnapshot();
+ final String latestTimestampVersion;
+ if (current != null) {
+ latestTimestampVersion = current.getTimestamp() + "-" + current.getBuildNumber();
+ } else {
+ throw new LocalStorageException(metadataPath(latestVersionDirectory)
+ + " does not contain current information in repository " + repository.getId());
+ }
+ final String latestVersionWithoutSnapshot = latestVersion.substring(0,
+ latestVersion.length() - "-SNAPSHOT".length());
+ final String pathUpToVersion = latestVersionDirectory + artifactNameStart + "-" + latestVersionWithoutSnapshot;
+ final String convertedPath = pathUpToVersion + "-" + latestTimestampVersion + artifactNameEnd;
+ return new ConversionResult(requestPath, convertedPath, latestVersion + "-" + latestTimestampVersion,
+ pathUpToVersion);
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ParsedRequest.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ParsedRequest.java
new file mode 100644
index 0000000..d5f0730
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/ParsedRequest.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.artifact.repository.metadata.Metadata;
+import org.apache.maven.artifact.repository.metadata.Versioning;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.StorageFileItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.proxy.maven.metadata.operations.MetadataBuilder;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+abstract class ParsedRequest {
+ private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
+
+ abstract ConversionResult resolve(final Repository repository) throws LocalStorageException;
+
+ Versioning getVersioning(final Repository repository, final String mdPath) throws LocalStorageException,
+ ItemNotFoundException {
+ final ResourceStoreRequest request = new ResourceStoreRequest(mdPath);
+ final PathLock.PathLockMonitor pathLock = PathLock.getLock(mdPath);
+ try {
+ synchronized (pathLock) {
+ final StorageItem mdItem = repository.retrieveItem(request);
+ if (mdItem instanceof StorageFileItem) {
+ final InputStream is = ((StorageFileItem) mdItem).getInputStream();
+ try {
+ final Metadata md = MetadataBuilder.read(is);
+ return md.getVersioning();
+ } finally {
+ is.close();
+ }
+ } else {
+ throw new LocalStorageException(mdPath + " is not an StorageFileItem in repository "
+ + repository.getId());
+ }
+ }
+ } catch (final ItemNotFoundException e) {
+ /*
+ * A not existing maven-metadata.xml is assumed to indicate an not existing snapshot GAV
+ * path, as there is no way to check GAV folder existence itself.
+ */
+ throw e;
+ } catch (final Exception e) {
+ throw new LocalStorageException(e);
+ } finally {
+ PathLock.releaseLock(pathLock);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ String selectVersion(final Versioning versioning, final VersionRange versionRange, final boolean findSnapshots)
+ throws ItemNotFoundException {
+ // do not rely on LATEST and RELEASE tag, because they not necessarily correspond to highest version number
+ String selectedVersion = getLatestVersion(versioning, findSnapshots);
+
+ if (versionRange != null && !versionRange.containsVersion(new DefaultArtifactVersion(selectedVersion))) {
+ final List<ArtifactVersion> versions = new ArrayList<ArtifactVersion>();
+ for (final String artifactVersion : versioning.getVersions()) {
+ if (findSnapshots || !artifactVersion.endsWith("-SNAPSHOT")) {
+ versions.add(new DefaultArtifactVersion(artifactVersion));
+ }
+ }
+ final ArtifactVersion matchedVersion = versionRange.matchVersion(versions);
+ if (matchedVersion != null) {
+ selectedVersion = matchedVersion.toString();
+ } else {
+ throw new ItemNotFoundException("No version found within range");
+ }
+ }
+ return selectedVersion;
+ }
+
+ @SuppressWarnings("deprecation")
+ private String getLatestVersion(final Versioning versioning, final boolean findSnapshots)
+ throws ItemNotFoundException {
+ final List<ArtifactVersion> artifactVersions = new ArrayList<ArtifactVersion>();
+ for (final String version : versioning.getVersions()) {
+ if (!findSnapshots && !version.trim().endsWith("-SNAPSHOT")) {
+ artifactVersions.add(new DefaultArtifactVersion(version));
+ } else if (findSnapshots) {
+ artifactVersions.add(new DefaultArtifactVersion(version));
+ }
+ }
+
+ ArtifactVersion maxVersion = null;
+ if (artifactVersions.isEmpty()) {
+ throw new ItemNotFoundException("maven-metadata.xml does not contain any version");
+ }
+ maxVersion = Collections.max(artifactVersions);
+ return ((DefaultArtifactVersion) maxVersion).toString();
+ }
+
+ String metadataPath(final String path) {
+ return path + MAVEN_METADATA_XML;
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLock.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLock.java
new file mode 100644
index 0000000..d6a5291
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLock.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Lock utility class to work around https://issues.sonatype.org/browse/NEXUS-3622
+ */
+class PathLock {
+ private static final Map<String, PathLock> lockMap = new HashMap<String, PathLock>();
+
+ static PathLockMonitor getLock(final String mdPath) {
+ PathLock ret = null;
+ synchronized (lockMap) {
+ ret = lockMap.get(mdPath);
+ if (ret == null) {
+ ret = new PathLock(mdPath);
+ lockMap.put(mdPath, ret);
+ }
+ ret.inc();
+ }
+ return ret.getLock();
+ }
+
+ private static boolean releaseLock(final String mdPath) {
+ synchronized (lockMap) {
+ final PathLock lock = lockMap.get(mdPath);
+ if (lock != null) {
+ lock.dec();
+ if (lock.getCount() <= 0) {
+ lockMap.remove(mdPath);
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean releaseLock(final PathLockMonitor lock) {
+ return releaseLock(lock.getPath());
+ }
+
+ PathLock(final String path) {
+ this.lock = new PathLockMonitor(path);
+ }
+
+ PathLockMonitor lock;
+ int count = 0;
+
+ private PathLockMonitor getLock() {
+ return lock;
+ }
+
+ private synchronized int getCount() {
+ return count;
+ }
+
+ private synchronized void inc() {
+ count++;
+ }
+
+ private synchronized void dec() {
+ count--;
+ }
+
+ static class PathLockMonitor {
+ private final String path;
+
+ public PathLockMonitor(final String path) {
+ super();
+ this.path = path;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverter.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverter.java
new file mode 100644
index 0000000..392e05d
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverter.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.sonatype.nexus.proxy.IllegalRequestException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+public class RequestPathConverter {
+ private static final Pattern SNAPSHOT_PATTERN = Pattern
+ .compile("^(.*/(?:\\d*\\.?)+(?:-\\w+)*-SNAPSHOT/)([^/]*-)SNAPSHOT[^/]");
+ private static final Pattern LATESTVERSION_PATTERN = Pattern.compile("/SNAPSHOT/([^/]*)-SNAPSHOT[^/]");
+ private static final Pattern RELEASE_PATTERN = Pattern.compile("/RELEASE/([^/]*)-RELEASE[^/]");
+
+ /**
+ * Converts the path to an artifact. The following requests are possible:
+ * <ul>
+ * <li>Latest released version, requested with the special version keyword <code>RELEASE</code></li>
+ * <li>Latest existing version (including SNAPSHOTs), requested with the special version keyword
+ * <code>SNAPSHOT</code></li>
+ * <li>The latest build identifier for a given SNAPSHOT-version, requested with the version
+ * <code>x.y.z-SNAPSHOT</code></li>
+ * </ul>
+ * Returns an unchanged path in case the provided path does not match the request structure.
+ *
+ * @param repository
+ * A <code>Repository</code>, where the requested artifact is searched for.
+ * @param request
+ * The request to be resolved to an specific artifact.
+ * @param useVirtualVersions
+ * whether the keywords SNAPSHOT or RELEASE shall be evaluated
+ * @return The result object of a dynamic version conversion.
+ *
+ * @throws LocalStorageException
+ * @throws IllegalRequestException
+ * if the range parameter of the request url cannot be parsed according Maven
+ * version range spec
+ */
+ public static ConversionResult convert(final Repository repository, final ResourceStoreRequest request,
+ final boolean useVirtualVersions) throws LocalStorageException, IllegalRequestException {
+ final ParsedRequest parsedRequest = parseRequest(request, useVirtualVersions);
+ return parsedRequest.resolve(repository);
+ }
+
+ private static ParsedRequest parseRequest(final ResourceStoreRequest request, final boolean useVirtualVersions)
+ throws IllegalRequestException {
+ final String requestPath = request.getRequestPath();
+
+ if (useVirtualVersions) {
+ final Matcher snapshotVersionMatcher = SNAPSHOT_PATTERN.matcher(requestPath);
+ if (snapshotVersionMatcher.find()) {
+ final MatchResult versionFolderMatchResult = snapshotVersionMatcher.toMatchResult();
+ final String pathToSnapshotArtifact = versionFolderMatchResult.group(1);
+ final String pathUpToVersion = pathToSnapshotArtifact + versionFolderMatchResult.group(2);
+ final String artifactNameEnd = requestPath.substring(versionFolderMatchResult.end() - 1);
+ return new SnapshotRequest(requestPath, pathUpToVersion, pathToSnapshotArtifact, artifactNameEnd);
+ }
+ final Matcher latestVersionMatcher = LATESTVERSION_PATTERN.matcher(requestPath);
+ if (latestVersionMatcher.find()) {
+
+ final MatchResult matchResult = latestVersionMatcher.toMatchResult();
+ final String groupArtifactPath = requestPath.substring(0, matchResult.start());
+ final String artifactNameStart = matchResult.group(1);
+ final String artifactNameEnd = requestPath.substring(matchResult.end() - 1);
+
+ final VersionRange versionRange = parseVersionRange(request);
+
+ return new LatestVersionRequest(requestPath, groupArtifactPath, artifactNameStart, artifactNameEnd,
+ versionRange);
+ }
+
+ final Matcher releaseVersionMatcher = RELEASE_PATTERN.matcher(requestPath);
+ if (releaseVersionMatcher.find()) {
+ final MatchResult matchResult = releaseVersionMatcher.toMatchResult();
+ final String groupArtifactPath = requestPath.substring(0, matchResult.start());
+ final String artifactNameStart = matchResult.group(1);
+ final String artifactNameEnd = requestPath.substring(matchResult.end() - 1);
+ final VersionRange versionRange = parseVersionRange(request);
+ return new LatestReleaseRequest(requestPath, groupArtifactPath, artifactNameStart, artifactNameEnd,
+ versionRange);
+ }
+ }
+ return new UnchangedRequest(requestPath);
+ }
+
+ private static VersionRange parseVersionRange(final ResourceStoreRequest request) throws IllegalRequestException {
+ // TODO: create versionRange form requestUrl.
+ return null;
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/SnapshotRequest.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/SnapshotRequest.java
new file mode 100644
index 0000000..7eed7ae
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/SnapshotRequest.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import org.apache.maven.artifact.repository.metadata.Snapshot;
+import org.apache.maven.artifact.repository.metadata.Versioning;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+public class SnapshotRequest extends ParsedRequest {
+ public String requestPath;
+ public String pathUpToVersion;
+ public String pathToSnapshotArtifact;
+ private final String artifactNameEnd;
+
+ public SnapshotRequest(final String requestPath, final String pathUpToVersion, final String pathToSnapshotArtifact,
+ final String artifactNameEnd) {
+ this.requestPath = requestPath;
+ this.pathUpToVersion = pathUpToVersion;
+ this.pathToSnapshotArtifact = pathToSnapshotArtifact;
+ this.artifactNameEnd = artifactNameEnd;
+ }
+
+ @Override
+ ConversionResult resolve(final Repository repository) throws LocalStorageException {
+ String latestSnapshotVersion;
+
+ try {
+ latestSnapshotVersion = getLatestSnapshotVersion(repository);
+ } catch (final ItemNotFoundException e) {
+ return new ConversionResult(requestPath, pathUpToVersion, false);
+ }
+
+ if (latestSnapshotVersion != null) {
+ final String pathToVersionedArtifact = pathUpToVersion + latestSnapshotVersion + artifactNameEnd;
+ return new ConversionResult(requestPath, pathToVersionedArtifact, latestSnapshotVersion, pathUpToVersion);
+ } else {
+ return new ConversionResult(requestPath);
+ }
+ }
+
+ private String getLatestSnapshotVersion(final Repository repository) throws LocalStorageException,
+ ItemNotFoundException {
+ final String mdPath = metadataPath(pathToSnapshotArtifact);
+ final Versioning mdVersioning = getVersioning(repository, mdPath);
+ if (mdVersioning != null) {
+ final Snapshot current = mdVersioning.getSnapshot();
+ if (current != null) {
+ return current.getTimestamp() + "-" + current.getBuildNumber();
+ } else {
+ throw new LocalStorageException(mdPath + " does not contain current information in repository "
+ + repository.getId());
+ }
+ } else {
+ throw new LocalStorageException(mdPath + " does not contain versioning information in repository "
+ + repository.getId());
+ }
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnchangedRequest.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnchangedRequest.java
new file mode 100644
index 0000000..feb2cf0
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnchangedRequest.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.repository.Repository;
+
+public class UnchangedRequest extends ParsedRequest {
+ public String requestPath;
+
+ public UnchangedRequest(final String requestPath) {
+ this.requestPath = requestPath;
+ }
+
+ @Override
+ ConversionResult resolve(final Repository repository) throws LocalStorageException {
+ return new ConversionResult(requestPath);
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCache.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCache.java
new file mode 100644
index 0000000..56eb067
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCache.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.codehaus.plexus.logging.Logger;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.cache.PathLock.PathLockMonitor;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
+import org.sonatype.nexus.proxy.storage.local.LocalRepositoryStorage;
+import org.sonatype.nexus.proxy.storage.local.fs.DefaultFSLocalRepositoryStorage;
+import org.sonatype.nexus.util.ItemPathUtils;
+
+public class UnzipCache {
+
+ private final DefaultUnzipRepository repository;
+ private final LocalRepositoryStorage localStorage;
+ private final Logger logger;
+
+ public UnzipCache(final DefaultUnzipRepository repository, final Logger logger) {
+ this.logger = logger;
+ this.repository = repository;
+ localStorage = this.repository.getLocalStorage();
+ }
+
+ /**
+ * Returns the requested artifact from the local storage if the artifact was already cached. If
+ * not it retrieves it from the corresponding repository and stores it in the local storage.
+ *
+ * @param requestPath
+ * the path to the requested artifact in the repository
+ *
+ * @return the file in the local storage
+ *
+ * @throws ItemNotFoundException
+ * thrown if the artifact cannot be found in the repository
+ *
+ * @throws LocalStorageException
+ *
+ */
+ public File getArchive(final String requestPath) throws ItemNotFoundException, LocalStorageException {
+ final PathLockMonitor folderLock = PathLock.getLock(getRequestPathParent(requestPath));
+ try {
+ synchronized (folderLock) {
+ final ResourceStoreRequest request = new ResourceStoreRequest(requestPath);
+ if (!localStorage.containsItem(repository, request)) {
+
+ logger.debug("Caching zip file from master repository: " + requestPath);
+ final StorageItem storageItem = retrieveItemFromMaster(requestPath);
+ localStorage.storeItem(repository, storageItem);
+ }
+ final File file = ((DefaultFSLocalRepositoryStorage) localStorage).getFileFromBase(repository, request);
+ logger.debug("Accessed cached zip file: " + requestPath);
+ return file;
+
+ }
+ } catch (final UnsupportedStorageOperationException e) {
+ throw new LocalStorageException(e);
+ } finally {
+ PathLock.releaseLock(folderLock);
+ }
+ }
+
+ /**
+ * Depending on the conversion result out-dated snapshots are removed from the storage, if
+ * possible.
+ *
+ *
+ * @param conversionResult
+ * if a snapshot conversion took place, old snapshot artifacts are removed from the
+ * cache. If no snapshot has been found, all snapshot artifacts are removed from the
+ * cache
+ *
+ * @throws ItemNotFoundException
+ * thrown if the artifact cannot be found in the repository
+ */
+ public void cleanSnapshots(final ConversionResult conversionResult) throws ItemNotFoundException {
+ if (conversionResult.isPathConverted() || !conversionResult.isASnapshotAvailable()) {
+
+ logger.debug("Looking for outdated cached snapshots artifacts to clean up");
+
+ final String requestPathParent = getRequestPathParent(conversionResult.getPathUpToVersion());
+ final ResourceStoreRequest parentPathRequest = new ResourceStoreRequest(requestPathParent);
+ final PathLockMonitor folderLock = PathLock.getLock(requestPathParent);
+ try {
+ synchronized (folderLock) {
+ final List<String> toBeDeleted = new LinkedList<String>();
+ for (final StorageItem item : localStorage.listItems(repository, parentPathRequest)) {
+ final String itemPath = item.getPath();
+ if (!conversionResult.isASnapshotAvailable()) {
+ toBeDeleted.add(itemPath);
+ } else if (itemPath.startsWith(conversionResult.getPathUpToVersion())
+ && !itemPath.contains(conversionResult.getLatestVersion())) {
+ toBeDeleted.add(itemPath);
+ }
+ }
+ //use list of Strings instead of items, cause file handles will prevent deletion in many cases
+ for (final String itemPath : toBeDeleted) {
+ localStorage.shredItem(repository, new ResourceStoreRequest(itemPath));
+ logger.debug("Deleted outdated cached snapshot artifact: " + itemPath);
+ }
+ if (toBeDeleted.size() == 0) {
+ logger.debug("No outdated cached snapshots artifacts found");
+ }
+ }
+ } catch (final UnsupportedStorageOperationException e) {
+ logger.warn(this.getClass().getName() + ": Unable to delete cached item", e);
+ } catch (@SuppressWarnings("deprecation") final org.sonatype.nexus.proxy.StorageException e) {
+ // do nothing, as we accept if the file cannot be deleted
+ } catch (final ItemNotFoundException e) {
+ // do nothing, as we accept that files might be deleted on OS level
+ // e,g localStorage.listItems(repository, parentPathRequest) throws this exception in case
+ // the parent folder was removed from the file system
+ } finally {
+ PathLock.releaseLock(folderLock);
+ }
+ }
+ }
+
+ private String getRequestPathParent(final String path) {
+ return ItemPathUtils.getParentPath(path) + ItemPathUtils.PATH_SEPARATOR;
+ }
+
+ private StorageItem retrieveItemFromMaster(final String requestPath) throws ItemNotFoundException,
+ LocalStorageException {
+ try {
+ final ResourceStoreRequest request = new ResourceStoreRequest(requestPath);
+ return repository.getMasterRepository().retrieveItem(request);
+ } catch (final IllegalOperationException e) {
+ throw new LocalStorageException(e);
+ } catch (final AccessDeniedException e) {
+ throw new LocalStorageException(e);
+ } catch (@SuppressWarnings("deprecation") final org.sonatype.nexus.proxy.StorageException e) {
+ throw new LocalStorageException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/Util.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/Util.java
new file mode 100644
index 0000000..05e3888
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/Util.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import org.sonatype.nexus.proxy.item.StorageFileItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+
+public class Util {
+
+ public static final String UNZIP_TYPE_EXTENSION = "-unzip";
+
+ /**
+ * Checks whether the given item represents a zip file.
+ *
+ * @param item
+ * the item for which it should be checked whether it represents a zip file
+ * @return <code>true</code> if the given item represents a zip file, otherwise
+ * <code>false</code>
+ */
+ public static boolean checkIfZip(final StorageItem item) {
+ if (item instanceof StorageFileItem) {
+ final String mimeType = ((StorageFileItem) item).getContentLocator().getMimeType();
+ return "application/zip".equals(mimeType) || "application/java-archive".equals(mimeType);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItem.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItem.java
new file mode 100644
index 0000000..3cc7ec5
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItem.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.codehaus.plexus.logging.Logger;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.cache.ConversionResult;
+import org.eclipse.tycho.nexus.internal.plugin.cache.RequestPathConverter;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.DefaultStorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+
+/**
+ * This class implements a collection storage item that returns for contained zip files instances of
+ * {@link ZipStorageCollectionItem} when its content is listed. By this a link to browse the zip
+ * file is displayed when this collection is browsed in the web.
+ */
+public class ZipAwareStorageCollectionItem extends DefaultStorageCollectionItem {
+
+ private final DefaultUnzipRepository repository;
+ private final StorageCollectionItem collectionStorageItem;
+ private final Logger logger;
+
+ /**
+ * Constructor.
+ *
+ * @param repository
+ * the shadow repository
+ * @param collectionStorageItem
+ * the file storage item that represents the zip file in the master repository
+ */
+ public ZipAwareStorageCollectionItem(final DefaultUnzipRepository repository,
+ final StorageCollectionItem collectionStorageItem, final Logger logger) {
+ super(repository, collectionStorageItem.getResourceStoreRequest(), true, false);
+ this.repository = repository;
+ this.collectionStorageItem = collectionStorageItem;
+ this.logger = logger;
+ }
+
+ @Override
+ public Collection<StorageItem> list() throws AccessDeniedException, NoSuchResourceStoreException,
+ IllegalOperationException, ItemNotFoundException, LocalStorageException {
+ final Collection<StorageItem> membersToDisplay = new LinkedList<StorageItem>();
+ final ResourceStoreRequest request = new ResourceStoreRequest(collectionStorageItem.getPath()
+ + "/artifact-1-SNAPSHOT.xml");
+ final ConversionResult snapshotConversionResult = RequestPathConverter.convert(
+ repository.getMasterRepository(), request, repository.isUseVirtualVersion());
+
+ Collection<StorageItem> members;
+ try {
+ members = collectionStorageItem.list();
+ } catch (@SuppressWarnings("deprecation") final org.sonatype.nexus.proxy.StorageException e) {
+ throw new LocalStorageException(e);
+ }
+ for (final StorageItem member : members) {
+ if (member instanceof StorageCollectionItem) {
+ membersToDisplay.add(member);
+ } else if (Util.checkIfZip(member)) {
+ if (snapshotConversionResult.isPathConverted()) {
+ if (member.getPath().contains(snapshotConversionResult.getLatestVersion())) {
+ membersToDisplay.add(new ZippedStorageCollectionItem(new ZippedItem(repository, member
+ .getPath().replace(snapshotConversionResult.getLatestVersion(), "SNAPSHOT"), "", member
+ .getModified(), logger)));
+ }
+ } else {
+ membersToDisplay.add(new ZippedStorageCollectionItem(new ZippedItem(repository, member.getPath(),
+ "", member.getModified(), logger)));
+ }
+ }
+ }
+ return membersToDisplay;
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItem.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItem.java
new file mode 100644
index 0000000..1a58741
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItem.java
@@ -0,0 +1,371 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.codehaus.plexus.logging.Logger;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageFileItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.util.WrappingInputStream;
+
+/**
+ * This class represents an item (file or folder) within an archive file. The archive can e.g. be a
+ * zip file, jar file etc.
+ */
+public class ZippedItem {
+
+ /**
+ * Simple utility class used to close a dedicated ZipFile on closing a provided InputStream.
+ */
+ private class ZipClosingEntryStream extends WrappingInputStream {
+
+ private final ZipFile zipFile;
+
+ public ZipClosingEntryStream(final InputStream inputStream, final ZipFile zipFile) {
+ super(inputStream);
+ this.zipFile = zipFile;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ zipFile.close();
+ }
+
+ }
+
+ private final DefaultUnzipRepository repository;
+ private final StorageItem zippedStorageItem;
+ private final String pathInZip;
+ private final String zipItemPath;
+ private final long lastModified;
+ private final Logger logger;
+
+ /**
+ * Creates a ZippedItem for a file or folder based on the path to and inside the zip file.
+ *
+ * @param repository
+ * the repository in which the zipped item is accessed (the unzip repository)
+ * @param zipItemPath
+ * the path to the zip file
+ * @param pathInZip
+ * the path of the zipped item relative to the zip file
+ * @param lastModified
+ * the modification timestamp of the zip file and all entries
+ * @throws ItemNotFoundException
+ * is thrown if the path within the zip file does not point to an existing zip entry
+ * @throws LocalStorageException
+ * is thrown if an issue with the zip file itself occured
+ */
+ public ZippedItem(final DefaultUnzipRepository repository, final String zipItemPath, final String pathInZip,
+ final long lastModified, final Logger logger) throws ItemNotFoundException, LocalStorageException {
+ this.repository = repository;
+ this.zipItemPath = zipItemPath;
+
+ this.lastModified = lastModified;
+ this.logger = logger;
+
+ if (pathInZip != null) {
+ this.pathInZip = removeTrailingSlash(pathInZip);
+ } else {
+ this.pathInZip = "";
+ }
+ zippedStorageItem = createZippedStorageItem();
+ }
+
+ private String removeTrailingSlash(final String path) {
+ return (path.endsWith("/") ? path.substring(0, path.length() - 1) : path);
+ }
+
+ /**
+ * This constructor is used to create ZippedItem objects for folders and files while browsing
+ * inside a zip file.
+ *
+ * @param parentItem
+ * the parent ZippedItem
+ * @param entry
+ * the zip entry representing the zipped file in the zip file
+ * @throws ItemNotFoundException
+ */
+ private ZippedItem(final ZippedItem parentItem, final ZipEntry entry, final Logger logger)
+ throws ItemNotFoundException {
+ repository = parentItem.getRepository();
+ zipItemPath = parentItem.zipItemPath;
+
+ lastModified = parentItem.getLastModified();
+ this.logger = logger;
+
+ if (entry.getName() != null) {
+ final String pathInZip = entry.getName();
+ this.pathInZip = removeTrailingSlash(pathInZip);
+ } else {
+ pathInZip = "";
+ }
+ zippedStorageItem = createZippedStorageItem(entry);
+ }
+
+ /**
+ * Returns the path of the zipped item relative to the zip file. The returned path is always
+ * without trailing slash (this is consistent with the behaviour of
+ * {@link StorageItem#getPath()}).
+ *
+ * @return the relative path of the zipped item
+ */
+ public String getPathInZip() {
+ return pathInZip;
+ }
+
+ /**
+ * Returns the absolute path of the zipped entry (path to zip + path within zip). The returned
+ * path is always without trailing slash (this is consistent with the behaviour of
+ * {@link StorageItem#getPath()}).
+ *
+ * @return the absolute path of the zipped entry
+ */
+ public String getPath() {
+ final String unzippedPath = zipItemPath + Util.UNZIP_TYPE_EXTENSION;
+ if (pathInZip != null && !"".equals(pathInZip)) {
+ return unzippedPath + "/" + pathInZip;
+ } else {
+ return unzippedPath;
+ }
+ }
+
+ /**
+ * Returns the request for this zipped item.
+ *
+ * @return the request for this zipped item
+ */
+ public ResourceStoreRequest getRequest() {
+ final ResourceStoreRequest request = new ResourceStoreRequest(getPath(), true, false);
+ return request;
+ }
+
+ /**
+ * Returns the repository in which this zipped item is stored (the shadow repository).
+ *
+ * @return the repository in which this zipped item is stored
+ */
+ public DefaultUnzipRepository getRepository() {
+ return repository;
+ }
+
+ /**
+ * Returns the mime type for this zipped item. The mime type is determined by the file
+ * extension. If a mime type cannot be determined, by default "application/octet-stream" is
+ * returned. If this zipped item represents a directory <code>null</code> is returned
+ *
+ * @return the mime type for this zipped item, <code>null</code> if this zipped item is a
+ * directory
+ */
+ public String getMimeType() {
+ if (!isDirectory()) {
+ return URLConnection.guessContentTypeFromName(pathInZip);
+ } else {
+ return null;
+ }
+ }
+
+ private StorageItem createZippedStorageItem(final ZipEntry entry) {
+ if (entry.isDirectory()) {
+ return new ZippedStorageCollectionItem(this);
+ } else {
+ return new ZippedStorageFileItem(this, entry.getSize());
+ }
+ }
+
+ /**
+ * Creates a storage item that represents the file or folder in the zip file. If this zipped
+ * item represents the zip file itself, a collection storage item for the zip file is returned.
+ *
+ * @return the storage item representing the file or folder in the zip file, can be a
+ * {@link StorageFileItem} or a {@link StorageCollectionItem}, cannot be
+ * <code>null</code>
+ * @throws ItemNotFoundException
+ * is thrown if the path within the zip file does not point to an existing zip entry
+ * @throws LocalStorageException
+ * is thrown if an issue with the zip file itself occured
+ */
+ private StorageItem createZippedStorageItem() throws ItemNotFoundException, LocalStorageException {
+ if (pathInZip.length() == 0) {
+ // this ZippedItem represents the zip file itself
+ // -> return a collection storage item for the zip file
+ return new ZippedStorageCollectionItem(this);
+ }
+ ZipFile zipFile = null;
+ try {
+ final File file = repository.getCache().getArchive(zipItemPath);
+ zipFile = new ZipFile(file);
+
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ final ZipEntry entry = entries.nextElement();
+ final String entryName = removeTrailingSlash(entry.getName());
+ if (pathInZip.equals(entryName)) {
+ return createZippedStorageItem(entry);
+ }
+ }
+ } catch (final ItemNotFoundException e) {
+ throw new LocalStorageException(e);
+ } catch (final IOException e) {
+ throw new LocalStorageException(e);
+ } finally {
+ if (zipFile != null) {
+ try {
+ zipFile.close();
+ } catch (final IOException e) {
+ logger.warn("Unable to close ZipFile " + zipFile.getName(), e);
+ }
+ }
+ }
+
+ throw new ItemNotFoundException(new ResourceStoreRequest(getPath()));
+ }
+
+ /**
+ * Returns the storage item that represents the zipped item. Can be a {@link StorageFileItem} or
+ * a {@link StorageCollectionItem}.
+ *
+ * @return the storage item that represents the zipped item, cannot be <code>null</code>
+ */
+ public StorageItem getZippedStorageItem() {
+ return zippedStorageItem;
+ }
+
+ private boolean isDirectMember(String otherPathInZip) {
+ if (otherPathInZip.endsWith("/")) {
+ // cut of trailing slash
+ otherPathInZip = otherPathInZip.substring(0, otherPathInZip.length() - 1);
+ }
+ if (pathInZip.length() == 0) {
+ // this ZippedItem represents the zip file itself
+ return !otherPathInZip.contains("/");
+ } else {
+ // this ZippedItem represents a folder within a zip file
+ return otherPathInZip.startsWith(pathInZip.toString() + "/") &&
+ /* limit to direct members: */otherPathInZip.indexOf("/", (pathInZip.toString() + "/").length()) == -1;
+ }
+ }
+
+ /**
+ * Checks whether this zipped item represents a directory.
+ *
+ * @return <code>true</code> if this zipped item represents a directory, otherwise
+ * <code>false</code>
+ */
+ public boolean isDirectory() {
+ return zippedStorageItem instanceof StorageCollectionItem;
+ }
+
+ /**
+ * Returns the storage items that are direct members of this zipped item.
+ *
+ * @return the storage items that are direct members of this zipped item
+ * @throws LocalStorageException
+ * is thrown in case of IO errors or if it's tried to list members of a file
+ * @throws ItemNotFoundException
+ */
+ public StorageItem[] listMembers() throws LocalStorageException, ItemNotFoundException {
+ if (!isDirectory()) {
+ throw new LocalStorageException("members cannot be listed for a file");
+ }
+
+ final List<StorageItem> members = new LinkedList<StorageItem>();
+
+ final File file = repository.getCache().getArchive(zipItemPath);
+ if (file.isDirectory()) {
+ throw new LocalStorageException("ZipFile cannot work on directory.");
+ }
+
+ ZipFile zipFile = null;
+ try {
+ zipFile = new ZipFile(file);
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ final ZipEntry entry = entries.nextElement();
+ if (isDirectMember(entry.getName())) {
+ StorageItem zipEntryItem;
+ final ZippedItem zippedItem = new ZippedItem(this, entry, logger);
+ if (entry.isDirectory()) {
+ zipEntryItem = new ZippedStorageCollectionItem(zippedItem);
+ } else {
+ zipEntryItem = new ZippedStorageFileItem(zippedItem, entry.getSize());
+ }
+ members.add(zipEntryItem);
+ }
+ }
+ } catch (final IOException e) {
+ throw new LocalStorageException(e);
+ } finally {
+ if (zipFile != null) {
+ try {
+ zipFile.close();
+ } catch (final IOException e) {
+ logger.warn("Unable to close ZipFile " + zipFile.getName(), e);
+ }
+ }
+
+ }
+
+ return members.toArray(new StorageItem[members.size()]);
+ }
+
+ InputStream getStreamOfZippedFile() throws IOException {
+
+ try {
+ final File file = repository.getCache().getArchive(zipItemPath);
+ ZipFile zipFile;
+
+ zipFile = new ZipFile(file);
+
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ final InputStream inputStream = zipFile.getInputStream(getEntry(entries));
+ final ZipClosingEntryStream zipClosingEntryStream = new ZipClosingEntryStream(inputStream, zipFile);
+ return zipClosingEntryStream;
+ } catch (final ItemNotFoundException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ private ZipEntry getEntry(final Enumeration<? extends ZipEntry> entries) {
+
+ while (entries.hasMoreElements()) {
+ final ZipEntry entry = entries.nextElement();
+ final String entryName = removeTrailingSlash(entry.getName());
+ if (pathInZip.equals(entryName)) {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItem.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItem.java
new file mode 100644
index 0000000..b4ee587
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItem.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.item.DefaultStorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+
+/**
+ * This class implements a collection storage item for the root folder or a sub-folder inside a zip
+ * file that allows to list the direct members of the folder.
+ */
+public class ZippedStorageCollectionItem extends DefaultStorageCollectionItem {
+
+ private final ZippedItem zippedItem;
+
+ /**
+ * Constructor.
+ *
+ * @param zippedItem
+ * a zipped item that represents a folder in a zip file
+ * @param modified
+ * the modification time of the represented file
+ */
+ public ZippedStorageCollectionItem(final ZippedItem zippedItem) {
+ super(zippedItem.getRepository(), zippedItem.getRequest(), true, false);
+ this.zippedItem = zippedItem;
+ setModified(zippedItem.getLastModified());
+ }
+
+ @Override
+ public Collection<StorageItem> list() throws AccessDeniedException, NoSuchResourceStoreException,
+ IllegalOperationException, ItemNotFoundException, LocalStorageException {
+ return Arrays.asList(zippedItem.listMembers());
+ }
+}
diff --git a/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageFileItem.java b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageFileItem.java
new file mode 100644
index 0000000..752931c
--- /dev/null
+++ b/src/main/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageFileItem.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.ContentLocator;
+import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
+
+/**
+ * This class implements a storage file item for a file inside a zip file.
+ */
+public final class ZippedStorageFileItem extends DefaultStorageFileItem {
+
+ private static class ZippedStorageFileContentLocator implements ContentLocator {
+ private final ZippedItem zippedItem;
+
+ private ZippedStorageFileContentLocator(final ZippedItem zippedItem) {
+ this.zippedItem = zippedItem;
+ }
+
+ public InputStream getContent() throws IOException {
+ return zippedItem.getStreamOfZippedFile();
+ }
+
+ public String getMimeType() {
+ return zippedItem.getMimeType();
+ }
+
+ public boolean isReusable() {
+ return false;
+ }
+
+ }
+
+ /**
+ * Constructor
+ *
+ * @param zippedItem
+ * the file item represented by this storage item
+ * @param length
+ * the length of the represented file
+ * @param modified
+ * the modification time of the represented file
+ */
+ public ZippedStorageFileItem(final ZippedItem zippedItem, final long length) {
+ super(zippedItem.getRepository(), new ResourceStoreRequest(zippedItem.getPath()), true, false,
+ new ZippedStorageFileContentLocator(zippedItem));
+ // At creation time the underlying zip entry is known.
+ // Keeping this information avoids to open the zip and loop over the
+ // entries when answering related questions
+ setLength(length);
+ setModified(zippedItem.getLastModified());
+ }
+
+}
diff --git a/src/main/resources/epl-v10.html b/src/main/resources/epl-v10.html
new file mode 100644
index 0000000..ed4b196
--- /dev/null
+++ b/src/main/resources/epl-v10.html
@@ -0,0 +1,328 @@
+<html xmlns:o="urn:schemas-microsoft-com:office:office"
+xmlns:w="urn:schemas-microsoft-com:office:word"
+xmlns="http://www.w3.org/TR/REC-html40">
+
+<head>
+<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
+<meta name=ProgId content=Word.Document>
+<meta name=Generator content="Microsoft Word 9">
+<meta name=Originator content="Microsoft Word 9">
+<link rel=File-List
+href="./Eclipse%20EPL%202003_11_10%20Final_files/filelist.xml">
+<title>Eclipse Public License - Version 1.0</title>
+<!--[if gte mso 9]><xml>
+ <o:DocumentProperties>
+ <o:Revision>2</o:Revision>
+ <o:TotalTime>3</o:TotalTime>
+ <o:Created>2004-03-05T23:03:00Z</o:Created>
+ <o:LastSaved>2004-03-05T23:03:00Z</o:LastSaved>
+ <o:Pages>4</o:Pages>
+ <o:Words>1626</o:Words>
+ <o:Characters>9270</o:Characters>
+ <o:Lines>77</o:Lines>
+ <o:Paragraphs>18</o:Paragraphs>
+ <o:CharactersWithSpaces>11384</o:CharactersWithSpaces>
+ <o:Version>9.4402</o:Version>
+ </o:DocumentProperties>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <w:WordDocument>
+ <w:TrackRevisions/>
+ </w:WordDocument>
+</xml><![endif]-->
+<style>
+<!--
+ /* Font Definitions */
+@font-face
+ {font-family:Tahoma;
+ panose-1:2 11 6 4 3 5 4 4 2 4;
+ mso-font-charset:0;
+ mso-generic-font-family:swiss;
+ mso-font-pitch:variable;
+ mso-font-signature:553679495 -2147483648 8 0 66047 0;}
+ /* Style Definitions */
+p.MsoNormal, li.MsoNormal, div.MsoNormal
+ {mso-style-parent:"";
+ margin:0in;
+ margin-bottom:.0001pt;
+ mso-pagination:widow-orphan;
+ font-size:12.0pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"Times New Roman";}
+p
+ {margin-right:0in;
+ mso-margin-top-alt:auto;
+ mso-margin-bottom-alt:auto;
+ margin-left:0in;
+ mso-pagination:widow-orphan;
+ font-size:12.0pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"Times New Roman";}
+p.BalloonText, li.BalloonText, div.BalloonText
+ {mso-style-name:"Balloon Text";
+ margin:0in;
+ margin-bottom:.0001pt;
+ mso-pagination:widow-orphan;
+ font-size:8.0pt;
+ font-family:Tahoma;
+ mso-fareast-font-family:"Times New Roman";}
+@page Section1
+ {size:8.5in 11.0in;
+ margin:1.0in 1.25in 1.0in 1.25in;
+ mso-header-margin:.5in;
+ mso-footer-margin:.5in;
+ mso-paper-source:0;}
+div.Section1
+ {page:Section1;}
+-->
+</style>
+</head>
+
+<body lang=EN-US style='tab-interval:.5in'>
+
+<div class=Section1>
+
+<p align=center style='text-align:center'><b>Eclipse Public License - v 1.0</b>
+</p>
+
+<p><span style='font-size:10.0pt'>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER
+THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (&quot;AGREEMENT&quot;). ANY USE,
+REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE
+OF THIS AGREEMENT.</span> </p>
+
+<p><b><span style='font-size:10.0pt'>1. DEFINITIONS</span></b> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Contribution&quot; means:</span> </p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a)
+in the case of the initial Contributor, the initial code and documentation
+distributed under this Agreement, and<br clear=left>
+b) in the case of each subsequent Contributor:</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>i)
+changes to the Program, and</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>ii)
+additions to the Program;</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>where
+such changes and/or additions to the Program originate from and are distributed
+by that particular Contributor. A Contribution 'originates' from a Contributor
+if it was added to the Program by such Contributor itself or anyone acting on
+such Contributor's behalf. Contributions do not include additions to the
+Program which: (i) are separate modules of software distributed in conjunction
+with the Program under their own license agreement, and (ii) are not derivative
+works of the Program. </span></p>
+
+<p><span style='font-size:10.0pt'>&quot;Contributor&quot; means any person or
+entity that distributes the Program.</span> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Licensed Patents &quot; mean patent
+claims licensable by a Contributor which are necessarily infringed by the use
+or sale of its Contribution alone or when combined with the Program. </span></p>
+
+<p><span style='font-size:10.0pt'>&quot;Program&quot; means the Contributions
+distributed in accordance with this Agreement.</span> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Recipient&quot; means anyone who
+receives the Program under this Agreement, including all Contributors.</span> </p>
+
+<p><b><span style='font-size:10.0pt'>2. GRANT OF RIGHTS</span></b> </p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a)
+Subject to the terms of this Agreement, each Contributor hereby grants Recipient
+a non-exclusive, worldwide, royalty-free copyright license to<span
+style='color:red'> </span>reproduce, prepare derivative works of, publicly
+display, publicly perform, distribute and sublicense the Contribution of such
+Contributor, if any, and such derivative works, in source code and object code
+form.</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b)
+Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide,<span style='color:green'> </span>royalty-free
+patent license under Licensed Patents to make, use, sell, offer to sell, import
+and otherwise transfer the Contribution of such Contributor, if any, in source
+code and object code form. This patent license shall apply to the combination
+of the Contribution and the Program if, at the time the Contribution is added
+by the Contributor, such addition of the Contribution causes such combination
+to be covered by the Licensed Patents. The patent license shall not apply to
+any other combinations which include the Contribution. No hardware per se is
+licensed hereunder. </span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>c)
+Recipient understands that although each Contributor grants the licenses to its
+Contributions set forth herein, no assurances are provided by any Contributor
+that the Program does not infringe the patent or other intellectual property
+rights of any other entity. Each Contributor disclaims any liability to Recipient
+for claims brought by any other entity based on infringement of intellectual
+property rights or otherwise. As a condition to exercising the rights and
+licenses granted hereunder, each Recipient hereby assumes sole responsibility
+to secure any other intellectual property rights needed, if any. For example,
+if a third party patent license is required to allow Recipient to distribute
+the Program, it is Recipient's responsibility to acquire that license before
+distributing the Program.</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>d)
+Each Contributor represents that to its knowledge it has sufficient copyright
+rights in its Contribution, if any, to grant the copyright license set forth in
+this Agreement. </span></p>
+
+<p><b><span style='font-size:10.0pt'>3. REQUIREMENTS</span></b> </p>
+
+<p><span style='font-size:10.0pt'>A Contributor may choose to distribute the
+Program in object code form under its own license agreement, provided that:</span>
+</p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a)
+it complies with the terms and conditions of this Agreement; and</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b)
+its license agreement:</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>i)
+effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose; </span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>ii)
+effectively excludes on behalf of all Contributors all liability for damages,
+including direct, indirect, special, incidental and consequential damages, such
+as lost profits; </span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>iii)
+states that any provisions which differ from this Agreement are offered by that
+Contributor alone and not by any other party; and</span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>iv)
+states that source code for the Program is available from such Contributor, and
+informs licensees how to obtain it in a reasonable manner on or through a
+medium customarily used for software exchange.<span style='color:blue'> </span></span></p>
+
+<p><span style='font-size:10.0pt'>When the Program is made available in source
+code form:</span> </p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a)
+it must be made available under this Agreement; and </span></p>
+
+<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) a
+copy of this Agreement must be included with each copy of the Program. </span></p>
+
+<p><span style='font-size:10.0pt'>Contributors may not remove or alter any
+copyright notices contained within the Program. </span></p>
+
+<p><span style='font-size:10.0pt'>Each Contributor must identify itself as the
+originator of its Contribution, if any, in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution. </span></p>
+
+<p><b><span style='font-size:10.0pt'>4. COMMERCIAL DISTRIBUTION</span></b> </p>
+
+<p><span style='font-size:10.0pt'>Commercial distributors of software may
+accept certain responsibilities with respect to end users, business partners
+and the like. While this license is intended to facilitate the commercial use
+of the Program, the Contributor who includes the Program in a commercial
+product offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes the
+Program in a commercial product offering, such Contributor (&quot;Commercial
+Contributor&quot;) hereby agrees to defend and indemnify every other
+Contributor (&quot;Indemnified Contributor&quot;) against any losses, damages and
+costs (collectively &quot;Losses&quot;) arising from claims, lawsuits and other
+legal actions brought by a third party against the Indemnified Contributor to
+the extent caused by the acts or omissions of such Commercial Contributor in
+connection with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In order
+to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial Contributor
+to control, and cooperate with the Commercial Contributor in, the defense and
+any related settlement negotiations. The Indemnified Contributor may participate
+in any such claim at its own expense.</span> </p>
+
+<p><span style='font-size:10.0pt'>For example, a Contributor might include the
+Program in a commercial product offering, Product X. That Contributor is then a
+Commercial Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance claims and
+warranties are such Commercial Contributor's responsibility alone. Under this
+section, the Commercial Contributor would have to defend claims against the
+other Contributors related to those performance claims and warranties, and if a
+court requires any other Contributor to pay any damages as a result, the
+Commercial Contributor must pay those damages.</span> </p>
+
+<p><b><span style='font-size:10.0pt'>5. NO WARRANTY</span></b> </p>
+
+<p><span style='font-size:10.0pt'>EXCEPT AS EXPRESSLY SET FORTH IN THIS
+AGREEMENT, THE PROGRAM IS PROVIDED ON AN &quot;AS IS&quot; BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
+WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and distributing the
+Program and assumes all risks associated with its exercise of rights under this
+Agreement , including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs or
+equipment, and unavailability or interruption of operations. </span></p>
+
+<p><b><span style='font-size:10.0pt'>6. DISCLAIMER OF LIABILITY</span></b> </p>
+
+<p><span style='font-size:10.0pt'>EXCEPT AS EXPRESSLY SET FORTH IN THIS
+AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF
+THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGES.</span> </p>
+
+<p><b><span style='font-size:10.0pt'>7. GENERAL</span></b> </p>
+
+<p><span style='font-size:10.0pt'>If any provision of this Agreement is invalid
+or unenforceable under applicable law, it shall not affect the validity or
+enforceability of the remainder of the terms of this Agreement, and without
+further action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.</span> </p>
+
+<p><span style='font-size:10.0pt'>If Recipient institutes patent litigation
+against any entity (including a cross-claim or counterclaim in a lawsuit)
+alleging that the Program itself (excluding combinations of the Program with
+other software or hardware) infringes such Recipient's patent(s), then such
+Recipient's rights granted under Section 2(b) shall terminate as of the date
+such litigation is filed. </span></p>
+
+<p><span style='font-size:10.0pt'>All Recipient's rights under this Agreement
+shall terminate if it fails to comply with any of the material terms or
+conditions of this Agreement and does not cure such failure in a reasonable
+period of time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use and
+distribution of the Program as soon as reasonably practicable. However,
+Recipient's obligations under this Agreement and any licenses granted by
+Recipient relating to the Program shall continue and survive. </span></p>
+
+<p><span style='font-size:10.0pt'>Everyone is permitted to copy and distribute
+copies of this Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The Agreement
+Steward reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement Steward has
+the right to modify this Agreement. The Eclipse Foundation is the initial
+Agreement Steward. The Eclipse Foundation may assign the responsibility to
+serve as the Agreement Steward to a suitable separate entity. Each new version
+of the Agreement will be given a distinguishing version number. The Program
+(including Contributions) may always be distributed subject to the version of
+the Agreement under which it was received. In addition, after a new version of
+the Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly stated
+in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
+the intellectual property of any Contributor under this Agreement, whether
+expressly, by implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.</span> </p>
+
+<p><span style='font-size:10.0pt'>This Agreement is governed by the laws of the
+State of New York and the intellectual property laws of the United States of
+America. No party to this Agreement will bring a legal action under this
+Agreement more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.</span> </p>
+
+<p class=MsoNormal><![if !supportEmptyParas]>&nbsp;<![endif]><o:p></o:p></p>
+
+</div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/src/main/resources/resources/js/unzip-repo.js b/src/main/resources/resources/js/unzip-repo.js
new file mode 100644
index 0000000..77fb7e9
--- /dev/null
+++ b/src/main/resources/resources/js/unzip-repo.js
@@ -0,0 +1,46 @@
+/*
+ * Sonatype Nexus (TM) Open Source Version
+ * Copyright (c) 2007-2012 Sonatype, Inc.
+ * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
+ *
+ * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
+ * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
+ * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
+ * Eclipse Foundation. All other trademarks are the property of their respective owners.
+ */
+Ext.override(Sonatype.repoServer.VirtualRepositoryEditor, {
+ afterProviderSelectHandler: function (combo, rec, index) {
+ var provider = rec.data.provider;
+ var sourceRepoCombo = this.form.findField("shadowOf");
+ sourceRepoCombo.clearValue();
+ sourceRepoCombo.focus();
+ if (provider == "m1-m2-shadow") {
+ sourceRepoCombo.store.filterBy(function fn(rec, id) {
+ if (rec.data.repoType != "virtual" && rec.data.format == "maven1") {
+ return true
+ }
+ return false
+ })
+ } else if (provider == "m2-m1-shadow") {
+ sourceRepoCombo.store.filterBy(function fn(rec, id) {
+ if (rec.data.repoType != "virtual" && rec.data.format == "maven2") {
+ return true
+ }
+ return false
+ })
+ } else if (provider == "org.eclipse.tycho.nexus.plugin.DefaultUnzipRepository") {
+ sourceRepoCombo.store.filterBy(function fn(rec, id) {
+ if (rec.data.format == "maven2") {
+ return true
+ }
+ return false
+ })
+ } else {
+ sourceRepoCombo.store.filterBy(function fn(rec, id) {
+ return true
+ })
+ }
+ }
+}); \ No newline at end of file
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstHostedRepositoryTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstHostedRepositoryTest.java
new file mode 100644
index 0000000..76a9ce6
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstHostedRepositoryTest.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import java.io.IOException;
+
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZipAwareStorageCollectionItem;
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+import org.eclipse.tycho.nexus.internal.plugin.test.TestUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.util.ItemPathUtils;
+
+/**
+ * Tests the DefaultUnzipRepository with a hosted repository as master repository.
+ */
+@SuppressWarnings("nls")
+public class DefaultUnzipRepositoryAgainstHostedRepositoryTest extends DefaultUnzipRepositoryTest {
+
+ @Override
+ protected RepositoryMock createRepositoryMock() {
+ return RepositoryMock.createMasterRepo();
+ }
+
+ @Test
+ public void testRetrieveCollection() throws ItemNotFoundException, IllegalOperationException, IOException,
+ AccessDeniedException, NoSuchResourceStoreException {
+ final String collectionPath = "/dir";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZipAwareStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(collectionPath, collectionItem.getPath());
+ TestUtil.assertMembers(new String[] { "/dir/subdir" }, new String[0], collectionItem.list());
+ }
+
+ @Test
+ public void testRetrieveCollectionWithTrailingSlash() throws ItemNotFoundException, IllegalOperationException,
+ IOException, AccessDeniedException, NoSuchResourceStoreException {
+ final String collectionPath = "/dir/";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZipAwareStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(collectionPath), collectionItem.getPath());
+ TestUtil.assertMembers(new String[] { "/dir/subdir" }, new String[0], collectionItem.list());
+ }
+
+ @Test
+ public void testRetrieveCollectionWithArchiveMember() throws Exception {
+ final String collectionPath = "/dir/subdir";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZipAwareStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(collectionPath, collectionItem.getPath());
+ TestUtil.assertMembers( //
+ new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION, //
+ "/dir/subdir/archive2.zip" + Util.UNZIP_TYPE_EXTENSION },//
+ new String[0], //
+ collectionItem.list());
+ }
+
+ @Test
+ public void testRetrieveCollectionWithArchiveMemberWithTrailingSlash() throws Exception {
+ final String collectionPath = "/dir/subdir/";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZipAwareStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(collectionPath), collectionItem.getPath());
+ TestUtil.assertMembers( //
+ new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION, //
+ "/dir/subdir/archive2.zip" + Util.UNZIP_TYPE_EXTENSION },//
+ new String[0], collectionItem.list());
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ TestUtil.cleanUpTestFiles();
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstProxyRepositoryTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstProxyRepositoryTest.java
new file mode 100644
index 0000000..8808b9e
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryAgainstProxyRepositoryTest.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+
+/**
+ * Tests the DefaultUnzipRepository with a proxy repository as master repository.
+ *
+ * With proxy repositories there is one problem in Nexus. If a proxy repository is asked for a
+ * collection that exists in the proxied repository and no artifact under this collection (directly
+ * under it or deeper in the hierarchy) has been cached in the proxy repository the request fails
+ * with ItemNotFoundException.
+ *
+ * Since this behavior is different from a hosted repository and since it has influence on the
+ * functionality of the unzip repository we have here an explicit test against a proxy repository
+ * that for test purposes always throws an ItemNotFoundException when a collection is accessed.
+ *
+ * Because of this this test does not include any test cases that directly request a collection (in
+ * opposite to {@link DefaultUnzipRepositoryAgainstHostedRepositoryTest}).
+ */
+@SuppressWarnings("nls")
+public class DefaultUnzipRepositoryAgainstProxyRepositoryTest extends DefaultUnzipRepositoryTest {
+
+ @Override
+ protected RepositoryMock createRepositoryMock() {
+ final RepositoryMock masterRepo = RepositoryMock.createMasterRepo();
+ masterRepo.setBehaveAsProxy(true);
+ return masterRepo;
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryTest.java
new file mode 100644
index 0000000..e515a2c
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/DefaultUnzipRepositoryTest.java
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.UnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZippedStorageCollectionItem;
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+import org.eclipse.tycho.nexus.internal.plugin.test.TestUtil;
+import org.eclipse.tycho.nexus.internal.plugin.test.UnzipRepositoryMock;
+import org.junit.Assert;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.proxy.repository.HostedRepository;
+import org.sonatype.nexus.proxy.repository.RepositoryKind;
+import org.sonatype.nexus.proxy.repository.ShadowRepository;
+import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
+import org.sonatype.nexus.util.ItemPathUtils;
+
+@SuppressWarnings("nls")
+public abstract class DefaultUnzipRepositoryTest {
+ protected final RepositoryMock repositoryMock;
+ protected final DefaultUnzipRepository unzipRepo;
+
+ public DefaultUnzipRepositoryTest() {
+ repositoryMock = createRepositoryMock();
+ unzipRepo = UnzipRepositoryMock.createUnzipRepository(repositoryMock);
+ }
+
+ protected abstract RepositoryMock createRepositoryMock();
+
+ @Test
+ public void testGetRepositoryKind() {
+ final DefaultUnzipRepository repo = new DefaultUnzipRepository();
+ final RepositoryKind kind = repo.getRepositoryKind();
+ assertEquals(UnzipRepository.class, kind.getMainFacet());
+ assertTrue(kind.isFacetAvailable(ShadowRepository.class));
+ assertFalse(kind.isFacetAvailable(HostedRepository.class));
+ }
+
+ @Test
+ public void testRetrieveFile() throws ItemNotFoundException, IllegalOperationException, IOException {
+ final String filePath = "/dir/a.txt";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(filePath));
+ Assert.assertTrue(item instanceof DefaultStorageFileItem);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) item;
+ Assert.assertEquals("text/plain", fileItem.getMimeType());
+ Assert.assertEquals(filePath, fileItem.getPath());
+ TestUtil.assertContent("content of a.txt", fileItem);
+ }
+
+ @Test(expected = ItemNotFoundException.class)
+ public void testRetrieveNonExistingItem() throws ItemNotFoundException, IllegalOperationException, IOException {
+ unzipRepo.doRetrieveItem(new ResourceStoreRequest("/x.txt"));
+ }
+
+ @Test
+ public void testRetrieveArchiveAsFile() throws ItemNotFoundException, IllegalOperationException, IOException {
+ final String archivePath = "/dir/subdir/archive.zip";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(archivePath));
+ Assert.assertTrue(item instanceof DefaultStorageFileItem);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) item;
+ Assert.assertEquals("application/zip", fileItem.getMimeType());
+ Assert.assertEquals(archivePath, fileItem.getPath());
+ }
+
+ @Test
+ public void testRetrieveArchiveAsCollection() throws ItemNotFoundException, IllegalOperationException, IOException,
+ AccessDeniedException, NoSuchResourceStoreException {
+ final String archivePath = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION;
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(archivePath));
+ Assert.assertTrue(item instanceof ZippedStorageCollectionItem);
+ final ZippedStorageCollectionItem archiveItem = (ZippedStorageCollectionItem) item;
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(archivePath), archiveItem.getPath());
+ TestUtil.assertMembers(new String[] { archivePath + "/dir" }, new String[] { archivePath + "/test.txt" },
+ archiveItem.list());
+ }
+
+ @Test
+ public void testRetriveFileInArchive() throws IllegalOperationException, ItemNotFoundException, IOException {
+ final String filePath = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/test.txt";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(filePath));
+ Assert.assertTrue(item instanceof DefaultStorageFileItem);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) item;
+ Assert.assertEquals("text/plain", fileItem.getMimeType());
+ Assert.assertEquals(filePath, fileItem.getPath());
+ TestUtil.assertContent("some content", fileItem);
+ }
+
+ @Test
+ public void testRetrieveCollectionInArchive() throws IllegalOperationException, ItemNotFoundException,
+ AccessDeniedException, NoSuchResourceStoreException, IOException {
+ final String collectionPath = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZippedStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(collectionPath, collectionItem.getPath());
+ final Collection<StorageItem> members = collectionItem.list();
+ TestUtil.assertMembers(new String[] { collectionPath + "/subdir" },
+ new String[] { collectionPath + "/test.txt" }, members);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) TestUtil.findItem(
+ collectionPath + "/test.txt", members);
+ TestUtil.assertContent("some file content", fileItem);
+ }
+
+ @Test
+ public void testRetrieveCollectionInArchiveWithTrailingSlash() throws IllegalOperationException,
+ ItemNotFoundException, AccessDeniedException, NoSuchResourceStoreException, IOException {
+ final String collectionPath = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir/";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZippedStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(collectionPath), collectionItem.getPath());
+ final Collection<StorageItem> members = collectionItem.list();
+ TestUtil.assertMembers(new String[] { collectionPath + "subdir" },
+ new String[] { collectionPath + "test.txt" }, members);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) TestUtil.findItem(collectionPath + "test.txt",
+ members);
+ TestUtil.assertContent("some file content", fileItem);
+ }
+
+ @Test
+ public void testRetrieveSubCollectionInArchive() throws IllegalOperationException, ItemNotFoundException,
+ AccessDeniedException, NoSuchResourceStoreException, IOException {
+ final String collectionPath = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir/subdir";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZippedStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(collectionPath, collectionItem.getPath());
+ final Collection<StorageItem> members = collectionItem.list();
+ TestUtil.assertMembers(new String[] {}, new String[] { collectionPath + "/a.txt" }, members);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) TestUtil.findItem(collectionPath + "/a.txt",
+ members);
+ TestUtil.assertContent("some more content", fileItem);
+ }
+
+ @Test
+ public void testRetrieveSubCollectionInArchiveWithTrailingSlash() throws IllegalOperationException,
+ ItemNotFoundException, AccessDeniedException, NoSuchResourceStoreException, IOException {
+ final String collectionPath = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir/subdir/";
+ final StorageItem item = unzipRepo.doRetrieveItem(new ResourceStoreRequest(collectionPath));
+ Assert.assertTrue(item instanceof ZippedStorageCollectionItem);
+ final StorageCollectionItem collectionItem = (StorageCollectionItem) item;
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(collectionPath), collectionItem.getPath());
+ final Collection<StorageItem> members = collectionItem.list();
+ TestUtil.assertMembers(new String[] {}, new String[] { collectionPath + "a.txt" }, members);
+ final DefaultStorageFileItem fileItem = (DefaultStorageFileItem) TestUtil.findItem(collectionPath + "a.txt",
+ members);
+ TestUtil.assertContent("some more content", fileItem);
+ }
+
+ @Test(expected = ItemNotFoundException.class)
+ public void testRetrieveNonExistingItemInArchive() throws LocalStorageException, IllegalOperationException,
+ ItemNotFoundException {
+ unzipRepo.doRetrieveItem(new ResourceStoreRequest("/dir/subdir/archive.zip/x.txt"));
+ }
+
+ @Test(expected = ItemNotFoundException.class)
+ public void testRetrieveNonExistingItemInFolderInArchive() throws LocalStorageException, IllegalOperationException,
+ ItemNotFoundException {
+ unzipRepo.doRetrieveItem(new ResourceStoreRequest("/dir/subdir/archive.zip/dir/x.txt"));
+ }
+
+ @Test
+ public void testCreateLink() throws LocalStorageException, UnsupportedStorageOperationException,
+ IllegalOperationException {
+ unzipRepo.createLink(null);
+ }
+
+ @Test
+ public void testDeleteLink() throws LocalStorageException, UnsupportedStorageOperationException,
+ IllegalOperationException, ItemNotFoundException {
+ unzipRepo.deleteLink(null);
+ }
+
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionConverterTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionConverterTest.java
new file mode 100644
index 0000000..8ee32ca
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/LatestVersionConverterTest.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.IllegalRequestException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.StorageFileItem;
+import org.sonatype.nexus.proxy.maven.MavenRepository;
+
+@SuppressWarnings({ "nls" })
+public class LatestVersionConverterTest {
+
+ private String outerMetadataXml = "./src/test/resources/outer-maven-metadata.xml";
+
+ @Test
+ public void testConvertToLatestVersion() throws Exception {
+ assertPathIsConvertedTo(
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/SNAPSHOT/org.eclipse.tycho.example.target-SNAPSHOT.zip-unzip",
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.7.1-SNAPSHOT/org.eclipse.tycho.example.target-0.7.1-20110718.111322-2.zip-unzip");
+ }
+
+ @Test
+ public void testConvertToLatestReleaseVersion() throws Exception {
+ assertPathIsConvertedTo(
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/RELEASE/org.eclipse.tycho.example.target-RELEASE.zip-unzip",
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.7.0/org.eclipse.tycho.example.target-0.7.0.zip-unzip");
+ }
+
+ @Test
+ public void testConvertToLatestVersionNotSnapshot() throws Exception {
+ outerMetadataXml = "./src/test/resources/maven-metadata-latest-is-not-a-snapshot.xml";
+ assertPathIsConvertedTo(
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/SNAPSHOT/org.eclipse.tycho.example.target-SNAPSHOT.zip-unzip",
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.7.0/org.eclipse.tycho.example.target-0.7.0.zip-unzip");
+ }
+
+ @Test
+ public void testConvertToReleaseVersionNoReleaseAvailable() throws Exception {
+ outerMetadataXml = "./src/test/resources/outer-maven-metadata-without-release.xml";
+ assertPathIsNotConverted("org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/RELEASE/org.eclipse.tycho.example.target-RELEASE.zip-unzip");
+ }
+
+ @Test(expected = IllegalRequestException.class)
+ @Ignore
+ public void testInvalidRange() throws Exception {
+ // TODO
+ // requires specification how version ranges are defined in url.
+
+// assertPathIsConvertedWithinRange(
+// "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/SNAPSHOT/org.eclipse.tycho.example.target-SNAPSHOT.zip-unzip", "",
+// "[2.0.0,1.0.0)");
+ }
+
+ @Test
+ @Ignore
+ public void testConvertToLatestVersionWithinRange() throws Exception {
+ // TODO
+ // requires specification how version ranges are defined in url.
+
+// assertPathIsConvertedWithinRange(
+// "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/SNAPSHOT/org.eclipse.tycho.example.target-SNAPSHOT.zip-unzip",
+// "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.6.1-SNAPSHOT/org.eclipse.tycho.example.target-0.6.1-20110718.111322-2.zip-unzip",
+// "[0.5.0,0.7.0-SNAPSHOT)");
+ }
+
+ @Test
+ @Ignore
+ public void testNoLatestVersionWithinRange() throws Exception {
+ // TODO
+ // requires specification how version ranges are defined in url.
+
+// assertPathIsNotConvertedWithinRange(
+// "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/SNAPSHOT/org.eclipse.tycho.example.target-SNAPSHOT.zip-unzip",
+// "[1.0.0,2.0.0)");
+ }
+
+ @Test
+ @Ignore
+ public void testConvertToLatestReleasedVersionWithinRange() throws Exception {
+ // TODO
+ // requires specification how version ranges are defined in url.
+
+// assertPathIsConvertedWithinRange(
+// "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/RELEASE/org.eclipse.tycho.example.target-RELEASE.zip-unzip",
+// "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.6.0/org.eclipse.tycho.example.target-0.6.0.zip-unzip",
+// "[0.5.0,0.7.0)");
+ }
+
+ private void assertPathIsConvertedTo(final String requestPath, final String convertedPath) throws Exception {
+ assertPathConvertion(new ResourceStoreRequest(requestPath), convertedPath, true);
+ }
+
+ private void assertPathIsNotConverted(final String requestPath) throws Exception {
+ assertPathConvertion(new ResourceStoreRequest(requestPath), requestPath, false);
+ }
+
+ private void assertPathConvertion(final ResourceStoreRequest request, final String convertedPath,
+ final boolean isPathConverted) throws Exception {
+ final MavenRepository repositoryMock = createRepositoryMock(
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/",
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.7.1-SNAPSHOT/",
+ "org/eclipse/tycho/nexus/org.eclipse.tycho.example.target/0.6.1-SNAPSHOT/");
+ final ConversionResult conversionResult = RequestPathConverter.convert(repositoryMock, request, true);
+ Assert.assertEquals(isPathConverted, conversionResult.isPathConverted());
+ Assert.assertEquals(convertedPath, conversionResult.getConvertedPath());
+ }
+
+ @SuppressWarnings("unchecked")
+ private MavenRepository createRepositoryMock(final String pathToOuterMetadata, final String pathToArtifact,
+ final String pathToArtifact2) {
+ final String pathToOuterMetadataXML = pathToOuterMetadata + "maven-metadata.xml";
+ final String pathToInnerMetadataXML = pathToArtifact + "maven-metadata.xml";
+ final String pathToArtifact2MetadataXML = pathToArtifact2 + "maven-metadata.xml";
+ final MavenRepository repositoryMock = EasyMock.createMock(MavenRepository.class);
+ final Capture<ResourceStoreRequest>[] captures = new Capture[] { new Capture<ResourceStoreRequest>() };
+ try {
+ EasyMock.expect(repositoryMock.getId()).andStubReturn("");
+ EasyMock.expect(repositoryMock.retrieveItem(EasyMock.capture(captures[0])))
+ .andAnswer(new IAnswer<StorageFileItem>() {
+ public StorageFileItem answer() throws Throwable {
+ final StorageFileItem mavenMetaDataXml = EasyMock.createMock(StorageFileItem.class);
+ EasyMock.expect(mavenMetaDataXml.getInputStream()).andAnswer(new IAnswer<InputStream>() {
+
+ public InputStream answer() throws Throwable {
+ final File metaDataFile;
+ final String requestPath = captures[0].getValue().getRequestPath();
+ if (pathToInnerMetadataXML.equals(requestPath)) {
+ metaDataFile = new File(
+ "./src/test/resources/0.7.1-SNAPSHOT/maven-metadata.xml");
+ } else if (pathToArtifact2MetadataXML.equals(requestPath)) {
+ metaDataFile = new File(
+ "./src/test/resources/0.6.1-SNAPSHOT/maven-metadata.xml");
+ } else if (pathToOuterMetadataXML.equals(captures[0].getValue().getRequestPath())) {
+ metaDataFile = new File(outerMetadataXml);
+ } else {
+ throw new AssertionFailedError("wrong request for maven-metadata.xml: "
+ + requestPath);
+ }
+ return new FileInputStream(metaDataFile);
+ }
+ });
+ EasyMock.replay(mavenMetaDataXml);
+ return mavenMetaDataXml;
+ }
+ }).anyTimes();
+ } catch (final Exception e) {
+ throw new RuntimeException("Unable to create Maven Repo Mock", e);
+ }
+ EasyMock.replay(repositoryMock);
+ return repositoryMock;
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLockTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLockTest.java
new file mode 100644
index 0000000..0bc83da
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/PathLockTest.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.eclipse.tycho.nexus.internal.plugin.cache.PathLock;
+import org.eclipse.tycho.nexus.internal.plugin.cache.PathLock.PathLockMonitor;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class PathLockTest {
+
+ public static String aString = "";
+ public static String bString = "";
+ @SuppressWarnings("unused")
+ private static String cString = "";
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ public static void buildStrings(final String path) {
+ final PathLockMonitor lock = PathLock.getLock(path);
+ synchronized (lock) {
+ if (path.equals("a")) {
+ aString += "a";
+ cString += "y";
+ Thread.yield();
+ cString += "z";
+ aString += "b";
+ } else {
+ bString += "c";
+ cString += "y";
+ Thread.yield();
+ cString += "z";
+ bString += "d";
+ }
+ }
+ Assert.assertTrue(PathLock.releaseLock(lock));
+ }
+
+ @Test
+ public void testPathLock() throws InterruptedException {
+ final CountDownLatch startSignal = new CountDownLatch(1);
+ final CountDownLatch doneSignal = new CountDownLatch(400);
+
+ final ExecutorService executor = Executors.newCachedThreadPool();
+ final List<Future<Void>> results = new LinkedList<Future<Void>>();
+
+ for (int i = 0; i < 200; i++) {
+
+ final Callable<Void> c = new CallableWorker(startSignal, doneSignal, "a");
+ results.add(executor.submit(c));
+ final Callable<Void> c2 = new CallableWorker(startSignal, doneSignal, "b");
+ results.add(executor.submit(c2));
+
+ }
+
+ startSignal.countDown();
+ doneSignal.await();
+ //In theory this test can fail, as it can not be ruled out that the threads are
+ //executed in sequential order, but it is very probable therefore it might be interesting for manual testing
+ //Assert.assertTrue(cString.contains("yy") || cString.contains("zz"));
+ Assert.assertFalse(aString.contains("aa"));
+ Assert.assertFalse(bString.contains("cc"));
+ executor.shutdown();
+ }
+
+ class CallableWorker implements Callable<Void> {
+
+ private final CountDownLatch startSignal;
+ private final CountDownLatch doneSignal;
+ private final String path;
+
+ CallableWorker(final CountDownLatch startSignal, final CountDownLatch doneSignal, final String path) {
+ this.startSignal = startSignal;
+ this.doneSignal = doneSignal;
+ this.path = path;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ try {
+ startSignal.await();
+ doWork();
+ } finally {
+ doneSignal.countDown();
+ }
+ return null;
+ }
+
+ void doWork() {
+ buildStrings(path);
+ }
+ }
+
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverterTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverterTest.java
new file mode 100644
index 0000000..c1c775b
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/RequestPathConverterTest.java
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.Assert;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.StorageException;
+import org.sonatype.nexus.proxy.item.StorageFileItem;
+import org.sonatype.nexus.proxy.maven.MavenRepository;
+
+@SuppressWarnings({ "deprecation" })
+public class RequestPathConverterTest {
+
+ private static final String PATH_TO_CORRECT_MAVEN_METADATA_XML = "./src/test/resources/maven-metadata.xml";
+ private static final String PATH_TO_MISSING_SNAPSHOT_MAVEN_METADATA_XML = "./src/test/resources/missingSnapshot-maven-metadata.xml";
+ private static final String PATH_TO_MISSING_VERSIONING_MAVEN_METADATA_XML = "./src/test/resources/missingVersioning-maven-metadata.xml";
+ private static final String PATH_TO_NOT_EXISTING_MAVEN_METADATA_XML = "./src/test/resources/no-maven-metadata.xml";
+ private static final String VALID_VERSION_QUALIFIER_OF_TESTRESOURCE = "20100505.133931-1";
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversion() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithClassifier() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "", "x.y-z");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithTrailingSlash() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "/");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithTrailingSlashAndClassifier() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "/", "x.y.z");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversion() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "/plugins");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversionWithClassifier() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "/plugins", "x-y-z");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversionWithTrailingSlash() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "/plugins/");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversionWithTrailingSlashWithClassifier()
+ throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "/plugins/", "x-y.z");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversionWithSnapshotInContentName()
+ throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE,
+ "/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversionWithClassifierAndSnapshotInContentName()
+ throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE,
+ "/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar", "assembly");
+ }
+
+ @Test
+ public void testDirectoryInSnapshotZipToDirectoryInLatestShapshotZipConversionWithBetaQualifierWithClassifierAndSnapshotInContentName()
+ throws Exception {
+ assertPathConversion("0.1.0-BETA-1-SNAPSHOT", "0.1.0-BETA-1-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE,
+ "/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar", "assembly");
+ }
+
+ @Test
+ public void testFileInSnapshotZipToFileInLatestShapshotZipConversion() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE,
+ "/plugins/myplugin.jar");
+ }
+
+ @Test
+ public void testFileInSnapshotZipToFileInLatestShapshotZipConversionWithClassifier() throws Exception {
+ assertPathConversion("0.1.0-SNAPSHOT", "0.1.0-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE,
+ "/plugins/myplugin.jar", "assembly");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithoutPointReleaseVersion() throws Exception {
+ assertPathConversion("0.1-SNAPSHOT", "0.1-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithoutPointReleaseVersionWithClassifier() throws Exception {
+ assertPathConversion("0.1-SNAPSHOT", "0.1-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "", "xyz");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithMultipleDigitVersionParts() throws Exception {
+ assertPathConversion("11.222.3333-SNAPSHOT", "11.222.3333-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "");
+ }
+
+ @Test
+ public void testSnapshotZipToLatestShapshotZipConversionWithMultipleDigitVersionPartsWithClassifier()
+ throws Exception {
+ assertPathConversion("11.222.3333-SNAPSHOT", "11.222.3333-" + VALID_VERSION_QUALIFIER_OF_TESTRESOURCE, "",
+ "xyx");
+ }
+
+ @Test
+ public void testNoConversionForNonSnapshotVersionButSimilar() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-MYSNAPSHOT/org.eclipse.tycho.example.updatesite-0.1.0-MYSNAPSHOT.zip";
+ final ConversionResult conversionResult = RequestPathConverter.convert(
+ createRepositoryMock(null, PATH_TO_CORRECT_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.assertFalse(conversionResult.isPathConverted());
+ Assert.assertEquals(path, conversionResult.getConvertedPath());
+ }
+
+ @Test
+ public void testNoConversionForNonSnapshotVersionWithoutPointRelease() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1-BETA/org.eclipse.tycho.example.updatesite-0.1-BETA.zip";
+ final ConversionResult conversionResult = RequestPathConverter.convert(
+ createRepositoryMock(null, PATH_TO_CORRECT_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.assertFalse(conversionResult.isPathConverted());
+ Assert.assertEquals(path, conversionResult.getConvertedPath());
+ }
+
+ @Test
+ public void testNoConversionForNonSnapshotVersionWithSnapshotIdInContent() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-BETA/org.eclipse.tycho.example.updatesite-0.1.0-BETA.zip/plugins/org.eclipse.tycho.example.updatesite-SNAPSHOT.jar";
+ final ConversionResult conversionResult = RequestPathConverter.convert(
+ createRepositoryMock(null, PATH_TO_CORRECT_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.assertFalse(conversionResult.isPathConverted());
+ Assert.assertEquals(path, conversionResult.getConvertedPath());
+ }
+
+ @Test
+ public void testNoConversionForNonSnapshotVersionWithSnapshotIdInArtifactIdandInContent() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-BETA/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.zip/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar";
+ final ConversionResult conversionResult = RequestPathConverter.convert(
+ createRepositoryMock(null, PATH_TO_CORRECT_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.assertFalse(conversionResult.isPathConverted());
+ Assert.assertEquals(path, conversionResult.getConvertedPath());
+ }
+
+ @Test
+ public void testNoConversionForSnapshotVersionWithNonSnapshotIdInArtifactIdandSnapshotInContent() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/org.eclipse.tycho.example.updatesite-0.1.0-20100505.zip/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar";
+ final ConversionResult conversionResult = RequestPathConverter.convert(
+ createRepositoryMock(null, PATH_TO_CORRECT_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.assertFalse(conversionResult.isPathConverted());
+ Assert.assertEquals(path, conversionResult.getConvertedPath());
+ }
+
+ @Test
+ public void testNoConversionBecauseOfMissingSnapshotTagInMetadataXml() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.zip/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar";
+ try {
+ RequestPathConverter.convert(
+ createRepositoryMock(
+ "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/",
+ PATH_TO_MISSING_SNAPSHOT_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.fail("StorageException expected");
+ } catch (final StorageException e) {
+ Assert.assertTrue(e.getMessage().contains("current"));
+ }
+ }
+
+ @Test
+ public void testNoConversionBecauseOfMissingVersioningTagInMetadataXml() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.zip/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar";
+ try {
+ RequestPathConverter.convert(
+ createRepositoryMock(
+ "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/",
+ PATH_TO_MISSING_VERSIONING_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ Assert.fail("StorageException expected");
+ } catch (final StorageException e) {
+ Assert.assertTrue(e.getMessage().contains("versioning"));
+ }
+ }
+
+ @Test(expected = StorageException.class)
+ public void testNoConversionBecauseOfNotExistingMetadataXml() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.zip/plugins/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.jar";
+ RequestPathConverter.convert(
+ createRepositoryMock("org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/",
+ PATH_TO_NOT_EXISTING_MAVEN_METADATA_XML), new ResourceStoreRequest(path), true);
+ }
+
+ @Test
+ public void testNoMetaDataFound() throws Exception {
+ final String path = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/org.eclipse.tycho.example.updatesite-0.1.0-SNAPSHOT.zip";
+ final MavenRepository repository = createRepositoryMockNotFindingMetaData("org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/0.1.0-SNAPSHOT/");
+ final ConversionResult conversionResult = RequestPathConverter.convert(repository, new ResourceStoreRequest(
+ path), true);
+ Assert.assertFalse(conversionResult.isASnapshotAvailable());
+ }
+
+ /**
+ *
+ * @param snapshotVersion
+ * String that will be converted to an existing version number during path
+ * conversion.
+ * @param timestampVersion
+ * The version number that shall be used instead of the snapshot version string. The
+ * given time stamp version has to be the same like specified in the
+ * <code>maven-metadata.xml</code> file in the resources folder.
+ * @param pathInZip
+ * Path to a specific file inside the archive, relative to the archive root. If not
+ * empty a leading slash is needed.
+ * @param classifier
+ * String that describes the content of an artifact and is added to the artifact name
+ * as suffix of the version, separated by '<code>-</code>'. In case that no
+ * classification is required, an alternative method without this parameter can be
+ * used.
+ * @throws IllegalArtifactCoordinateException
+ * @throws IOException
+ * @throws ItemNotFoundException
+ */
+ private void assertPathConversion(final String snapshotVersion, final String timestampVersion,
+ final String pathInZip, final String classifier) throws Exception {
+ String appliedClassifier = "";
+ if (classifier.length() > 0) {
+ appliedClassifier = "-" + classifier;
+ }
+ final String parentPath = "org/eclipse/tycho/example/org.eclipse.tycho.example.updatesite/" + snapshotVersion
+ + "/";
+ final String snapshotFileName = "org.eclipse.tycho.example.updatesite-" + snapshotVersion + appliedClassifier
+ + ".zip" + Util.UNZIP_TYPE_EXTENSION;
+ final String snapshotPath = parentPath + snapshotFileName + pathInZip;
+ final String expectedLatestSnapshotPath = parentPath
+ + snapshotFileName.replace(snapshotVersion, timestampVersion) + pathInZip;
+ final ConversionResult conversionResult = RequestPathConverter.convert(
+ createRepositoryMock(parentPath, PATH_TO_CORRECT_MAVEN_METADATA_XML), new ResourceStoreRequest(
+ snapshotPath), true);
+ Assert.assertTrue(conversionResult.isPathConverted());
+ Assert.assertEquals(expectedLatestSnapshotPath, conversionResult.getConvertedPath());
+ }
+
+ /**
+ *
+ * @param snapshotVersion
+ * String that will be converted to an existing version number during path
+ * conversion.
+ * @param timestampVersion
+ * The version number that shall be used instead of the snapshot version string. The
+ * given time stamp version has to be the same like specified in the
+ * <code>maven-metadata.xml</code> file in the resources folder.
+ * @param pathInZip
+ * Path to a specific file inside the archive, relative to the archive root. If not
+ * empty a leading slash is needed.
+ * @throws Exception
+ */
+ private void assertPathConversion(final String snapshotVersion, final String timestampVersion,
+ final String pathInZip) throws Exception {
+ assertPathConversion(snapshotVersion, timestampVersion, pathInZip, "");
+ }
+
+ @SuppressWarnings("unchecked")
+ private MavenRepository createRepositoryMock(final String pathToArtifact, final String pathToTestMetadata) {
+ final String pathToMetadataXML = pathToArtifact + "maven-metadata.xml";
+ final MavenRepository repositoryMock = EasyMock.createMock(MavenRepository.class);
+ final Capture<ResourceStoreRequest>[] captures = new Capture[] { new Capture<ResourceStoreRequest>() };
+ try {
+ EasyMock.expect(repositoryMock.getId()).andStubReturn("");
+ EasyMock.expect(repositoryMock.retrieveItem(EasyMock.capture(captures[0]))).andAnswer(
+ new IAnswer<StorageFileItem>() {
+ @Override
+ public StorageFileItem answer() throws Throwable {
+ final StorageFileItem mavenMetaDataXml = EasyMock.createMock(StorageFileItem.class);
+ EasyMock.expect(mavenMetaDataXml.getInputStream()).andAnswer(new IAnswer<InputStream>() {
+ @Override
+ public InputStream answer() throws Throwable {
+ Assert.assertEquals(pathToMetadataXML, captures[0].getValue().getRequestPath());
+ final File metaDataFile = new File(pathToTestMetadata);
+ return new FileInputStream(metaDataFile);
+ }
+ });
+ EasyMock.replay(mavenMetaDataXml);
+ return mavenMetaDataXml;
+ }
+ });
+ } catch (final Exception e) {
+ throw new RuntimeException("Unable to create Maven Repo Mock", e);
+ }
+ EasyMock.replay(repositoryMock);
+ return repositoryMock;
+ }
+
+ private MavenRepository createRepositoryMockNotFindingMetaData(final String pathToArtifact) {
+ final String pathToMetadataXML = pathToArtifact + "maven-metadata.xml";
+ final MavenRepository repositoryMock = EasyMock.createMock(MavenRepository.class);
+ try {
+ EasyMock.expect(repositoryMock.getId()).andStubReturn("");
+ EasyMock.expect(repositoryMock.retrieveItem((ResourceStoreRequest) EasyMock.anyObject())).andThrow(
+ new ItemNotFoundException(new ResourceStoreRequest(pathToMetadataXML)));
+ } catch (final Exception e) {
+ throw new RuntimeException("Unable to create Maven Repo Mock", e);
+ }
+ EasyMock.replay(repositoryMock);
+ return repositoryMock;
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCacheTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCacheTest.java
new file mode 100644
index 0000000..41bf1c5
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/cache/UnzipCacheTest.java
@@ -0,0 +1,295 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.cache;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.eclipse.tycho.nexus.internal.plugin.cache.ConversionResult;
+import org.eclipse.tycho.nexus.internal.plugin.cache.UnzipCache;
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+import org.eclipse.tycho.nexus.internal.plugin.test.TestUtil;
+import org.eclipse.tycho.nexus.internal.plugin.test.UnzipRepositoryMock;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.StorageException;
+
+@SuppressWarnings("deprecation")
+public class UnzipCacheTest {
+ private static final String PATH_UP_TO_VERSION = "/ga/1.0.0-SNAPSHOT/archive-1.0.0-";
+
+ private static final String LATEST_VERSION = "20101013";
+ private static final String SNAPSHOT_REQUEST_PATH = "/ga/1.0.0-SNAPSHOT/archive-1.0.0-SNAPSHOT.zip-unzip";
+
+ private static final String PATH_TO_OLD_ZIP = "/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip";
+ private static final String PATH_TO_OLD_OTHER_ZIP = "/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip";
+ private static final String PATH_TO_LATEST_OTHER_ZIP = "/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip";
+ private static final String PATH_TO_LATEST_ZIP = "/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip";
+
+ private UnzipCache snapshotRepoUnzipCache;
+ private static File oldZip;
+ private static File oldOtherzip;
+ private static File latestOtherZip;
+
+ @Before
+ public void setupTestRepos() throws StorageException, ItemNotFoundException {
+ snapshotRepoUnzipCache = UnzipRepositoryMock.createUnzipRepository(RepositoryMock.createSnapshotRepo())
+ .getCache();
+
+ oldZip = snapshotRepoUnzipCache.getArchive(PATH_TO_OLD_ZIP);
+ oldOtherzip = snapshotRepoUnzipCache.getArchive(PATH_TO_OLD_OTHER_ZIP);
+ latestOtherZip = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_OTHER_ZIP);
+ assertTrue(oldOtherzip.exists());
+ assertTrue(oldZip.exists());
+ assertTrue(latestOtherZip.exists());
+ }
+
+ @Test
+ public void testCleanUpOldSnapshots() throws StorageException, ItemNotFoundException {
+
+ final File latestZip = new File(oldZip.getParentFile() + File.separator + "archive-1.0.0-20101013-2.zip");
+ assertFalse(latestZip.exists());
+
+ final ConversionResult conversionResult = new ConversionResult(SNAPSHOT_REQUEST_PATH, PATH_TO_LATEST_ZIP,
+ LATEST_VERSION, PATH_UP_TO_VERSION);
+
+ assertTrue(conversionResult.isPathConverted());
+
+ final File latestZipFromCache = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+ snapshotRepoUnzipCache.cleanSnapshots(conversionResult);
+ assertEquals(latestZip.getPath(), latestZipFromCache.getPath());
+
+ assertFalse(oldZip.exists());
+ assertFalse(oldOtherzip.exists());
+
+ assertTrue(latestZip.exists());
+ assertTrue(latestOtherZip.exists());
+ }
+
+ @Test
+ public void testCleanUpOldSnapshotsCurrentSnapshotAlreadyCached() throws StorageException, ItemNotFoundException {
+
+ final File latestZip = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+ assertTrue(latestZip.exists());
+ assertTrue(oldZip.exists());
+ assertTrue(oldOtherzip.exists());
+ assertTrue(latestOtherZip.exists());
+
+ final ConversionResult conversionResult = new ConversionResult("/ga/1.0.0-SNAPSHOT/archive-1.0.0-SNAPSHOT.zip",
+ PATH_TO_LATEST_ZIP, "20101013", PATH_UP_TO_VERSION);
+
+ assertTrue(conversionResult.isPathConverted());
+
+ final File latestZipFromCache = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+ snapshotRepoUnzipCache.cleanSnapshots(conversionResult);
+ assertEquals(latestZip.getPath(), latestZipFromCache.getPath());
+
+ assertFalse(oldZip.exists());
+ assertFalse(oldOtherzip.exists());
+
+ assertTrue(latestZip.exists());
+ assertTrue(latestOtherZip.exists());
+ }
+
+ @Test
+ public void testNoCleanUpOldSnapshotsNoConversion() throws StorageException, ItemNotFoundException {
+
+ final File latestZip = new File(oldZip.getParentFile() + File.separator + "archive-1.0.0-20101013-2.zip");
+ assertFalse(latestZip.exists());
+
+ final ConversionResult conversionResult = new ConversionResult(SNAPSHOT_REQUEST_PATH);
+
+ assertFalse(conversionResult.isPathConverted());
+
+ final File latestZipFromCache = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+ snapshotRepoUnzipCache.cleanSnapshots(conversionResult);
+ assertEquals(latestZip.getPath(), latestZipFromCache.getPath());
+
+ assertTrue(oldZip.exists());
+ assertTrue(oldOtherzip.exists());
+
+ assertTrue(latestZip.exists());
+ assertTrue(latestOtherZip.exists());
+ }
+
+ @Test
+ public void testNoSnapshotsCleanAll() throws ItemNotFoundException, IOException {
+ final File latestZipFromCache = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+
+ // modification of the cached file, so that we can check the recreation after the cache was cleaned
+ latestZipFromCache.delete();
+ latestZipFromCache.createNewFile();
+ final long modifiedFileLength = latestZipFromCache.length();
+
+ final ConversionResult conversionResult = new ConversionResult(SNAPSHOT_REQUEST_PATH, PATH_UP_TO_VERSION, false);
+
+ assertFalse(conversionResult.isPathConverted());
+ assertFalse(conversionResult.isASnapshotAvailable());
+
+ snapshotRepoUnzipCache.cleanSnapshots(conversionResult);
+ final File latestZipFromCache2 = snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+ assertTrue("Expected that the file would be deleted and recreated by the cache, but was not.",
+ modifiedFileLength != latestZipFromCache2.length());
+
+ assertFalse(oldZip.exists());
+ assertFalse(oldOtherzip.exists());
+ assertFalse(latestOtherZip.exists());
+
+ }
+
+ @Test
+ public void testCacheForThreadSafty() throws InterruptedException, ExecutionException {
+ final CountDownLatch startSignal = new CountDownLatch(1);
+ final CountDownLatch doneSignal = new CountDownLatch(40);
+
+ final ExecutorService executor = Executors.newCachedThreadPool();
+ final List<Future<Void>> results = new LinkedList<Future<Void>>();
+
+ for (int i = 0; i < 20; i++) {
+ final Callable<Void> c = new CacheStressWorker(startSignal, doneSignal, PATH_TO_OLD_ZIP);
+ results.add(executor.submit(c));
+ final Callable<Void> c2 = new CacheStressWorker(startSignal, doneSignal, PATH_TO_LATEST_ZIP);
+ results.add(executor.submit(c2));
+ }
+
+ //all threads: GO!
+ startSignal.countDown();
+
+ //Wait till all threads are done
+ doneSignal.await();
+
+ //Check all results: if an exception occured fail (probably an concurrency issue)
+ for (final Future<Void> submitResult : results) {
+ try {
+ submitResult.get();
+ } catch (final ExecutionException e) {
+ throw new ExecutionException("At least one thread fail to execute", e);
+ }
+ }
+
+ executor.shutdown();
+ }
+
+ class CacheStressWorker implements Callable<Void> {
+ private final CountDownLatch startSignal;
+ private final String archivePath;
+ private final CountDownLatch doneSignal;
+
+ CacheStressWorker(final CountDownLatch startSignal, final CountDownLatch doneSignal, final String archivePath) {
+ this.startSignal = startSignal;
+ this.archivePath = archivePath;
+ this.doneSignal = doneSignal;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ try {
+ startSignal.await();
+ return doWork();
+ } finally {
+ doneSignal.countDown();
+ }
+ }
+
+ public Void doWork() throws StorageException, ItemNotFoundException {
+ snapshotRepoUnzipCache.getArchive(archivePath);
+ return null;
+ }
+
+ }
+
+ @Test
+ public void testMultiThreadedCleanUpAndCreation() throws InterruptedException, ExecutionException {
+ final CountDownLatch startSignal = new CountDownLatch(1);
+ final CountDownLatch doneSignal = new CountDownLatch(40);
+
+ final ExecutorService executor = Executors.newCachedThreadPool();
+ final List<Future<Void>> results = new LinkedList<Future<Void>>();
+
+ for (int i = 0; i < 20; i++) {
+ final Callable<Void> c = new RequestWorker(startSignal, doneSignal, true);
+ results.add(executor.submit(c));
+ final Callable<Void> c2 = new RequestWorker(startSignal, doneSignal, false);
+ results.add(executor.submit(c2));
+ }
+
+ //all threads: GO!
+ startSignal.countDown();
+
+ //Wait till all threads are done
+ doneSignal.await();
+
+ //Check all results: if an exception occured fail (probably an concurrency issue)
+ for (final Future<Void> submitResult : results) {
+ try {
+ submitResult.get();
+ } catch (final ExecutionException e) {
+ throw new ExecutionException("At least one thread fail to execute", e);
+ }
+ }
+
+ executor.shutdown();
+ }
+
+ class RequestWorker implements Callable<Void> {
+
+ private final CountDownLatch startSignal;
+ private final boolean cleanup;
+ private final CountDownLatch doneSignal;
+
+ RequestWorker(final CountDownLatch startSignal, final CountDownLatch doneSignal, final boolean cleanup) {
+ this.startSignal = startSignal;
+ this.cleanup = cleanup;
+ this.doneSignal = doneSignal;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ try {
+ startSignal.await();
+ doWork();
+ } finally {
+ doneSignal.countDown();
+ }
+ return null;
+ }
+
+ void doWork() throws StorageException, ItemNotFoundException {
+
+ if (cleanup) {
+ snapshotRepoUnzipCache.getArchive(PATH_TO_LATEST_ZIP);
+ snapshotRepoUnzipCache.cleanSnapshots(new ConversionResult(SNAPSHOT_REQUEST_PATH, PATH_TO_LATEST_ZIP,
+ LATEST_VERSION, PATH_UP_TO_VERSION));
+ } else {
+ snapshotRepoUnzipCache.getArchive(PATH_TO_OLD_ZIP);
+ }
+ }
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ TestUtil.cleanUpTestFiles();
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItemTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItemTest.java
new file mode 100644
index 0000000..ab4c629
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZipAwareStorageCollectionItemTest.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.codehaus.plexus.logging.console.ConsoleLogger;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZipAwareStorageCollectionItem;
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+import org.eclipse.tycho.nexus.internal.plugin.test.TestUtil;
+import org.eclipse.tycho.nexus.internal.plugin.test.UnzipRepositoryMock;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+
+@SuppressWarnings("nls")
+public class ZipAwareStorageCollectionItemTest {
+
+ @Test
+ public void testListWithArchiveMember() throws ItemNotFoundException, IOException, AccessDeniedException,
+ NoSuchResourceStoreException, IllegalOperationException {
+ final RepositoryMock masterRepository = RepositoryMock.createMasterRepo();
+ final DefaultUnzipRepository unzipRepositoryMock = UnzipRepositoryMock.createUnzipRepository(masterRepository);
+ final StorageCollectionItem collectionStorageItem = (StorageCollectionItem) masterRepository
+ .createStorageItem("/dir/subdir");
+ final ZipAwareStorageCollectionItem zipAwareStorageCollectionItem = new ZipAwareStorageCollectionItem(
+ unzipRepositoryMock, collectionStorageItem, new ConsoleLogger());
+ TestUtil.assertMembers( //
+ new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION, //
+ "/dir/subdir/archive2.zip" + Util.UNZIP_TYPE_EXTENSION },//
+ new String[0], //
+ zipAwareStorageCollectionItem.list());
+ }
+
+ @Test
+ public void testListWithSnapshotArchives() throws Exception {
+ final RepositoryMock masterRepository = RepositoryMock.createSnapshotRepo();
+ final DefaultUnzipRepository unzipRepositoryMock = UnzipRepositoryMock.createUnzipRepository(masterRepository);
+ final StorageCollectionItem collectionStorageItem = (StorageCollectionItem) masterRepository
+ .createStorageItem("/ga/1.0.0-SNAPSHOT");
+ final ZipAwareStorageCollectionItem zipAwareStorageCollectionItem = new ZipAwareStorageCollectionItem(
+ unzipRepositoryMock, collectionStorageItem, new ConsoleLogger());
+ TestUtil.assertMembers(new String[] {
+ "/ga/1.0.0-SNAPSHOT/archive-1.0.0-SNAPSHOT-juhu.zip" + Util.UNZIP_TYPE_EXTENSION,
+ "/ga/1.0.0-SNAPSHOT/archive-1.0.0-SNAPSHOT.zip" + Util.UNZIP_TYPE_EXTENSION }, new String[0],
+ zipAwareStorageCollectionItem.list());
+
+ }
+
+ @Test
+ public void testModificationTimes() throws ItemNotFoundException, IOException, AccessDeniedException,
+ NoSuchResourceStoreException, IllegalOperationException {
+ final RepositoryMock masterRepository = RepositoryMock.createMasterRepo();
+ final DefaultUnzipRepository unzipRepositoryMock = UnzipRepositoryMock.createUnzipRepository(masterRepository);
+ final StorageCollectionItem collectionStorageItem = (StorageCollectionItem) masterRepository
+ .createStorageItem("/dir/subdir");
+ final ZipAwareStorageCollectionItem zipAwareStorageCollectionItem = new ZipAwareStorageCollectionItem(
+ unzipRepositoryMock, collectionStorageItem, new ConsoleLogger());
+ final ArrayList<StorageItem> items = new ArrayList<StorageItem>(zipAwareStorageCollectionItem.list());
+ System.out.println(items.get(0).getPath());
+ System.out.println(items.get(0).getModified());
+ final File archiveFile = new File("./src/test/resources/masterRepo/dir/subdir/archive.zip");
+ final File archive2File = new File("./src/test/resources/masterRepo/dir/subdir/archive2.zip");
+
+ Assert.assertEquals(archiveFile.lastModified(), items.get(0).getModified());
+ Assert.assertEquals(archive2File.lastModified(), items.get(1).getModified());
+ }
+
+ @Test
+ public void testListWithoutArchiveMember() throws ItemNotFoundException, IOException, AccessDeniedException,
+ NoSuchResourceStoreException, IllegalOperationException {
+ final RepositoryMock masterRepository = RepositoryMock.createMasterRepo();
+ final DefaultUnzipRepository unzipRepositoryMock = UnzipRepositoryMock.createUnzipRepository(masterRepository);
+ final StorageCollectionItem collectionStorageItem = (StorageCollectionItem) masterRepository
+ .createStorageItem("/dir");
+ final ZipAwareStorageCollectionItem zipAwareStorageCollectionItem = new ZipAwareStorageCollectionItem(
+ unzipRepositoryMock, collectionStorageItem, new ConsoleLogger());
+ TestUtil.assertMembers(new String[] { "/dir/subdir" }, new String[0], zipAwareStorageCollectionItem.list());
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ TestUtil.cleanUpTestFiles();
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItemTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItemTest.java
new file mode 100644
index 0000000..8ac78ac
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedItemTest.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.io.IOException;
+
+import org.codehaus.plexus.logging.console.ConsoleLogger;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZippedItem;
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+import org.eclipse.tycho.nexus.internal.plugin.test.TestUtil;
+import org.eclipse.tycho.nexus.internal.plugin.test.UnzipRepositoryMock;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.item.DefaultStorageCollectionItem;
+import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
+import org.sonatype.nexus.util.ItemPathUtils;
+
+@SuppressWarnings("nls")
+public class ZippedItemTest {
+ private final String pathToArchive = "/dir/subdir/archive.zip";
+ private final String pathToUnzippedArchive = "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION;
+ private final DefaultUnzipRepository unzipReposMock = UnzipRepositoryMock.createUnzipRepository(RepositoryMock
+ .createMasterRepo());
+
+ @Test
+ public void testZippedItemInRoot() throws ItemNotFoundException, IOException {
+ final String pathInZip = "test.txt";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ Assert.assertEquals(pathInZip, zippedItem.getPathInZip());
+ Assert.assertEquals(pathToUnzippedArchive + "/" + pathInZip, zippedItem.getPath());
+ Assert.assertEquals("text/plain", zippedItem.getMimeType().toString());
+
+ final DefaultStorageFileItem zippedStorageItem = (DefaultStorageFileItem) zippedItem.getZippedStorageItem();
+ Assert.assertEquals(pathToUnzippedArchive + "/" + pathInZip, zippedStorageItem.getPath());
+ Assert.assertEquals(pathToUnzippedArchive, zippedStorageItem.getParentPath());
+ Assert.assertEquals("text/plain", zippedStorageItem.getMimeType().toString());
+ TestUtil.assertContent("some content", zippedStorageItem);
+ }
+
+ @Test
+ public void testZippedItemInDir() throws ItemNotFoundException, IOException {
+ final String pathInZip = "dir/test.txt";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ Assert.assertEquals(pathInZip, zippedItem.getPathInZip());
+ Assert.assertEquals(pathToUnzippedArchive + "/" + pathInZip, zippedItem.getPath());
+ Assert.assertEquals("text/plain", zippedItem.getMimeType().toString());
+
+ final DefaultStorageFileItem zippedStorageItem = (DefaultStorageFileItem) zippedItem.getZippedStorageItem();
+ Assert.assertEquals(pathToUnzippedArchive + "/" + pathInZip, zippedStorageItem.getPath());
+ Assert.assertEquals(pathToUnzippedArchive + "/dir", zippedStorageItem.getParentPath());
+ Assert.assertEquals("text/plain", zippedStorageItem.getMimeType().toString());
+
+ TestUtil.assertContent("some file content", zippedStorageItem);
+ }
+
+ @Test(expected = ItemNotFoundException.class)
+ public void testZippedItemNotExisting() throws ItemNotFoundException, IOException {
+ final String pathInZip = "x.txt";
+ createZippedItem(pathInZip);
+ }
+
+ @Test(expected = ItemNotFoundException.class)
+ public void testZippedItemNotExisting2() throws ItemNotFoundException, IOException {
+ final String pathInZip = "dir/x.txt";
+ createZippedItem(pathInZip);
+ }
+
+ @Test
+ public void testZippedItemEmptyPath() throws ItemNotFoundException, IOException {
+ final String pathInZip = "";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ Assert.assertEquals(pathInZip, zippedItem.getPathInZip());
+ Assert.assertEquals(pathToUnzippedArchive, zippedItem.getPath());
+ Assert.assertNull(zippedItem.getMimeType());
+
+ final DefaultStorageCollectionItem zippedStorageItem = (DefaultStorageCollectionItem) zippedItem
+ .getZippedStorageItem();
+ Assert.assertEquals(pathToUnzippedArchive, zippedStorageItem.getPath());
+ }
+
+ @Test
+ public void testZippedItemNullPath() throws ItemNotFoundException, IOException {
+ final String pathInZip = null;
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ Assert.assertEquals("", zippedItem.getPathInZip());
+ Assert.assertEquals(pathToUnzippedArchive, zippedItem.getPath());
+ Assert.assertNull(zippedItem.getMimeType());
+
+ final DefaultStorageCollectionItem zippedStorageItem = (DefaultStorageCollectionItem) zippedItem
+ .getZippedStorageItem();
+ Assert.assertEquals(pathToUnzippedArchive, zippedStorageItem.getPath());
+ }
+
+ @Test
+ public void testZippedItemDir() throws ItemNotFoundException, IOException {
+ final String pathInZip = "dir";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ Assert.assertEquals(pathInZip, zippedItem.getPathInZip());
+ Assert.assertEquals(pathToUnzippedArchive + "/" + pathInZip, zippedItem.getPath());
+ Assert.assertNull(zippedItem.getMimeType());
+
+ final DefaultStorageCollectionItem zippedStorageItem = (DefaultStorageCollectionItem) zippedItem
+ .getZippedStorageItem();
+ Assert.assertEquals(pathToUnzippedArchive + "/" + pathInZip, zippedStorageItem.getPath());
+ }
+
+ @Test
+ public void testZippedItemDirWithTrailingSlash() throws ItemNotFoundException, IOException {
+ final String pathInZip = "dir/";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(pathInZip), zippedItem.getPathInZip());
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(pathToUnzippedArchive + "/" + pathInZip),
+ zippedItem.getPath());
+ Assert.assertNull(zippedItem.getMimeType());
+
+ final DefaultStorageCollectionItem zippedStorageItem = (DefaultStorageCollectionItem) zippedItem
+ .getZippedStorageItem();
+ Assert.assertEquals(ItemPathUtils.cleanUpTrailingSlash(pathToUnzippedArchive + "/" + pathInZip),
+ zippedStorageItem.getPath());
+ }
+
+ @Test(expected = LocalStorageException.class)
+ public void testZipItemNotFound() throws Exception {
+ final String pathInZip = "test.txt";
+ new ZippedItem(unzipReposMock, "/", pathInZip, 0L, new ConsoleLogger());
+ }
+
+ @Test
+ public void testListMembersEmptyPath() throws ItemNotFoundException, IOException {
+ final String pathInZip = "";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ TestUtil.assertMembers(new String[] { pathToUnzippedArchive + "/dir" }, new String[] { pathToUnzippedArchive
+ + "/test.txt" }, zippedItem.listMembers());
+ }
+
+ @Test
+ public void testListMembersDir() throws ItemNotFoundException, IOException {
+ final String pathInZip = "dir/";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+
+ TestUtil.assertMembers(new String[] { pathToUnzippedArchive + "/" + pathInZip + "subdir" },
+ new String[] { pathToUnzippedArchive + "/" + pathInZip + "test.txt" }, zippedItem.listMembers());
+ }
+
+ @Test(expected = LocalStorageException.class)
+ public void testListMembersOnFile() throws ItemNotFoundException, IOException {
+ final String pathInZip = "test.txt";
+ final ZippedItem zippedItem = createZippedItem(pathInZip);
+ zippedItem.listMembers();
+ }
+
+ private ZippedItem createZippedItem(final String pathInZip) throws LocalStorageException, ItemNotFoundException {
+ return new ZippedItem(unzipReposMock, pathToArchive, pathInZip, System.currentTimeMillis(), new ConsoleLogger());
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ TestUtil.cleanUpTestFiles();
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItemTest.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItemTest.java
new file mode 100644
index 0000000..c20cc52
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/storage/ZippedStorageCollectionItemTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.storage;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+import junit.framework.Assert;
+
+import org.codehaus.plexus.logging.console.ConsoleLogger;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.eclipse.tycho.nexus.internal.plugin.storage.Util;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZippedItem;
+import org.eclipse.tycho.nexus.internal.plugin.storage.ZippedStorageCollectionItem;
+import org.eclipse.tycho.nexus.internal.plugin.test.RepositoryMock;
+import org.eclipse.tycho.nexus.internal.plugin.test.TestUtil;
+import org.eclipse.tycho.nexus.internal.plugin.test.UnzipRepositoryMock;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.item.StorageItem;
+
+@SuppressWarnings("nls")
+public class ZippedStorageCollectionItemTest {
+ private DefaultUnzipRepository unzipRepositoryMock;
+
+ @Before
+ public void setupRepo() {
+ unzipRepositoryMock = UnzipRepositoryMock.createUnzipRepository(RepositoryMock.createMasterRepo());
+ }
+
+ @Test
+ public void testList() throws ItemNotFoundException, IOException, AccessDeniedException,
+ NoSuchResourceStoreException, IllegalOperationException {
+ final ZippedItem zippedItem = new ZippedItem(unzipRepositoryMock, "/dir/subdir/archive.zip", "dir", 0L,
+ new ConsoleLogger());
+ final ZippedStorageCollectionItem zippedStorageCollectionItem = new ZippedStorageCollectionItem(zippedItem);
+ TestUtil.assertMembers(new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir/subdir" },
+ new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir/test.txt" },
+ zippedStorageCollectionItem.list());
+ }
+
+ @Test
+ public void testListInRoot() throws ItemNotFoundException, IOException, AccessDeniedException,
+ NoSuchResourceStoreException, IllegalOperationException {
+ final ZippedItem zippedItem = new ZippedItem(unzipRepositoryMock, "/dir/subdir/archive.zip", "", 0L,
+ new ConsoleLogger());
+ final ZippedStorageCollectionItem zippedStorageCollectionItem = new ZippedStorageCollectionItem(zippedItem);
+ TestUtil.assertMembers(new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/dir" },
+ new String[] { "/dir/subdir/archive.zip" + Util.UNZIP_TYPE_EXTENSION + "/test.txt" },
+ zippedStorageCollectionItem.list());
+ }
+
+ @Test
+ public void testModificationTimeInRoot() throws ItemNotFoundException, IOException, AccessDeniedException,
+ NoSuchResourceStoreException, IllegalOperationException {
+ final File file = new File("./src/test/resources/" + "masterRepo" + "/dir/subdir/archive.zip");
+ final long time = file.lastModified();
+
+ final ZippedItem zipItem = new ZippedItem(unzipRepositoryMock, "/dir/subdir/archive.zip", "", time,
+ new ConsoleLogger());
+ final ZippedStorageCollectionItem zipStorageCollectionItem = new ZippedStorageCollectionItem(zipItem);
+ Assert.assertEquals(time, zipStorageCollectionItem.getModified());
+
+ //timestamps of zip entries are not time zone aware, therefore all entries shall inherit
+ //the timestamp of the zip file itself
+ final Collection<StorageItem> list = zipStorageCollectionItem.list();
+ for (final StorageItem storageItem : list) {
+ Assert.assertEquals(time, storageItem.getModified());
+ }
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ TestUtil.cleanUpTestFiles();
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/FSLocalRepositoryStorageMock.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/FSLocalRepositoryStorageMock.java
new file mode 100644
index 0000000..22ce32d
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/FSLocalRepositoryStorageMock.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.codehaus.plexus.util.FileUtils;
+import org.sonatype.nexus.mime.DefaultMimeSupport;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.item.DefaultLinkPersister;
+import org.sonatype.nexus.proxy.repository.Repository;
+import org.sonatype.nexus.proxy.storage.local.fs.DefaultFSLocalRepositoryStorage;
+import org.sonatype.nexus.proxy.storage.local.fs.DefaultFSPeer;
+import org.sonatype.nexus.proxy.wastebasket.DefaultWastebasket;
+
+public class FSLocalRepositoryStorageMock extends DefaultFSLocalRepositoryStorage {
+
+ private final File baseDir = FileUtils.createTempFile("nexus-unzip" + File.separator, "test-storage", null);
+
+ public FSLocalRepositoryStorageMock() {
+ super(new DefaultWastebasket(), new DefaultLinkPersister(), new DefaultMimeSupport(),
+ new HashMap<String, Long>(), new DefaultFSPeer());
+
+ if (baseDir.exists()) {
+ try {
+ FileUtils.forceDelete(baseDir);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public File getBaseDir(final Repository repository, final ResourceStoreRequest request) {
+ return baseDir;
+ }
+
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/RepositoryMock.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/RepositoryMock.java
new file mode 100644
index 0000000..1c03859
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/RepositoryMock.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.sonatype.nexus.configuration.Configurator;
+import org.sonatype.nexus.configuration.model.CRepositoryExternalConfigurationHolderFactory;
+import org.sonatype.nexus.proxy.AccessDeniedException;
+import org.sonatype.nexus.proxy.IllegalOperationException;
+import org.sonatype.nexus.proxy.ItemNotFoundException;
+import org.sonatype.nexus.proxy.LocalStorageException;
+import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
+import org.sonatype.nexus.proxy.ResourceStoreRequest;
+import org.sonatype.nexus.proxy.StorageException;
+import org.sonatype.nexus.proxy.item.ContentLocator;
+import org.sonatype.nexus.proxy.item.DefaultRepositoryItemUidFactory;
+import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
+import org.sonatype.nexus.proxy.item.RepositoryItemUidFactory;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+import org.sonatype.nexus.proxy.item.uid.DefaultRepositoryItemUidAttributeManager;
+import org.sonatype.nexus.proxy.item.uid.RepositoryItemUidAttributeManager;
+import org.sonatype.nexus.proxy.registry.ContentClass;
+import org.sonatype.nexus.proxy.repository.AbstractRepository;
+import org.sonatype.nexus.proxy.repository.RepositoryKind;
+import org.sonatype.nexus.util.ItemPathUtils;
+
+@SuppressWarnings({ "nls", "deprecation" })
+public class RepositoryMock extends AbstractRepository {
+
+ private final String repositoryId;
+ private boolean behaveAsProxy = false;
+
+ /**
+ * Creates a Repository with dummy snapshot content from src/test/resources/snapshotRepo
+ *
+ * @return
+ */
+ public static RepositoryMock createSnapshotRepo() {
+ return new RepositoryMock("snapshotRepo");
+ }
+
+ /**
+ * Creates a Repository with dummy content from src/test/resources/masterRepo
+ *
+ * @return
+ */
+ public static RepositoryMock createMasterRepo() {
+ return new RepositoryMock("masterRepo");
+ }
+
+ private RepositoryMock(final String repositoryId) {
+ this.repositoryId = repositoryId;
+ }
+
+ @Override
+ protected CRepositoryExternalConfigurationHolderFactory<?> getExternalConfigurationHolderFactory() {
+ return null;
+ }
+
+ @Override
+ protected Configurator getConfigurator() {
+ return null;
+ }
+
+ @Override
+ public String getId() {
+ return repositoryId;
+ }
+
+ @Override
+ public StorageItem doRetrieveItem(final ResourceStoreRequest request) throws IllegalOperationException,
+ ItemNotFoundException, StorageException {
+ return RepositoryMock.this.createStorageItem(request.getRequestPath());
+ }
+
+ @Override
+ public StorageItem retrieveItem(final ResourceStoreRequest request) throws IllegalOperationException,
+ ItemNotFoundException, StorageException, AccessDeniedException {
+ if (request.getRequestPath().equals("") || request.getRequestPath().equals("/")) {
+ throw new ItemNotFoundException(request);
+ }
+ return doRetrieveItem(request);
+ }
+
+ @Override
+ protected RepositoryItemUidFactory getRepositoryItemUidFactory() {
+ return new DefaultRepositoryItemUidFactory();
+ }
+
+ @Override
+ public RepositoryItemUidAttributeManager getRepositoryItemUidAttributeManager() {
+ return new DefaultRepositoryItemUidAttributeManager();
+ }
+
+ public StorageItem createStorageItem(final String path) throws StorageException, ItemNotFoundException,
+ IllegalOperationException {
+ final String pathWithoutTrailingSlash = ItemPathUtils.cleanUpTrailingSlash(path);
+ final File file = new File("./src/test/resources/" + repositoryId + pathWithoutTrailingSlash);
+ if (!file.exists()) {
+ throw new ItemNotFoundException(new ResourceStoreRequest(pathWithoutTrailingSlash));
+ }
+
+ if (file.isFile()) {
+ if (path.endsWith("/")) {
+ // in nexus repositories files are not found if the path ends with a trailing slash
+ throw new ItemNotFoundException(new ResourceStoreRequest(path));
+ }
+ try {
+ final ContentLocator contentLocator = EasyMock.createMock(ContentLocator.class);
+ // we need a new input stream for every getContent() call
+ EasyMock.expect(contentLocator.getContent()).andAnswer(new IAnswer<InputStream>() {
+ @Override
+ public InputStream answer() throws Throwable {
+ return new FileInputStream(file);
+ }
+ }).anyTimes();
+ EasyMock.expect(contentLocator.getMimeType()).andAnswer(new IAnswer<String>() {
+ @Override
+ public String answer() throws Throwable {
+ return URLConnection.guessContentTypeFromName(pathWithoutTrailingSlash);
+ }
+ }).anyTimes();
+ EasyMock.replay(contentLocator);
+ final DefaultStorageFileItem defaultStorageFileItem = new DefaultStorageFileItem(this,
+ new ResourceStoreRequest(pathWithoutTrailingSlash), true, false, contentLocator);
+ defaultStorageFileItem.setModified(file.lastModified());
+
+ return defaultStorageFileItem;
+ } catch (final IOException e) {
+ throw new LocalStorageException(e);
+ }
+ } else {
+ if (behaveAsProxy) {
+ // a proxy repository throws an ItemNotFoundException for folders existing in the
+ // proxied repository if no artefact within this folder hierarchy was cached in
+ // the proxy repository
+ // to test this behavior an ItemNotFoundException is thrown here
+ throw new ItemNotFoundException(new ResourceStoreRequest(pathWithoutTrailingSlash));
+ }
+ try {
+ final StorageCollectionItem collectionItem = EasyMock.createMock(StorageCollectionItem.class);
+ EasyMock.expect(collectionItem.getRepositoryId()).andStubReturn(repositoryId);
+ EasyMock.expect(collectionItem.getPath()).andStubReturn(pathWithoutTrailingSlash);
+ EasyMock.expect(collectionItem.getResourceStoreRequest()).andAnswer(
+ new IAnswer<ResourceStoreRequest>() {
+ @Override
+ public ResourceStoreRequest answer() throws Throwable {
+ return new ResourceStoreRequest(pathWithoutTrailingSlash);
+ }
+ });
+ EasyMock.expect(collectionItem.list()).andAnswer(new IAnswer<Collection<StorageItem>>() {
+
+ @Override
+ public Collection<StorageItem> answer() throws Throwable {
+ final Collection<StorageItem> members = new LinkedList<StorageItem>();
+ final String[] memberNames = file.list();
+ for (final String memberName : memberNames) {
+ members.add(createStorageItem(pathWithoutTrailingSlash + "/" + memberName));
+ }
+ return members;
+ }
+ });
+ EasyMock.replay(collectionItem);
+ return collectionItem;
+ } catch (final AccessDeniedException e) {
+ throw new LocalStorageException(e);
+ } catch (final NoSuchResourceStoreException e) {
+ throw new LocalStorageException(e);
+ }
+ }
+ }
+
+ public void setBehaveAsProxy(final boolean behaveAsProxy) {
+ this.behaveAsProxy = behaveAsProxy;
+ }
+
+ @Override
+ public RepositoryKind getRepositoryKind() {
+ return null;
+ }
+
+ @Override
+ public ContentClass getRepositoryContentClass() {
+ return null;
+ }
+
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/TestUtil.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/TestUtil.java
new file mode 100644
index 0000000..b90109e
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/TestUtil.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.codehaus.plexus.util.FileUtils;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
+import org.sonatype.nexus.proxy.item.StorageCollectionItem;
+import org.sonatype.nexus.proxy.item.StorageFileItem;
+import org.sonatype.nexus.proxy.item.StorageItem;
+
+@SuppressWarnings("nls")
+@Ignore
+public class TestUtil {
+
+ public static void assertContent(final String expectedContent, final DefaultStorageFileItem item)
+ throws IOException {
+ Assert.assertNotNull(item);
+ final byte[] content = new byte[expectedContent.length()];
+ final int len = item.getInputStream().read(content);
+ Assert.assertEquals(expectedContent, new String(content, 0, len));
+ }
+
+ public static void assertMembers(final String[] expectedCollections, final String[] expectedFiles,
+ final Collection<StorageItem> actualMembers) {
+ final List<String> expectedCollectionsList = new ArrayList<String>(Arrays.asList(expectedCollections));
+ final List<String> expectedFilesList = new ArrayList<String>(Arrays.asList(expectedFiles));
+ Assert.assertEquals(expectedCollections.length + expectedFiles.length, actualMembers.size());
+ final Iterator<StorageItem> actualMemberIt = actualMembers.iterator();
+ while (actualMemberIt.hasNext()) {
+ final StorageItem actualMember = actualMemberIt.next();
+ if (actualMember instanceof StorageCollectionItem) {
+ Assert.assertTrue("unexpected collection member [" + actualMember.getPath() + "]",
+ expectedCollectionsList.remove(actualMember.getPath()));
+ } else if (actualMember instanceof StorageFileItem) {
+ Assert.assertTrue("unexpected file member [" + actualMember.getPath() + "]",
+ expectedFilesList.remove(actualMember.getPath()));
+ } else {
+ Assert.fail("unexpected storage item [" + actualMember.getPath() + "]");
+ }
+ }
+ Assert.assertTrue(expectedCollectionsList.isEmpty());
+ Assert.assertTrue(expectedFilesList.isEmpty());
+ }
+
+ public static void assertMembers(final String[] expectedCollections, final String[] expectedFiles,
+ final StorageItem[] actualMembers) {
+ assertMembers(expectedCollections, expectedFiles, Arrays.asList(actualMembers));
+ }
+
+ public static StorageItem findItem(final String path, final Collection<StorageItem> items) {
+ for (final StorageItem item : items) {
+ if (path.equals(item.getPath())) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public static void cleanUpTestFiles() {
+ try {
+ FileUtils.deleteDirectory(FileUtils.createTempFile("nexus-unzip" + File.separator, "", null)
+ .getParentFile());
+ } catch (final IOException e) {
+ System.out.println("Warning: Test clean up: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/UnzipRepositoryMock.java b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/UnzipRepositoryMock.java
new file mode 100644
index 0000000..dd435e0
--- /dev/null
+++ b/src/test/java/org/eclipse/tycho/nexus/internal/plugin/test/UnzipRepositoryMock.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 SAP AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.nexus.internal.plugin.test;
+
+import org.easymock.EasyMock;
+import org.eclipse.tycho.nexus.internal.plugin.DefaultUnzipRepository;
+import org.sonatype.nexus.proxy.attributes.AttributesHandler;
+import org.sonatype.nexus.proxy.item.DefaultRepositoryItemUidFactory;
+import org.sonatype.nexus.proxy.item.RepositoryItemUidFactory;
+import org.sonatype.nexus.proxy.repository.LocalStatus;
+import org.sonatype.nexus.proxy.repository.Repository;
+import org.sonatype.nexus.proxy.storage.local.LocalRepositoryStorage;
+
+public class UnzipRepositoryMock extends DefaultUnzipRepository {
+
+ private final Repository masterRepository;
+ private final LocalRepositoryStorage localStorage = new FSLocalRepositoryStorageMock();
+
+ public static DefaultUnzipRepository createUnzipRepository(final Repository masterRepo) {
+ return new UnzipRepositoryMock(masterRepo);
+ }
+
+ private UnzipRepositoryMock(final Repository masterRepository) {
+ super();
+ this.masterRepository = masterRepository;
+ }
+
+ @Override
+ public AttributesHandler getAttributesHandler() {
+ return EasyMock.createNiceMock(AttributesHandler.class);
+ }
+
+ @Override
+ public String getId() {
+ return UnzipRepositoryMock.class.getName();
+ }
+
+ @Override
+ public LocalRepositoryStorage getLocalStorage() {
+ return localStorage;
+ }
+
+ @Override
+ protected RepositoryItemUidFactory getRepositoryItemUidFactory() {
+ return new DefaultRepositoryItemUidFactory();
+ }
+
+ @Override
+ public LocalStatus getLocalStatus() {
+ return LocalStatus.IN_SERVICE;
+ }
+
+ @Override
+ public Repository getMasterRepository() {
+ return masterRepository;
+ }
+
+ @Override
+ public boolean isUseVirtualVersion() {
+ return true;
+ }
+
+}
diff --git a/src/test/resources/0.6.1-SNAPSHOT/maven-metadata.xml b/src/test/resources/0.6.1-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..4fe08dd
--- /dev/null
+++ b/src/test/resources/0.6.1-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata modelVersion="1.1.0">
+ <groupId>org.eclipse.tycho.nexus.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.target</artifactId>
+ <version>0.6.1-SNAPSHOT</version>
+ <versioning>
+ <snapshot>
+ <timestamp>20110718.111322</timestamp>
+ <buildNumber>2</buildNumber>
+ </snapshot>
+ <lastUpdated>20110718111322</lastUpdated>
+ <snapshotVersions>
+ <snapshotVersion>
+ <extension>jar</extension>
+ <value>0.6.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <extension>pom</extension>
+ <value>0.6.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <classifier>p2metadata</classifier>
+ <extension>xml</extension>
+ <value>0.6.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <classifier>p2artifacts</classifier>
+ <extension>xml</extension>
+ <value>0.6.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ </snapshotVersions>
+ </versioning>
+</metadata>
diff --git a/src/test/resources/0.7.1-SNAPSHOT/maven-metadata.xml b/src/test/resources/0.7.1-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..46d34f1
--- /dev/null
+++ b/src/test/resources/0.7.1-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata modelVersion="1.1.0">
+ <groupId>org.eclipse.tycho.nexus.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.target</artifactId>
+ <version>0.7.1-SNAPSHOT</version>
+ <versioning>
+ <snapshot>
+ <timestamp>20110718.111322</timestamp>
+ <buildNumber>2</buildNumber>
+ </snapshot>
+ <lastUpdated>20110718111322</lastUpdated>
+ <snapshotVersions>
+ <snapshotVersion>
+ <extension>jar</extension>
+ <value>0.7.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <extension>pom</extension>
+ <value>0.7.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <classifier>p2metadata</classifier>
+ <extension>xml</extension>
+ <value>0.7.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <classifier>p2artifacts</classifier>
+ <extension>xml</extension>
+ <value>0.7.1-20110718.111322-2</value>
+ <updated>20110718111322</updated>
+ </snapshotVersion>
+ </snapshotVersions>
+ </versioning>
+</metadata>
diff --git a/src/test/resources/emptyArchive.zip-unzipped/create.txt b/src/test/resources/emptyArchive.zip-unzipped/create.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/test/resources/emptyArchive.zip-unzipped/create.txt
diff --git a/src/test/resources/masterRepo/dir/a.txt b/src/test/resources/masterRepo/dir/a.txt
new file mode 100644
index 0000000..bd6be1d
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/a.txt
@@ -0,0 +1 @@
+content of a.txt \ No newline at end of file
diff --git a/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/subdir/a.txt b/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/subdir/a.txt
new file mode 100644
index 0000000..aa6c40b
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/subdir/a.txt
@@ -0,0 +1 @@
+some more content \ No newline at end of file
diff --git a/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/test.txt b/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/test.txt
new file mode 100644
index 0000000..a3b9373
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/dir/test.txt
@@ -0,0 +1 @@
+some file content \ No newline at end of file
diff --git a/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/test.txt b/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/test.txt
new file mode 100644
index 0000000..f0eec86
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/subdir/archive.zip-unzipped/test.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/subdir/a.txt b/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/subdir/a.txt
new file mode 100644
index 0000000..aa6c40b
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/subdir/a.txt
@@ -0,0 +1 @@
+some more content \ No newline at end of file
diff --git a/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/test.txt b/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/test.txt
new file mode 100644
index 0000000..a3b9373
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/dir/test.txt
@@ -0,0 +1 @@
+some file content \ No newline at end of file
diff --git a/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/test.txt b/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/test.txt
new file mode 100644
index 0000000..f0eec86
--- /dev/null
+++ b/src/test/resources/masterRepo/dir/subdir/archive2.zip-unzipped/test.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/src/test/resources/maven-metadata-latest-is-not-a-snapshot.xml b/src/test/resources/maven-metadata-latest-is-not-a-snapshot.xml
new file mode 100644
index 0000000..b756f92
--- /dev/null
+++ b/src/test/resources/maven-metadata-latest-is-not-a-snapshot.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+ <groupId>org.eclipse.tycho.nexus.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.target</artifactId>
+ <versioning>
+ <latest>0.7.0</latest>
+ <release>0.7.0</release>
+ <versions>
+ <version>0.2.0</version>
+ <version>0.3.0</version>
+ <version>0.4.0</version>
+ <version>0.4.1</version>
+ <version>0.4.2</version>
+ <version>0.5.0</version>
+ <version>0.5.1-SNAPSHOT</version>
+ <version>0.6.0</version>
+ <version>0.6.1-SNAPSHOT</version>
+ <version>0.7.0-SNAPSHOT</version>
+ <version>0.7.0</version>
+ </versions>
+ <lastUpdated>20110718111322</lastUpdated>
+ </versioning>
+</metadata>
diff --git a/src/test/resources/maven-metadata.xml b/src/test/resources/maven-metadata.xml
new file mode 100644
index 0000000..7530d49
--- /dev/null
+++ b/src/test/resources/maven-metadata.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata xsi:schemaLocation="http://maven.apache.org/METADATA/1.0.0 http://maven.apache.org/xsd/metadata-1.0.0.xsd" xmlns="http://maven.apache.org/METADATA/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <groupId>org.eclipse.tycho.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.updatesite</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <versioning>
+ <snapshot>
+ <timestamp>20100505.133931</timestamp>
+ <buildNumber>1</buildNumber>
+ </snapshot>
+ <lastUpdated>20100526153505</lastUpdated>
+ </versioning>
+</metadata> \ No newline at end of file
diff --git a/src/test/resources/missingSnapshot-maven-metadata.xml b/src/test/resources/missingSnapshot-maven-metadata.xml
new file mode 100644
index 0000000..6bcc617
--- /dev/null
+++ b/src/test/resources/missingSnapshot-maven-metadata.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata xsi:schemaLocation="http://maven.apache.org/METADATA/1.0.0 http://maven.apache.org/xsd/metadata-1.0.0.xsd" xmlns="http://maven.apache.org/METADATA/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <groupId>org.eclipse.tycho.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.updatesite</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <versioning>
+ <lastUpdated>20100526153505</lastUpdated>
+ </versioning>
+</metadata> \ No newline at end of file
diff --git a/src/test/resources/missingVersioning-maven-metadata.xml b/src/test/resources/missingVersioning-maven-metadata.xml
new file mode 100644
index 0000000..d0e6af6
--- /dev/null
+++ b/src/test/resources/missingVersioning-maven-metadata.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata xsi:schemaLocation="http://maven.apache.org/METADATA/1.0.0 http://maven.apache.org/xsd/metadata-1.0.0.xsd" xmlns="http://maven.apache.org/METADATA/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <groupId>org.eclipse.tycho.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.updatesite</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+</metadata> \ No newline at end of file
diff --git a/src/test/resources/outer-maven-metadata-without-release.xml b/src/test/resources/outer-maven-metadata-without-release.xml
new file mode 100644
index 0000000..489eb75
--- /dev/null
+++ b/src/test/resources/outer-maven-metadata-without-release.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+ <groupId>org.eclipse.tycho.nexus.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.target</artifactId>
+ <versioning>
+ <versions>
+ <version>0.7.1-SNAPSHOT</version>
+ </versions>
+ <lastUpdated>20110718111322</lastUpdated>
+ </versioning>
+</metadata>
diff --git a/src/test/resources/outer-maven-metadata.xml b/src/test/resources/outer-maven-metadata.xml
new file mode 100644
index 0000000..0ac5605
--- /dev/null
+++ b/src/test/resources/outer-maven-metadata.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+ <groupId>org.eclipse.tycho.nexus.example</groupId>
+ <artifactId>org.eclipse.tycho.nexus.example.target</artifactId>
+ <versioning>
+ <latest>0.5.1-SNAPSHOT</latest>
+ <release>0.3.0</release>
+ <versions>
+ <version>0.4.0</version>
+ <version>0.7.1-SNAPSHOT</version>
+ <version>0.4.1</version>
+ <version>0.5.0</version>
+ <version>0.3.0</version>
+ <version>0.5.1-SNAPSHOT</version>
+ <version>0.6.0</version>
+ <version>0.7.0-SNAPSHOT</version>
+ <version>0.7.0</version>
+ <version>0.4.2</version>
+ <version>0.6.1-SNAPSHOT</version>
+ <version>0.2.0</version>
+ </versions>
+ <lastUpdated>20110718111322</lastUpdated>
+ </versioning>
+</metadata> \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/subdir/a.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/subdir/a.txt
new file mode 100644
index 0000000..aa6c40b
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/subdir/a.txt
@@ -0,0 +1 @@
+some more content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/test.txt
new file mode 100644
index 0000000..a3b9373
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/dir/test.txt
@@ -0,0 +1 @@
+some file content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/test.txt
new file mode 100644
index 0000000..f0eec86
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1-juhu.zip-unzipped/test.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/subdir/a.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/subdir/a.txt
new file mode 100644
index 0000000..aa6c40b
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/subdir/a.txt
@@ -0,0 +1 @@
+some more content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/test.txt
new file mode 100644
index 0000000..a3b9373
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/dir/test.txt
@@ -0,0 +1 @@
+some file content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/test.txt
new file mode 100644
index 0000000..f0eec86
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101012-1.zip-unzipped/test.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/subdir/a.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/subdir/a.txt
new file mode 100644
index 0000000..aa6c40b
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/subdir/a.txt
@@ -0,0 +1 @@
+some more content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/test.txt
new file mode 100644
index 0000000..a3b9373
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/dir/test.txt
@@ -0,0 +1 @@
+some file content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/test.txt
new file mode 100644
index 0000000..f0eec86
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2-juhu.zip-unzipped/test.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/subdir/a.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/subdir/a.txt
new file mode 100644
index 0000000..aa6c40b
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/subdir/a.txt
@@ -0,0 +1 @@
+some more content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/test.txt
new file mode 100644
index 0000000..a3b9373
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/dir/test.txt
@@ -0,0 +1 @@
+some file content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/test.txt b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/test.txt
new file mode 100644
index 0000000..f0eec86
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/archive-1.0.0-20101013-2.zip-unzipped/test.txt
@@ -0,0 +1 @@
+some content \ No newline at end of file
diff --git a/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/maven-metadata.xml b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/maven-metadata.xml
new file mode 100644
index 0000000..3547ae1
--- /dev/null
+++ b/src/test/resources/snapshotRepo/ga/1.0.0-SNAPSHOT/maven-metadata.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata modelVersion="1.1.0">
+ <groupId>g</groupId>
+ <artifactId>a</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <versioning>
+ <snapshot>
+ <timestamp>20101013</timestamp>
+ <buildNumber>2</buildNumber>
+ </snapshot>
+ <lastUpdated>20101013</lastUpdated>
+ <snapshotVersions>
+ <snapshotVersion>
+ <extension>zip</extension>
+ <value>1.0.0-20101013-2</value>
+ <updated>20101013</updated>
+ </snapshotVersion>
+ <snapshotVersion>
+ <classifier>juhu</classifier>
+ <extension>zip</extension>
+ <value>1.0.0-20101013-2</value>
+ <updated>20101013</updated>
+ </snapshotVersion>
+ </snapshotVersions>
+ </versioning>
+</metadata> \ No newline at end of file

Back to the top