Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/Eclipse-Formatter-Profile-Clarin.xml
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/Eclipse-Formatter-Profile-Clarin.xml	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/Eclipse-Formatter-Profile-Clarin.xml	(revision 1025)
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="11">
+<profile kind="CodeFormatterProfile" name="Clarin" version="11">
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.5"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+</profile>
+</profiles>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/Protocol.txt
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/Protocol.txt	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/Protocol.txt	(revision 1025)
@@ -0,0 +1,197 @@
+Virtual Collection Registry RESTful web service
+===============================================
+
+The Virtual Collection Registry (VCR) provides a RESTful web service for
+working with the registry.
+
+
+1. Prerequisite
+
+    a) Internal XML format for virtual collections:
+       A virtual collection is represented in an internal XML format, which is
+       defined by the W3C XML schema "VirtualCollection.xsd" found in the
+       "src/resources/META-INF" directory.
+       
+    b) The VCR relies on various HTTP headers:
+        - "Content-Type" must be set for POST and PUT requests. Accepted
+          values are "text/xml", "application/xml" or "application/json".
+        - "Accept" can be used all requests. Accepted values are "text/xml",
+          "application/xml" or "application/json". If the header is not set,
+          the VCR assumes "application/xml" as default.
+        - Headers for HTTP Basic Auth need to be set on POST, PUT and DELETE
+          requests.      
+
+
+2. RESTful web service operations
+
+    The following describes the RESTful web service operations. The URIs are
+    relative to a base URI ($BASE), which depends on the machine, where the
+    VCR is installed.
+
+    a) Create a virtual collection (POST) 
+                 description: A virtual collection will be created based on the
+                              representation of the virtual collection sent in
+                              the request body.
+                 HTTP method: POST
+                         URI: $BASE/service/virtualcollection
+         URI path parameters: none
+            query parameters: none 
+                HTTP headers: Content-Type (required), Accept (optional)
+              authentication: required
+                request body: Depending on Content-Type header either a valid
+                              XML instance or the JSON representation of a
+                              virtual collection conforming to the above
+                              mentioned XML schema. The root element is expected
+                              to be "VirtualCollection"
+                 result body: Depending on Accept header either an XML instance
+                              or the JSON representation of the outcome of the
+                              request. If successful, the result will contain
+                              the ID of the created virtual collection
+
+    b) Update a virtual collection (PUT)
+                 description: The virtual collection identified by the URI will
+                              be updated, actually replaced, with the
+                              representation of the virtual collection sent in
+                              the request body.
+                 HTTP method: PUT
+                         URI: $BASE/service/virtualcollection/$id
+         URI path parameters: $id := (required) is the ID of the virtual
+                                     collection, which is to be changed 
+            query parameters: none 
+                HTTP headers: Content-Type (required), Accept (optional)
+              authentication: required
+                request body: Depending on Content-Type header either a valid
+                              XML instance or the JSON representation of a
+                              virtual collection conforming to the above
+                              mentioned XML schema. The root element is expected
+                              to be "VirtualCollection"
+                 result body: Depending on Accept header either an XML instance
+                              or the JSON representation of the outcome of the
+                              request.
+
+    c) Delete a virtual collection (DELETE)
+                 description: The virtual collection referenced by the URI will
+                              be deleted.
+                 HTTP method: DELETE
+                         URI: $BASE/service/virtualcollection/$id
+         URI path parameters: $id := (required) is the ID of the virtual
+                                     collection, which is to be deleted  
+            query parameters: none
+                HTTP headers: Accept (optional)
+              authentication: required
+                request body: none
+                 result body: depending on Accept header either an XML instance
+                              or the JSON representation of the outcome of the
+                              request.
+
+    d) Retrieve a virtual collection (GET)
+                 description: The virtual collection referenced by the URI will
+                              be retrieved.
+                 HTTP method: GET
+                         URI: $BASE/service/virtualcollection/$id
+         URI path parameters: $id := (required) is the ID of the virtual
+                                     collection, which is to be retrieved
+            query parameters: none
+                HTTP headers: Accept (optional)
+              authentication: not required
+                request body: none
+                 result body: Depending on Accept header either an XML instance
+                              or the JSON representation of the virtual
+                              collection. If the virtual collection is not found
+                              the appropriate HTTP status code is issued and an
+                              error message is returned.
+
+    e) Retrieve all / search virtual collections (GET)
+                 description: All virtual collections will be retrieved; if a
+                              query expression is used, only the virtual
+                              collections satisfying the query will be
+                              retrieved. 
+                 HTTP method: GET
+                         URI: $BASE/service/virtualcollections
+         URI path parameters: none
+            query parameters: q      := (optional) URL-encoded query
+                                        expression (see below)
+                              offset := (optional) start of result list at
+                                        a given position (default: 0)
+                              count  := (optional) limit the result list to
+                                        a number of entries (default: unlimited)
+                HTTP headers: Accept (optional)
+              authentication: not required
+                request body: none
+                 result body: Depending on Accept header either an XML instance
+                              or the JSON representation of the virtual
+                              collection. If no virtual collection are found
+                              an empty list will be returned.
+
+    f) Retrieve all / search virtual collection owned by the user (GET)
+                 description: All virtual collections owned by the authenticated
+                              user will be retrieved; if a query expression is
+                              used, only the virtual collections satisfying the
+                              query will be retrieved.
+                 HTTP method: GET
+                         URI: $BASE/service/my-virtualcollections
+         URI path parameters: none
+            query parameters: q      := (optional) URL-encoded query
+                                        expression (see below)
+                              offset := (optional) start of result list at
+                                        a given position (default: 0)
+                              count  := (optional) limit the result list to
+                                        a number of entries (default: unlimited)
+                HTTP headers: Accept (optional)
+              authentication: required
+                request body: none
+                 result body: Depending on Accept header either an XML instance
+                              or the JSON representation of the virtual
+                              collection. If no virtual collection are found
+                              an empty list will be returned.
+
+    g) Retrieve a virtual collection in CMDI format (GET)
+                 description: The virtual collection referenced by the URI will
+                              be retrieved in CMDI format.
+                 HTTP method: GET
+                         URI: $BASE/service/clarin-virtualcollection/$id
+         URI path parameters: $id := is the ID of the virtual collection,
+                                     which is to be retrieved
+            query parameters: none
+                HTTP headers: none
+              authentication: not required
+                request body: none
+                 result body: The virtual collection in CMDI format If the
+                              virtual collection is not found the appropriate
+                              HTTP status code is issued and an error message
+                              is returned.
+
+
+3. Virtual Collection Query language (VCRQL)
+     query            = expression
+                      | expression 'and' expression
+                      | expression 'or' expression
+                      | '(' expression ')'
+
+     expression       = 'name'         ( '=' | '<>' ) value
+                      | 'description'  ( '=' | '<>' ) value
+                      | 'creator'      ( '=' | '<>' ) value
+                      | 'email'        ( '=' | '<>' ) value
+                      | 'organization' ( '=' | '<>' ) value
+                      | 'state'        ( '=' | '<>' )
+                                       ( 'public' | 'private' | 'deleted' )
+                      | 'created'      ( '<' | '<=' | '=' | '<>' | '>=' | '>' )
+                                       iso-date
+                      | 'modified'     ( '<' | '<=' | '=' | '<>' | '>=' | '>' )
+                                       iso-date
+
+     value            = double quoted string value, use backslash
+                        for escaping double quotes, use "*" and "?" for
+                        wildcards.
+     iso-date         = full ISO 8601 date: YYYY-MM-DD-T-HH:MM:SSZ
+                        where YYYY := year
+                                MM := month
+                                DD := day
+                                HH := hour
+                                 T := time designator (literal)
+                                MM := minute
+                                SS := second
+                                Z  := UTC indicator (literal) 
+
+   (see "src/main/jjtree/eu/clarin/cmdi/virtualcollectionregistry/query/QueryParser.jjt"
+    and "src/main/main/eu/clarin/cmdi/virtualcollectionregistry/query/*.java")
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/README.txt
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/README.txt	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/README.txt	(revision 1025)
@@ -0,0 +1,44 @@
+CLARIN Virtual Collection Registry
+----------------------------------
+
+The connection to the database is configured using JNDI using the
+name "jdbc/VirtualCollectionStore".
+When using Apache Tomcat add the following to the context configuration
+of the web application (by either modifying "server.xml" or providing an
+appropiate context configuration, e.g. like
+  "$CATALINA_HOME/conf/Catalina/localhost/VirtualCollectionRegistry.xml";
+if in doubt, please check the Apache Tomcat documentation):
+
+  <Resource name="jdbc/VirtualCollectionStore" auth="Container"
+            type="javax.sql.DataSource"
+            maxActive="8" maxIdle="4" maxWait="10000"
+            removeAbandoned="true"
+            defaultAutoCommit="false"
+            driverClassName="com.mysql.jdbc.Driver"
+            validationQuery="SELECT 1"
+            username="$dbuser"
+            password="$dbpass"
+            url="jdbc:mysql://127.0.0.1:3306/$dbname" />
+
+  This will configure a DBCP data source for the virtual collection registry.
+  Customize $dbuser, $dbpass and $dbname to match your local settings.
+  NOTE: currently only MySQL is supported.
+
+  You need to choose, which persistent identifier provider you want to use.
+  You can either use a dummy provider or the GWDG handle provider.
+  a) For using the dummy provider add the following:
+  <Parameter name="pid_provider.class"
+             value="eu.clarin.cmdi.virtualcollectionregistry.DummyPersistentIdentifierProvider"
+             override="false"/>
+
+  b) For using the GWDG handle provider add following and customize the
+     base URI for the virtual collection registry and the values for
+     $gwdg_user and $gwdg_password:
+  <Parameter name="pid_provider.class"
+             value="eu.clarin.cmdi.virtualcollectionregistry.GWDGPersistentIdentifierProvider"
+             override="false"/>
+  <Parameter name="pid_provider.base_uri"
+             value="http://127.0.0.1:8080/VirtualCollectionRegistry"
+             override="false"/>
+  <Parameter name="pid_provider.username" value="$gwdg_user" override="false"/>
+  <Parameter name="pid_provider.password" value="$gwdg_password" override="false"/>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/pom.xml
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/pom.xml	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/pom.xml	(revision 1025)
@@ -0,0 +1,445 @@
+<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>eu.clarin.cmdi</groupId>
+  <artifactId>VirtualCollectionRegistry</artifactId>
+  <packaging>war</packaging>
+  <name>CLARIN Virtual Collection Registry</name>
+  <version>0.2.1</version>
+
+  <organization>
+  	<name>Institut fÃŒr Deutsche Sprache</name>
+  	<url>http://www.ids-mannheim.de/</url>
+  </organization>
+
+  <build>
+    <resources>
+      <resource>
+        <filtering>true</filtering>
+        <directory>src/main/resources</directory>
+      </resource>
+      <resource>
+        <filtering>true</filtering>
+        <directory>src/main/java</directory>
+        <includes>
+          <include>**/BasePage.html</include>
+        </includes>
+      </resource>
+      <resource>
+        <filtering>false</filtering>
+        <directory>src/main/java</directory>
+        <includes>
+          <include>**/*.html</include>
+          <include>**/*.css</include>
+          <include>**/*.js</include>
+          <include>**/*.properties</include>
+        </includes>
+      </resource>
+    </resources>
+
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+      </testResource>
+    </testResources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>1.4</version>
+        <executions>
+          <execution>
+            <id>process-jpa-annotations</id>
+            <phase>generate-sources</phase>
+            <configuration>
+              <tasks>
+                <echo>processing JPA annotations</echo>
+                <property name="target.dir"
+                  value="${project.build.directory}/generated-sources/jpamodelgen" />
+                <mkdir dir="${target.dir}" />
+                <javac
+                  srcdir="${basedir}/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model"
+                  destdir="${target.dir}" failonerror="false" fork="true"
+                  target="${maven.compiler.target}">
+                  <!-- hack to silence javac warnings -->
+                  <include name="**/*.java" />
+                  <exclude name="**/VirtualCollectionValidator.java" />
+                  <exclude name="**/*Provider.java" />
+                  <compilerarg value="-proc:only" />
+                  <classpath>
+                    <path refid="maven.compile.classpath" />
+                  </classpath>
+                </javac>
+              </tasks>
+              <sourceRoot>${project.build.directory}/generated-sources/jpamodelgen</sourceRoot>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>javacc-maven-plugin</artifactId>
+        <version>2.6</version>
+        <executions>
+          <execution>
+            <id>javacc</id>
+            <goals>
+              <goal>jjtree-javacc</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>${maven.compiler.source}</source>
+          <target>${maven.compiler.target}</target>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <version>2.1.1</version>
+        <configuration>
+          <webResources>
+            <!-- additional web resources: also copy schemas to web-app -->
+            <resource>
+              <directory>src/main/resources/META-INF</directory>
+              <targetPath>schema</targetPath>
+              <includes>
+                <include>*.xsd</include>
+              </includes>
+            </resource>
+          </webResources>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <repositories>
+    <repository>
+      <id>repository.jboss-public</id>
+      <name>JBoss Repository</name>
+      <url>https://repository.jboss.org/nexus/content/groups/public/</url>
+    </repository>
+
+    <repository>
+      <id>repository.java-net</id>
+      <name>Java.net Repository</name>
+      <url>http://download.java.net/maven/2/</url>
+    </repository>
+
+    <repository>
+      <id>wiquery repository</id>
+      <name>wiQuery repository</name>
+      <url>http://wiquery.googlecode.com/svn/repo/</url>
+      <layout>default</layout>
+    </repository>
+
+    <repository>
+      <id>repository.clarin</id>
+      <name>CLARIN Repository</name>
+      <url>http://catalog.clarin.eu/ds/nexus/content/groups/public/</url>
+    </repository>
+  </repositories>
+
+  <pluginRepositories>
+    <pluginRepository>
+      <id>repository.jboss-public</id>
+      <name>JBoss Repository</name>
+      <url>https://repository.jboss.org/nexus/content/groups/public/</url>
+    </pluginRepository>
+
+    <pluginRepository>
+      <id>repository.java-net</id>
+      <name>Java.net Repository</name>
+      <url>http://download.java.net/maven/2/</url>
+    </pluginRepository>
+  </pluginRepositories>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-jdk14</artifactId>
+      <version>${slf4j.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.5</version>
+      <type>jar</type>
+    </dependency>
+
+    <dependency>
+        <groupId>commons-codec</groupId>
+        <artifactId>commons-codec</artifactId>
+        <version>1.4</version>
+        <type>jar</type>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.xml.bind</groupId>
+      <artifactId>jaxb-api</artifactId>
+      <version>2.2.1</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.xml.stream</groupId>
+          <artifactId>stax-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+        <groupId>org.codehaus.woodstox</groupId>
+        <artifactId>woodstox-core-asl</artifactId>
+        <version>4.0.8</version>
+        <type>jar</type>
+        <scope>compile</scope>
+    </dependency>
+
+    <dependency>
+        <groupId>net.java.dev.msv</groupId>
+        <artifactId>msv-core</artifactId>
+        <version>2009.1</version>
+        <type>jar</type>
+        <scope>compile</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.hibernate.javax.persistence</groupId>
+      <artifactId>hibernate-jpa-2.0-api</artifactId>
+      <version>${hibernate.jpaVersion}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-entitymanager</artifactId>
+      <version>${hibernate.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.hibernate</groupId>
+      <artifactId>hibernate-jpamodelgen</artifactId>
+      <version>${hibernate.jpaVersion}</version>
+      <!--
+        "provided" is actually a lie, but this dependency is only needed
+        at compile time, and "provided" does the trick
+      -->
+      <scope>provided</scope>
+      <exclusions>
+        <exclusion>
+          <!--
+            Use the jpa-api defined here, crossing fingers they match up
+            signature-wise
+          -->
+          <groupId>org.hibernate.javax.persistence</groupId>
+          <artifactId>hibernate-jpa-2.0-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.5</version>
+      <type>jar</type>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.ws.rs</groupId>
+      <artifactId>jsr311-api</artifactId>
+      <version>1.1.1</version>
+      <type>jar</type>
+    </dependency>
+
+    <dependency>
+      <groupId>com.sun.jersey</groupId>
+      <artifactId>jersey-server</artifactId>
+      <version>${jersey.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.sun.jersey</groupId>
+      <artifactId>jersey-json</artifactId>
+      <version>${jersey.version}</version>
+      <scope>runtime</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>stax</groupId>
+          <artifactId>stax-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.sun.xml.bind</groupId>
+          <artifactId>jaxb-impl</artifactId>
+        </exclusion>
+        <exclusion>
+        	<artifactId>jackson-mapper-asl</artifactId>
+        	<groupId>org.codehaus.jackson</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+
+    <dependency>
+      <groupId>org.codehaus.jettison</groupId>
+      <artifactId>jettison</artifactId>
+      <version>1.2</version>
+      <exclusions>
+        <exclusion>
+          <groupId>stax</groupId>
+          <artifactId>stax-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.0.1</version>
+      <type>jar</type>
+      <scope>compile</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket</artifactId>
+      <version>${wicket.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-auth-roles</artifactId>
+      <version>${wicket.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-datetime</artifactId>
+      <version>${wicket.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.wicket</groupId>
+      <artifactId>wicket-extensions</artifactId>
+      <version>${wicket.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.odlabs.wiquery</groupId>
+      <artifactId>wiquery</artifactId>
+      <version>1.1.2</version>
+    </dependency>
+
+    <dependency>
+      <groupId>net.java.dev.javacc</groupId>
+      <artifactId>javacc</artifactId>
+      <version>5.0</version>
+      <type>jar</type>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>eu.clarin.cmdi</groupId>
+      <artifactId>OAIProvider</artifactId>
+      <version>0.0.1</version>
+    </dependency>
+
+    <dependency>
+    	<groupId>junit</groupId>
+    	<artifactId>junit</artifactId>
+    	<version>4.8.1</version>
+    	<type>jar</type>
+    	<scope>test</scope>
+    </dependency>
+
+    <dependency>
+    	<groupId>org.hsqldb</groupId>
+    	<artifactId>hsqldb</artifactId>
+    	<version>2.0.0</version>
+    	<type>jar</type>
+    	<scope>test</scope>
+    </dependency>
+
+    <dependency>
+    	<groupId>mysql</groupId>
+    	<artifactId>mysql-connector-java</artifactId>
+    	<version>5.1.13</version>
+    	<type>jar</type>
+    	<scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <dependencyManagement>
+  	<dependencies>
+     <dependency>
+       <groupId>commons-codec</groupId>
+       <artifactId>commons-codec</artifactId>
+       <version>1.4</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.codehaus.jettison</groupId>
+        <artifactId>jettison</artifactId>
+        <version>1.2</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.apache.wicket</groupId>
+        <artifactId>wicket</artifactId>
+        <version>${wicket.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>${slf4j.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-jdk14</artifactId>
+        <version>${slf4j.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.codehaus.jackson</groupId>
+        <artifactId>jackson-core-asl</artifactId>
+        <version>1.6.1</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.codehaus.jackson</groupId>
+        <artifactId>jackson-mapper-asl</artifactId>
+        <version>1.6.1</version>
+      </dependency>
+  	</dependencies>
+  </dependencyManagement>
+
+  <properties>
+    <!-- project settings -->
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <!-- plug-in settings -->
+    <maven.compiler.source>1.6</maven.compiler.source>
+    <maven.compiler.target>1.6</maven.compiler.target>
+    <!-- dependency versions -->
+    <slf4j.version>1.6.1</slf4j.version>
+    <hibernate.version>3.6.0.Final</hibernate.version>
+    <hibernate.jpaVersion>1.0.0.Final</hibernate.jpaVersion>
+    <jersey.version>1.4</jersey.version>
+    <wicket.version>1.4.14</wicket.version>
+  </properties>
+</project>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/DataStore.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/DataStore.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/DataStore.java	(revision 1025)
@@ -0,0 +1,63 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Persistence;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DataStore {
+    private static final Logger logger =
+        LoggerFactory.getLogger(DataStore.class);
+    private final EntityManagerFactory emf;
+    private final ThreadLocal<EntityManager> em;
+
+    DataStore(Map<String, String> config)
+        throws VirtualCollectionRegistryException {
+        try {
+            emf = Persistence.createEntityManagerFactory(
+                    "VirtualCollectionStore", config);
+            em = new ThreadLocal<EntityManager>() {
+                protected EntityManager initialValue() {
+                    if (emf == null) {
+                        throw new InternalError(
+                                "JPA not initalizied correctly");
+                    }
+                    return emf.createEntityManager();
+                }
+            };
+        } catch (Exception e) {
+            logger.error("error initializing data store", e);
+            throw new VirtualCollectionRegistryException(
+                    "error initializing", e);
+        }
+        logger.debug("data store was successfully initialized");
+    }
+
+    public void destroy() throws VirtualCollectionRegistryException {
+        if (emf != null) {
+            emf.close();
+        }
+    }
+
+    public EntityManager getEntityManager() {
+        return em.get();
+    }
+
+    public void closeEntityManager() {
+        EntityManager manager = em.get();
+        if (manager != null) {
+            em.remove();
+            EntityTransaction tx = manager.getTransaction();
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+            manager.close();
+        }
+    }
+
+} // class DataStore
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/DummyPersistentIdentifierProvider.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/DummyPersistentIdentifierProvider.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/DummyPersistentIdentifierProvider.java	(revision 1025)
@@ -0,0 +1,32 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.net.URI;
+import java.util.Map;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifier;
+import eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifierProvider;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+public class DummyPersistentIdentifierProvider extends
+        PersistentIdentifierProvider {
+
+    public DummyPersistentIdentifierProvider(Map<String, String> config)
+            throws VirtualCollectionRegistryException {
+        super(config);
+    }
+
+    public PersistentIdentifier createIdentifier(VirtualCollection vc)
+            throws VirtualCollectionRegistryException {
+        return doCreate(vc, PersistentIdentifier.Type.DUMMY,
+                        "dummy-" + Long.toString(vc.getId()));
+    }
+
+    public void updateIdentifier(String pid, URI target)
+            throws VirtualCollectionRegistryException {
+    }
+
+    public void deleteIdentifier(String pid)
+            throws VirtualCollectionRegistryException {
+    }
+
+} // class DummyPersistentIdentifierProvider
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/GWDGPersistentIdentifierProvider.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/GWDGPersistentIdentifierProvider.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/GWDGPersistentIdentifierProvider.java	(revision 1025)
@@ -0,0 +1,270 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamReader;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.StatusLine;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifier;
+import eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifierProvider;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+public class GWDGPersistentIdentifierProvider extends
+        PersistentIdentifierProvider {
+    private static enum Attribute {
+        PID, URL, CREATOR, EXPDATE;
+
+        public static Attribute fromString(String s) {
+            if (s.equalsIgnoreCase("pid")) {
+                return PID;
+            } else if (s.equalsIgnoreCase("url")) {
+                return URL;
+            } else if (s.equalsIgnoreCase("creator")) {
+                return CREATOR;
+            } else if (s.equalsIgnoreCase("expdate")) {
+                return EXPDATE;
+            }
+            return null;
+        }
+
+        public String toString() {
+            switch (this) {
+            case PID:
+                return "pid";
+            case URL:
+                return "url";
+            case CREATOR:
+                return "creator";
+            case EXPDATE:
+                return "expdate";
+            default:
+                throw new InternalError();
+            }
+        }
+    } // private enum Attribute
+
+    public static final String USERNAME = "pid_provider.username";
+    public static final String PASSWORD = "pid_provider.password";
+    private static final String SERVICE_URI_BASE =
+        "http://handle.gwdg.de:8080/pidservice/";
+    private static final String USER_AGENT =
+        "CLARIN-VirtualCollectionRegisty/1.0";
+    private static final Logger logger =
+        LoggerFactory.getLogger(GWDGPersistentIdentifierProvider.class);
+    private String base_uri = null;
+    private String username = null;
+    private String password = null;
+    private XMLInputFactory factory;
+
+    public GWDGPersistentIdentifierProvider(Map<String, String> config)
+            throws VirtualCollectionRegistryException {
+        super(config);
+        try {
+            String base_uri = getConfigParameter(config, BASE_URI);
+            if (!base_uri.endsWith("/")) {
+                base_uri = base_uri + "/";
+            }
+            URI uri = new URI(base_uri);
+            this.base_uri = uri.toString();
+        } catch (URISyntaxException e) {
+            throw new VirtualCollectionRegistryException("configuration "
+                    + "parameter \"" + BASE_URI + "\" is invalid", e);
+        }
+        this.username = getConfigParameter(config, USERNAME);
+        this.password = getConfigParameter(config, PASSWORD);
+
+        this.factory = XMLInputFactory.newInstance();
+        factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+        factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES,
+                Boolean.TRUE);
+    }
+
+    public PersistentIdentifier createIdentifier(VirtualCollection vc)
+            throws VirtualCollectionRegistryException {
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+        logger.debug("creating handle for virtual collection \"{}\"",
+                vc.getId());
+        try {
+            String target = makeCollectionURI(vc);
+            URI serviceURI = URI.create(SERVICE_URI_BASE + "write/create");
+
+            List<NameValuePair> params = new ArrayList<NameValuePair>();
+            params.add(new BasicNameValuePair("url", target));
+            Map<Attribute, String> props = invokeWebService(serviceURI, params);
+            String pid = props.get(Attribute.PID);
+            if (pid == null) {
+                throw new VirtualCollectionRegistryException(
+                        "no handle returned");
+            }
+            logger.info("created handle \"{}\" for virtual collection \"{}\"",
+                    pid, vc.getId());
+            return doCreate(vc, PersistentIdentifier.Type.GWDG, pid);
+        } catch (VirtualCollectionRegistryException e) {
+            throw new RuntimeException("failed to create handle", e);
+        }
+    }
+
+    public void updateIdentifier(String pid, URI target)
+            throws VirtualCollectionRegistryException {
+        if (pid == null) {
+            throw new NullPointerException("pid == null");
+        }
+        if (pid.isEmpty()) {
+            throw new IllegalArgumentException("pid is empty");
+        }
+        if (target == null) {
+            throw new NullPointerException("target == null");
+        }
+        List<NameValuePair> params = new ArrayList<NameValuePair>();
+        params.add(new BasicNameValuePair("pid", pid));
+        params.add(new BasicNameValuePair("url", target.toString()));
+        URI serviceURI = URI.create(SERVICE_URI_BASE + "write/modify");
+        invokeWebService(serviceURI, params);
+        logger.info("updated handle \"{}\"", pid);
+    }
+
+    public void deleteIdentifier(String pid)
+            throws VirtualCollectionRegistryException {
+        if (pid == null) {
+            throw new NullPointerException("pid == null");
+        }
+        if (pid.isEmpty()) {
+            throw new IllegalArgumentException("pid is empty");
+        }
+        /*
+         * actually one cannot delete a handle, but we can set an expired date
+         * to mark it invalid
+         */
+        List<NameValuePair> params = new ArrayList<NameValuePair>();
+        params.add(new BasicNameValuePair("pid", pid));
+        params.add(new BasicNameValuePair("expdate", "1970-01-01"));
+        URI serviceURI = URI.create(SERVICE_URI_BASE + "write/modify");
+        invokeWebService(serviceURI, params);
+        logger.info("deleted/expired handle \"{}\"", pid);
+    }
+
+    private String makeCollectionURI(VirtualCollection vc) {
+        return base_uri + "service/clarin-virtualcollection/" + vc.getId();
+    }
+
+    private Map<Attribute, String> invokeWebService(URI serviceTargetURI,
+            List<NameValuePair> formparams)
+            throws VirtualCollectionRegistryException {
+        // force xml encoding
+        formparams.add(new BasicNameValuePair("encoding", "xml"));
+
+        DefaultHttpClient client = null;
+        try {
+            client = new DefaultHttpClient();
+            int port = serviceTargetURI.getPort() != -1 ? serviceTargetURI
+                    .getPort() : AuthScope.ANY_PORT;
+            client.getCredentialsProvider().setCredentials(
+                    new AuthScope(serviceTargetURI.getHost(), port),
+                    new UsernamePasswordCredentials(username, password));
+            // disable expect continue, GWDG does not like very well
+            client.getParams().setParameter(
+                    HttpProtocolParams.USE_EXPECT_CONTINUE, Boolean.FALSE);
+            // set a proper user agent
+            client.getParams().setParameter(HttpProtocolParams.USER_AGENT,
+                    USER_AGENT);
+            HttpPost request = new HttpPost(serviceTargetURI);
+            request.addHeader("Accept", "text/xml, application/xml");
+            request.setEntity(new UrlEncodedFormEntity(formparams, "UTF-8"));
+            HttpContext ctx = new BasicHttpContext();
+
+            logger.debug("invoking GWDG service at {}", serviceTargetURI);
+            HttpResponse response = client.execute(request, ctx);
+            StatusLine status = response.getStatusLine();
+            HttpEntity entity = response.getEntity();
+            Map<Attribute, String> props = Collections.emptyMap();
+
+            logger.debug("GWDG Service status: {}", status.toString());
+            if ((status.getStatusCode() >= 200)
+                    && (status.getStatusCode() <= 299) && (entity != null)) {
+                String encoding = EntityUtils.getContentCharSet(entity);
+                if (encoding == null) {
+                    encoding = "UTF-8";
+                }
+
+                XMLStreamReader reader = factory.createXMLStreamReader(entity
+                        .getContent(), encoding);
+                props = new HashMap<Attribute, String>();
+                while (reader.hasNext()) {
+                    reader.next();
+
+                    int type = reader.getEventType();
+                    if (type != XMLStreamConstants.START_ELEMENT) {
+                        continue;
+                    }
+                    Attribute attribute = Attribute.fromString(reader
+                            .getLocalName());
+                    if (attribute != null) {
+                        if (!reader.hasNext()) {
+                            throw new VirtualCollectionRegistryException(
+                                    "unexpected end of data stream");
+                        }
+                        reader.next();
+                        if (reader.getEventType() != XMLStreamConstants.CHARACTERS) {
+                            throw new VirtualCollectionRegistryException(
+                                    "unexpected element type: "
+                                            + reader.getEventType());
+                        }
+                        String value = reader.getText();
+                        if (value == null) {
+                            throw new VirtualCollectionRegistryException(
+                                    "element \"" + attribute + "\" was empty");
+                        }
+                        value = value.trim();
+                        if (!value.isEmpty()) {
+                            props.put(attribute, value);
+                        }
+                    }
+                }
+
+            } else {
+                logger.debug("GWDG Handle service failed: {}", status);
+                request.abort();
+                throw new VirtualCollectionRegistryException(
+                        "error invoking GWDG handle service");
+            }
+            return props;
+        } catch (VirtualCollectionRegistryException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.debug("GWDG Handle service failed", e);
+            throw new VirtualCollectionRegistryException(
+                    "error invoking GWDG handle service", e);
+        } finally {
+            if (client != null) {
+                client.getConnectionManager().shutdown();
+            }
+        }
+    }
+
+} // class GWDGPersistentIdentifierProvider
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/QueryOptions.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/QueryOptions.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/QueryOptions.java	(revision 1025)
@@ -0,0 +1,544 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Order;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.User_;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection_;
+
+@SuppressWarnings("serial")
+public class QueryOptions implements Serializable {
+    public enum Property {
+        VC_OWNER,
+        VC_NAME,
+        VC_TYPE,
+        VC_STATE,
+        VC_DESCRIPTION,
+        VC_CREATION_DATE
+    } // enum QueryOptions.Property
+
+    public enum Relation {
+        EQ, NE, LT, LE, GT, GE;
+
+        private byte getCode() {
+            switch (this) {
+            case EQ:
+                return RELATION_EQ;
+            case NE:
+                return RELATION_NE;
+            case LT:
+                return RELATION_LT;
+            case LE:
+                return RELATION_LE;
+            case GT:
+                return RELATION_GT;
+            case GE:
+                return RELATION_GE;
+            }
+            return -1;
+        }
+    } // enum QueryOptions.Relation
+
+    
+    private static final byte RELATION_EQ = 0x01;
+    private static final byte RELATION_NE = 0x02;
+    private static final byte RELATION_LT = 0x04;
+    private static final byte RELATION_LE = 0x08;
+    private static final byte RELATION_GT = 0x10;
+    private static final byte RELATION_GE = 0x20;
+    private static final byte RELATIONS_EQ_NE = RELATION_EQ | RELATION_NE;
+    private static final byte RELATIONS_ALL   = RELATION_EQ | RELATION_NE |
+                                                RELATION_LT | RELATION_LE |
+                                                RELATION_GT | RELATION_GE;
+    public static final List<AbstractPropertyImpl> PROPERTIES =
+        Arrays.asList(
+                new PropertyImplOwner(),
+                new PropertyImplName(),
+                new PropertyImplType(),
+                new PropertyImplState(),
+                new PropertyImplDescription(),
+                new PropertyImplCreationDate()
+        );
+    private Filter filter;
+    private List<OrderBy> orderByItems;
+
+    
+    public QueryOptions() {
+        super();
+    }
+
+    public Filter getFilter() {
+        return filter;
+    }
+
+    public void setFilter(Filter filter) {
+        this.filter = filter;
+    }
+
+    public boolean addSortProperty(Property property, boolean asc) {
+        if (property == null) {
+            throw new IllegalArgumentException("property == null");
+        }
+        final AbstractPropertyImpl impl = getPropertyImpl(property);
+        if (orderByItems == null) {
+            orderByItems = new ArrayList<OrderBy>();
+        } else {
+            for (OrderBy item : orderByItems) {
+                if (item.getProperty().equals(impl)) {
+                    return false;
+                }
+            }
+        }
+        orderByItems.add(new OrderBy(impl, asc));
+        return true;
+    }
+
+    public Filter and() {
+        return new AndFilter();
+    }
+    
+    public Filter or() {
+        return new OrFilter();
+    }
+
+    public Filter expr(Property property, Relation relation, Object value) {
+        if (property == null) {
+            throw new IllegalArgumentException("property == null");
+        }
+        if (relation == null) {
+            throw new IllegalArgumentException("relation == null");
+        }
+        if (value == null) {
+            throw new IllegalArgumentException("value == null");
+        }
+        final AbstractPropertyImpl impl = getPropertyImpl(property);
+        if (!impl.isValidRelation(relation)) {
+            throw new IllegalArgumentException("invalid relation");
+        }
+        return new ExpressionFilter(impl, relation.getCode(), value);
+    }
+
+    Predicate getWhere(CriteriaBuilder cb, CriteriaQuery<?> cq,
+            Root<VirtualCollection> root) {
+        if (filter != null) {
+            return filter.makePredicate(cb, cq, root);
+        }
+        return null;
+    }
+
+    Order[] getOrderBy(CriteriaBuilder cb, Root<VirtualCollection> root) {
+        Order[] order = null;
+        if (orderByItems != null) {
+            order = new Order[orderByItems.size()];
+            int i = 0;
+            for (OrderBy item : orderByItems) {
+                order[i++] = item.makeOrder(cb, root);
+            }
+        }
+        return order;
+    }
+
+    private static AbstractPropertyImpl getPropertyImpl(Property property) {
+        for (AbstractPropertyImpl predicate : PROPERTIES) {
+            if (predicate.matchProperty(property)) {
+                return predicate;
+            }
+        }
+        return null;
+    }
+
+    
+    public abstract class Filter implements Serializable {
+        public final Filter add(Property property, Relation relation,
+                Object value) {
+            if (property == null) {
+                throw new IllegalArgumentException("property == null");
+            }
+            if (relation == null) {
+                throw new IllegalArgumentException("relation == null");
+            }
+            if (value == null) {
+                throw new IllegalArgumentException("value == null");
+            }
+            final AbstractPropertyImpl impl = getPropertyImpl(property);
+            if (!impl.isValidRelation(relation)) {
+                throw new IllegalArgumentException("invalid relation");
+            }
+            doAdd(new ExpressionFilter(impl, relation.getCode(), value));
+            return this;
+        }
+
+        public abstract Filter add(Filter filter);
+
+        protected abstract void doAdd(ExpressionFilter filter);
+        
+        protected abstract Predicate makePredicate(CriteriaBuilder cb,
+                CriteriaQuery<?> cq, Root<VirtualCollection> root);
+    } // QueryOptions.Filter
+
+    
+    public final class AndFilter extends Filter {
+        private final List<Filter> items = new ArrayList<Filter>();
+
+        private AndFilter() {
+        }
+
+        @Override
+        public Filter add(Filter filter) {
+            if (filter == null) {
+                throw new IllegalArgumentException("filter == null");
+            }
+            if (filter == this) {
+                throw new IllegalArgumentException("filter == this");
+            }
+            items.add(filter);
+            return this;
+        }
+
+        @Override
+        protected void doAdd(ExpressionFilter filter) {
+            items.add(filter);
+        }
+
+        protected Predicate makePredicate(CriteriaBuilder cb,
+                CriteriaQuery<?> cq, Root<VirtualCollection> root) {
+            Predicate[] preds = new Predicate[items.size()];
+            int i = 0;
+            for (Filter filter : items) {
+                preds[i++] = filter.makePredicate(cb, cq, root);
+            }
+            return cb.and(preds);
+        }
+    } // QueryOptions.AndFilter
+
+    
+    public final class OrFilter extends Filter {
+        private final List<Filter> items = new ArrayList<Filter>();
+
+        private OrFilter() {
+        }
+
+        @Override
+        public Filter add(Filter filter) {
+            if (filter == null) {
+                throw new IllegalArgumentException("filter == null");
+            }
+            if (filter == this) {
+                throw new IllegalArgumentException("filter == this");
+            }
+            items.add(filter);
+            return this;
+        }
+
+        @Override
+        protected void doAdd(ExpressionFilter filter) {
+            items.add(filter);
+        }
+
+        protected Predicate makePredicate(CriteriaBuilder cb,
+                CriteriaQuery<?> cq, Root<VirtualCollection> root) {
+            Predicate[] preds = new Predicate[items.size()];
+            int i = 0;
+            for (Filter filter : items) {
+                preds[i++] = filter.makePredicate(cb, cq, root);
+            }
+            return cb.or(preds);
+        }
+    } // QueryOptions.OrFilter
+
+    
+    private final class ExpressionFilter extends Filter {
+        private final AbstractPropertyImpl property;
+        private final byte relation;
+        private final Object value;
+
+        private ExpressionFilter(AbstractPropertyImpl property, byte relation,
+                Object value) {
+            this.property = property;
+            this.relation = relation;
+            this.value = value;
+        }
+
+        @Override
+        public Filter add(Filter filter) {
+            throw new RuntimeException("not permitted");
+        }
+
+        @Override
+        protected void doAdd(ExpressionFilter filter) {
+            throw new RuntimeException("not permitted");
+        }
+
+        public Predicate makePredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root) {
+            return property.getPredicate(cb, cq, root, relation, value);
+        }
+    } // QueryOptions.PropertyFilter
+
+    
+    private final class OrderBy {
+        private final AbstractPropertyImpl property;
+        private final boolean asc;
+        
+        public OrderBy(AbstractPropertyImpl property, boolean asc) {
+            this.property = property;
+            this.asc = asc;
+        }
+        
+        public AbstractPropertyImpl getProperty() {
+            return property;
+        }
+        
+        public Order makeOrder(CriteriaBuilder cb, Root<VirtualCollection> root) {
+            if (asc) {
+                return cb.asc(property.getExpression(root));
+            } else {
+                return cb.desc(property.getExpression(root));
+            }
+        }
+    } // class QueryOptions.OrderBy
+
+    private static abstract class AbstractPropertyImpl {
+        public abstract Property getProperty();
+
+        public abstract byte getValidRelationMask();
+
+        public abstract Expression<?> getExpression(
+                Root<VirtualCollection> root);
+        
+        public abstract Predicate getPredicate(CriteriaBuilder cb,
+                CriteriaQuery<?> cq, Root<VirtualCollection> root,
+                byte relation, Object value);
+
+        public final boolean matchProperty(Property property) {
+            return getProperty() == property;
+        }
+
+        public final boolean isValidRelation(Relation relation) {
+            return (getValidRelationMask() & relation.getCode()) > 0;
+        }
+
+        protected static final Predicate makePredicate(
+                CriteriaBuilder cb, Expression<?> expr, byte relation,
+                Object value) {
+            switch (relation) {
+            case RELATION_EQ:
+                return cb.equal(expr, value);
+            case RELATION_NE:
+                return cb.notEqual(expr, value);
+            default:
+                throw new InternalError("bad relation");
+            } // switch
+        }
+
+        protected static final Predicate makeDatePredicate(
+                CriteriaBuilder cb, Expression<Date> expr, byte relation,
+                Date value) {
+            switch (relation) {
+            case RELATION_EQ:
+                return cb.equal(expr, value);
+            case RELATION_NE:
+                return cb.notEqual(expr, value);
+            case RELATION_GT:
+                return cb.greaterThan(expr, value);
+            case RELATION_GE:
+                return cb.greaterThanOrEqualTo(expr, value);
+            case RELATION_LT:
+                return cb.lessThan(expr, value);
+            case RELATION_LE:
+                return cb.lessThanOrEqualTo(expr, value);
+            default:
+                throw new InternalError("bad relation");
+            } // switch
+        }
+
+        protected static final Predicate makeStringPredicate(
+                CriteriaBuilder cb, Expression<String> expr, byte relation,
+                String value) {
+            // escape SQL specific stuff
+            value = value.replace("\\", "\\\\");
+            value = value.replace("%", "\\%");
+            value = value.replace("_", "\\_");
+            // replace query language wildcards with add SQL wildcards
+            value = value.replace("*", "%");
+
+            switch (relation) {
+            case RELATION_EQ:
+                return cb.like(expr, value);
+            case RELATION_NE:
+                return cb.notLike(expr, value);
+            default:
+                throw new InternalError("bad relation");
+            } // switch
+        }
+    } // class QueryOptions.AbstractPropertyImpl
+
+    
+    private static final class PropertyImplOwner extends
+            AbstractPropertyImpl {
+        @Override
+        public Property getProperty() {
+            return Property.VC_OWNER;
+        }
+
+        @Override
+        public byte getValidRelationMask() {
+            return RELATIONS_EQ_NE;
+        }
+
+        @Override
+        public Expression<String> getExpression(Root<VirtualCollection> root) {
+            return root.get(VirtualCollection_.owner).get(User_.name);
+        }
+        
+        @Override
+        public Predicate getPredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root, byte relation, Object value) {
+            final Expression<String> expr = getExpression(root);
+            return makeStringPredicate(cb, expr, relation, (String) value);
+        }
+    } // class QueryOptions.PropertyImplOwner
+
+    
+    private static final class PropertyImplName extends
+            AbstractPropertyImpl {
+        @Override
+        public Property getProperty() {
+            return Property.VC_NAME;
+        }
+
+        @Override
+        public byte getValidRelationMask() {
+            return RELATIONS_EQ_NE;
+        }
+
+        @Override
+        public Expression<String> getExpression(Root<VirtualCollection> root) {
+            return root.get(VirtualCollection_.name);
+        }
+
+        @Override
+        public Predicate getPredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root, byte relation, Object value) {
+            final Expression<String> expr = getExpression(root);
+            return makeStringPredicate(cb, expr, relation, (String) value);
+        }
+    } // class QueryOptions.PropertyImplName
+
+    
+    private static final class PropertyImplType extends AbstractPropertyImpl {
+        @Override
+        public Property getProperty() {
+            return Property.VC_TYPE;
+        }
+
+        @Override
+        public byte getValidRelationMask() {
+            return RELATIONS_EQ_NE;
+        }
+
+        @Override
+        public Expression<VirtualCollection.Type> getExpression(
+                Root<VirtualCollection> root) {
+            return root.get(VirtualCollection_.type);
+        }
+
+        @Override
+        public Predicate getPredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root, byte relation, Object value) {
+            final Expression<VirtualCollection.Type> expr =
+                getExpression(root);
+            return makePredicate(cb, expr, relation, value);
+        }
+    } // class QueryOptions.PropertyImplName
+
+
+    private static final class PropertyImplState extends AbstractPropertyImpl {
+        @Override
+        public Property getProperty() {
+            return Property.VC_STATE;
+        }
+
+        @Override
+        public byte getValidRelationMask() {
+            return RELATIONS_EQ_NE;
+        }
+
+        @Override
+        public Expression<VirtualCollection.State> getExpression(
+                Root<VirtualCollection> root) {
+            return root.get(VirtualCollection_.state);
+        }
+
+        @Override
+        public Predicate getPredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root, byte relation, Object value) {
+            final Expression<VirtualCollection.State> expr =
+                getExpression(root);
+            return makePredicate(cb, expr, relation, value);
+        }
+    } // class QueryOptions.PropertyImplName
+    
+    private static final class PropertyImplDescription extends
+            AbstractPropertyImpl {
+        @Override
+        public Property getProperty() {
+            return Property.VC_DESCRIPTION;
+        }
+
+        @Override
+        public byte getValidRelationMask() {
+            return RELATIONS_EQ_NE;
+        }
+
+        @Override
+        public Expression<String> getExpression(Root<VirtualCollection> root) {
+            return root.get(VirtualCollection_.description);
+        }
+
+        @Override
+        public Predicate getPredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root, byte relation, Object value) {
+            final Expression<String> expr =
+                getExpression(root);
+            return makeStringPredicate(cb, expr, relation, (String) value);
+        }
+    }
+
+    private static final class PropertyImplCreationDate extends
+            AbstractPropertyImpl {
+        @Override
+        public Property getProperty() {
+            return Property.VC_CREATION_DATE;
+        }
+
+        @Override
+        public byte getValidRelationMask() {
+            return RELATIONS_ALL;
+        }
+
+        @Override
+        public Expression<Date> getExpression(Root<VirtualCollection> root) {
+            return root.get(VirtualCollection_.creationDate);
+        }
+
+        @Override
+        public Predicate getPredicate(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root, byte relation, Object value) {
+            final Expression<Date> expr = getExpression(root);
+            return makeDatePredicate(cb, expr, relation, (Date) value);
+        }
+    }
+
+} // class QueryOptions
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionMarshaller.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionMarshaller.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionMarshaller.java	(revision 1025)
@@ -0,0 +1,875 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.codehaus.jettison.mapped.MappedXMLInputFactory;
+import org.codehaus.jettison.mapped.MappedXMLOutputFactory;
+import org.codehaus.stax2.XMLInputFactory2;
+import org.codehaus.stax2.XMLOutputFactory2;
+import org.codehaus.stax2.XMLStreamReader2;
+import org.codehaus.stax2.XMLStreamWriter2;
+import org.codehaus.stax2.validation.XMLValidationSchema;
+import org.codehaus.stax2.validation.XMLValidationSchemaFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+import eu.clarin.cmdi.virtualcollectionregistry.model.GeneratedBy;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollectionList;
+
+public class VirtualCollectionMarshaller {
+    private static final String NS_CMDI = "http://www.clarin.eu/cmd";
+    private static final String NS_CMDI_PREFIX = "cmdi";
+    // FIXME: use correct schema for CMDI virtual collections
+    private static final String NS_CMDI_SCHEMA_LOCATION =
+        "http://www.clarin.eu/cmd/xsd/minimal-cmdi.xsd";
+
+    public static enum Format {
+        XML, JSON, UNSUPPORTED
+    } // public enum Format
+
+    private static final String ENCODING = "UTF-8";
+    private static final String VERSION = "1.0";
+    private static final Logger logger =
+        LoggerFactory.getLogger(VirtualCollectionMarshaller.class);
+    private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+    private final XMLValidationSchema schema;
+    private final XMLInputFactory2 xmlReaderFactory;
+    private final XMLOutputFactory2 xmlWriterFactory;
+    private final MappedXMLInputFactory jsonReaderFactory;
+    private final MappedXMLOutputFactory jsonWriterFactory;
+
+    VirtualCollectionMarshaller() throws VirtualCollectionRegistryException {
+        logger.debug("initializing schemas for marshaller ...");
+        try {
+            XMLValidationSchemaFactory sf = XMLValidationSchemaFactory
+                    .newInstance(XMLValidationSchema.SCHEMA_ID_W3C_SCHEMA);
+            URL url = VirtualCollectionMarshaller.class
+                    .getClassLoader()
+                    .getResource("META-INF/VirtualCollection.xsd");
+            if (url == null) {
+                throw new NullPointerException("schema not found!");
+            }
+            schema = sf.createSchema(url);
+
+            // XML factories
+            xmlReaderFactory =
+                (XMLInputFactory2) XMLInputFactory2.newInstance();
+            xmlReaderFactory.configureForSpeed();
+            xmlReaderFactory
+                .setProperty(XMLInputFactory2.IS_NAMESPACE_AWARE, Boolean.TRUE);
+            xmlReaderFactory
+                .setProperty(XMLInputFactory2.P_INTERN_NAMES, Boolean.TRUE);
+            xmlReaderFactory
+                .setProperty(XMLInputFactory2.P_INTERN_NS_URIS, Boolean.TRUE);
+            xmlWriterFactory =
+                (XMLOutputFactory2) XMLOutputFactory2.newInstance();
+            xmlWriterFactory.configureForSpeed();
+
+            // JSON factories
+            final Map<String, String> mapping = Collections.emptyMap();
+            jsonReaderFactory = new MappedXMLInputFactory(mapping);
+            jsonWriterFactory = new MappedXMLOutputFactory(mapping);
+        } catch (Exception e) {
+            logger.error("error initializing marshaller", e);
+            throw new VirtualCollectionRegistryException(
+                    "error initializing marshaller", e);
+        }
+    }
+
+    public void marshal(OutputStream out, Format format, VirtualCollection vc)
+            throws IOException {
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+        XMLStreamWriter writer = null;
+        try {
+            writer = createWriter(out, format, true);
+            writer.writeStartDocument(ENCODING, VERSION);
+            writeVirtualCollection(writer, vc);
+            writer.writeEndDocument();
+        } catch (XMLStreamException e) {
+            logger.error("error marshalling virtual collection", e);
+            throw new IOException("error marshalling virtual collection", e);
+        } finally {
+            try {
+                if (writer != null) {
+                    writer.close();
+                }
+            } catch (XMLStreamException e) {
+                /* IGNORE */
+            }
+        }
+    }
+
+    public void marshal(OutputStream out, Format format,
+            VirtualCollectionList vcs) throws IOException {
+        if (vcs == null) {
+            throw new NullPointerException("vcs == null");
+        }
+        XMLStreamWriter writer = null;
+        try {
+            writer = createWriter(out, format, true);
+            writer.writeStartDocument(ENCODING, VERSION);
+            writer.writeStartElement("VirtualCollections");
+            writer.writeAttribute("totalCount",
+                    Integer.toString(vcs.getTotalCount()));
+            writer.writeAttribute("offset",
+                    Integer.toString(vcs.getOffset()));
+            writer.writeAttribute("result",
+                    (vcs.isPartialList() ? "partial" : "full"));
+            for (VirtualCollection vc : vcs.getItems()) {
+                writeVirtualCollection(writer, vc);
+            }
+            writer.writeEndElement(); // "VirtualCollections" element
+            writer.writeEndDocument();
+        } catch (XMLStreamException e) {
+            logger.error("error marshalling virtual collections", e);
+            throw new IOException("error marshalling virtual collections", e);
+        } finally {
+            try {
+                if (writer != null) {
+                    writer.close();
+                }
+            } catch (XMLStreamException e) {
+                /* IGNORE */
+            }
+        }
+    }
+
+    public VirtualCollection unmarshal(InputStream in, Format format,
+            String encoding) throws IOException {
+        XMLStreamReader reader = null;
+        try {
+            reader = createReader(in, format, encoding);
+            return readVirtualCollection(reader);
+        } catch (XMLStreamException e) {
+            logger.error("error unmarshalling virtual collection", e);
+            throw new IOException("error unmarshalling virtual collection", e);
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (XMLStreamException e) {
+                /* IGNORE */
+            }
+        }
+    }
+
+    public void marshalAsCMDI(OutputStream out, Format format,
+            VirtualCollection vc) throws IOException {
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+
+        XMLStreamWriter writer = null;
+        try {
+            writer = createWriter(out, format, false);
+            writer.writeStartDocument(ENCODING, VERSION);
+            writeCMDI(writer, vc);
+            writer.writeEndDocument();
+        } catch (XMLStreamException e) {
+            logger.error("error marshalling clarin virtual collections", e);
+            throw new IOException(
+                    "error marshalling clarin virtual collections", e);
+        } finally {
+            try {
+                if (writer != null) {
+                    writer.close();
+                }
+            } catch (XMLStreamException e) {
+                /* IGNORE */
+            }
+        }
+    }
+
+    private void writeVirtualCollection(XMLStreamWriter writer,
+            VirtualCollection vc) throws XMLStreamException {
+        writer.writeStartElement("VirtualCollection");
+        if (vc.getId() != null) {
+            writer.writeAttribute("id", Long.toString(vc.getId()));
+        }
+        if (vc.getPersistentIdentifier() != null) {
+            writer.writeAttribute("persistentId",
+                    vc.getPersistentIdentifier().getIdentifier());
+        }
+        String s = null;
+        switch (vc.getState()) {
+        case PRIVATE:
+            s = "private";
+            break;
+        case PUBLIC_PENDING:
+            s = "public-pending";
+            break;
+        case PUBLIC:
+            s = "public";
+        case DELETED:
+            s = "deleted";
+        case DEAD:
+            s = "dead";
+            break;
+        }
+        writer.writeAttribute("state", s);
+        writer.writeStartElement("Type");
+        switch (vc.getType()) {
+        case EXTENSIONAL:
+            writer.writeCharacters("extensional");
+            break;
+        case INTENSIONAL:
+            writer.writeCharacters("intensional");
+            break;
+        } // switch
+        writer.writeEndElement(); // "Type" element
+
+        writer.writeStartElement("Name");
+        writer.writeCharacters(vc.getName());
+        writer.writeEndElement(); // "Name" element
+
+        if (vc.getDescription() != null) {
+            writer.writeStartElement("Description");
+            writer.writeCharacters(vc.getDescription());
+            writer.writeEndElement(); // "Description" element
+        }
+
+        if (vc.getCreationDate() != null) {
+            writer.writeStartElement("CreationDate");
+            // XXX: threading vs. performance
+            synchronized (df) {
+                writer.writeCharacters(df.format(vc.getCreationDate()));
+            } // synchronized (df)
+            writer.writeEndElement(); // "CreationDate" element
+        }
+
+        if ((vc.getCreators() != null) && !vc.getCreators().isEmpty()) {
+            writer.writeStartElement("Creators");
+            for (Creator creator : vc.getCreators()) {
+                writer.writeStartElement("Creator");
+
+                writer.writeStartElement("Person");
+                writer.writeCharacters(creator.getPerson());
+                writer.writeEndElement(); // "Person" element
+
+                if (creator.getAddress() != null) {
+                    writer.writeStartElement("Address");
+                    writer.writeCharacters(creator.getAddress());
+                    writer.writeEndElement(); // "Address" element
+                }
+
+                if (creator.getEMail() != null) {
+                    writer.writeStartElement("Email");
+                    writer.writeCharacters(creator.getEMail());
+                    writer.writeEndElement(); // "Email" element
+                }
+
+                if (creator.getOrganisation() != null) {
+                    writer.writeStartElement("Organisation");
+                    writer.writeCharacters(creator.getOrganisation());
+                    writer.writeEndElement(); // "Organisation" element
+                }
+
+                if (creator.getTelephone() != null) {
+                    writer.writeStartElement("Telephone");
+                    writer.writeCharacters(creator.getTelephone());
+                    writer.writeEndElement(); // "Telephone" element
+                }
+
+                if (creator.getWebsite() != null) {
+                    writer.writeStartElement("Website");
+                    writer.writeCharacters(creator.getWebsite());
+                    writer.writeEndElement(); // "Website" element
+                }
+
+                if (creator.getRole() != null) {
+                    writer.writeStartElement("Role");
+                    writer.writeCharacters(creator.getRole());
+                    writer.writeEndElement(); // "Role" element
+                }
+
+                writer.writeEndElement(); // "Creator" element
+            }
+            writer.writeEndElement(); // "Creators" element
+        } // Creators
+
+        if (vc.getPurpose() != null) {
+            writer.writeStartElement("Purpose");
+            switch (vc.getPurpose()) {
+            case RESEARCH:
+                writer.writeCharacters("research");
+                break;
+            case REFERENCE:
+                writer.writeCharacters("reference");
+                break;
+            case SAMPLE:
+                writer.writeCharacters("sample");
+                break;
+            case FUTURE_USE:
+                writer.writeCharacters("future-use");
+                break;
+            } // switch
+            writer.writeEndElement(); // "Purpose" element
+        }
+
+        if (vc.getReproducibility() != null) {
+            writer.writeStartElement("Reproducibility");
+            switch (vc.getReproducibility()) {
+            case INTENDED:
+                writer.writeCharacters("intended");
+                break;
+            case FLUCTUATING:
+                writer.writeCharacters("fluctuating");
+                break;
+            case UNTENDED:
+                writer.writeCharacters("untended");
+                break;
+            } // switch
+            writer.writeEndElement(); // "Reproducibility" element
+        }
+
+        if (vc.getReproducibilityNotice() != null) {
+            writer.writeStartElement("ReproducibilityNotice");
+            writer.writeCharacters(vc.getReproducibilityNotice());
+            writer.writeEndElement(); // "ReproducibilityNotice" element
+        }
+
+        if ((vc.getKeywords() != null) && !vc.getKeywords().isEmpty()) {
+            writer.writeStartElement("Keywords");
+
+            for (final String keyword : vc.getKeywords()) {
+                writer.writeStartElement("Keyword");
+                writer.writeCharacters(keyword);
+                writer.writeEndElement(); // "Keyword" element
+            }
+
+            writer.writeEndElement(); // "Keywords" element
+        } // Keywords
+
+        if ((vc.getResources() != null) && !vc.getResources().isEmpty()) {
+            writer.writeStartElement("Resources");
+
+            for (final Resource resource : vc.getResources()) {
+                writer.writeStartElement("Resource");
+
+                writer.writeStartElement("ResourceType");
+                switch (resource.getType()) {
+                case METADATA:
+                    writer.writeCharacters("Metadata");
+                    break;
+                case RESOURCE:
+                    writer.writeCharacters("Resource");
+                    break;
+                } // switch
+                writer.writeEndElement(); // "ResourceType" element
+
+                writer.writeStartElement("ResourceRef");
+                writer.writeCharacters(resource.getRef());
+                writer.writeEndElement(); // "ResourceRef" element
+
+                writer.writeEndElement(); // "Resource" element
+            }
+
+            writer.writeEndElement(); // "Resources" element
+        } // Resources
+
+        if (vc.getGeneratedBy() != null) {
+            final GeneratedBy generatedBy = vc.getGeneratedBy();
+            writer.writeStartElement("GeneratedBy");
+
+            writer.writeStartElement("Description");
+            writer.writeCharacters(generatedBy.getDescription());
+            writer.writeEndElement(); // "Description" element
+
+            if (generatedBy.getURI() != null) {
+                writer.writeStartElement("URI");
+                writer.writeCharacters(generatedBy.getURI());
+                writer.writeEndElement(); // "URI" element
+            }
+
+            if (generatedBy.getQuery() != null) {
+                GeneratedBy.Query query = generatedBy.getQuery();
+                writer.writeStartElement("Query");
+                writer.writeAttribute("profile", query.getProfile());
+                writer.writeCData(query.getValue());
+                writer.writeEndElement(); // "Query" element
+            }
+
+            writer.writeEndElement(); // "GeneratedBy" element
+        }
+
+        writer.writeEndElement(); // "VirtualCollection" element
+    }
+
+    private VirtualCollection readVirtualCollection(
+            XMLStreamReader reader) throws XMLStreamException {
+        readStart(reader, "VirtualCollection", true, false);
+
+        VirtualCollection vc = new VirtualCollection();
+        String s = reader.getAttributeValue(null, "state");
+        if ((s != null) && !s.isEmpty()) {
+            if ("private".equals(s)) {
+                vc.setState(VirtualCollection.State.PRIVATE);
+            } else if ("public-pending".equals(s)) {
+                vc.setState(VirtualCollection.State.PUBLIC_PENDING);
+            } else if ("public".equals(s)) {
+                vc.setState(VirtualCollection.State.PUBLIC);
+            } else if ("deleted".equals(s)) {
+                vc.setState(VirtualCollection.State.DELETED);
+            } else if ("dead".equals(s)) {
+                vc.setState(VirtualCollection.State.DEAD);
+            } else {
+                throw new XMLStreamException("invalid value for attribute " +
+                        "'state' on element 'VirtualCollecion', expected one " +
+                        "of 'private', 'public-pending', 'public', " +
+                        "'deleted' or 'dead'");
+            }
+        }
+        reader.next();
+        readStart(reader, "Type", true, true);
+        s = readString(reader, true);
+        if ("extensional".equals(s)) {
+            vc.setType(VirtualCollection.Type.EXTENSIONAL);
+        } else if ("intensional".equals(s)) {
+            vc.setType(VirtualCollection.Type.INTENSIONAL);
+        } else {
+            throw new XMLStreamException("invalid value for element 'Type', " +
+                    "expected either 'extensional' or 'intensional'");
+        }
+        readStart(reader, "Name", true, true);
+        vc.setName(readString(reader, false));
+        if (readStart(reader, "Description", false, true)) {
+            vc.setDescription(readString(reader, false));
+        }
+        if (readStart(reader, "CreationDate", false, true)) {
+            try {
+                // XXX: threading vs. performance
+                synchronized (df) {
+                    vc.setCreationDate(df.parse(readString(reader, false)));
+                } // synchronized df
+            } catch (ParseException e) {
+                throw new XMLStreamException("invalid date format", e);
+            }
+        }
+        if (readStart(reader, "Creators", false, true)) {
+            readStart(reader, "Creator", true, true);
+            do {
+                readStart(reader, "Person", true, true);
+                Creator creator = new Creator(readString(reader, false));
+                if (readStart(reader, "Address", false, true)) {
+                    creator.setAddress(readString(reader, false));
+                }
+                if (readStart(reader, "Email", false, true)) {
+                    creator.setEMail(readString(reader, false));
+                }
+                if (readStart(reader, "Organisation", false, true)) {
+                    creator.setOrganisation(readString(reader, false));
+                }
+                if (readStart(reader, "Telephone", false, true)) {
+                    creator.setTelephone(readString(reader, false));
+                }
+                if (readStart(reader, "Website", false, true)) {
+                    creator.setWebsite(readString(reader, false));
+                }
+                if (readStart(reader, "Role", false, true)) {
+                    creator.setRole(readString(reader, false));
+                }
+                vc.getCreators().add(creator);
+            } while (readStart(reader, "Creator", false, true));
+        }
+        if (readStart(reader, "Purpose", false, true)) {
+            s = readString(reader, true);
+            if ("research".equals(s)) {
+                vc.setPurpose(VirtualCollection.Purpose.RESEARCH);
+            } else if ("reference".equals(s)) {
+                vc.setPurpose(VirtualCollection.Purpose.REFERENCE);
+            } else if ("sample".equals(s)) {
+                vc.setPurpose(VirtualCollection.Purpose.SAMPLE);
+            } else if ("future-use".equals(s)) {
+                vc.setPurpose(VirtualCollection.Purpose.FUTURE_USE);
+            } else {
+                throw new XMLStreamException("invalid value for element " +
+                        "'Purpose', expected one of 'research', 'reference'," +
+                        " 'sample' or 'future-use'");
+            }
+        }
+        if (readStart(reader, "Reproducibility", false, true)) {
+            s = readString(reader, true);
+            if ("intended".equals(s)) {
+                vc.setReproducibility(VirtualCollection
+                        .Reproducibility.INTENDED);
+            } else if ("fluctuating".equals(s)) {
+                vc.setReproducibility(VirtualCollection
+                        .Reproducibility.FLUCTUATING);
+            } else if ("untended".endsWith(s)) {
+                vc.setReproducibility(VirtualCollection
+                        .Reproducibility.UNTENDED);
+            } else {
+                throw new XMLStreamException("invalid value for element " +
+                        "'Reproducibility', expected one of 'intended', "+
+                        "'fluctuating' or 'fluctuating'");
+            }
+        }
+        if (readStart(reader, "ReproducibilityNotice", false, true)) {
+            vc.setReproducibilityNotice(readString(reader, false));
+        }
+        if (readStart(reader, "Keywords", false, true)) {
+            readStart(reader, "Keyword", true, true);
+            do {
+                vc.getKeywords().add(readString(reader, false));
+            } while (readStart(reader, "Keyword", false, true));
+        }
+        if (readStart(reader, "Resources", false, true)) {
+            readStart(reader, "Resource", true, true);
+            do {
+                readStart(reader, "ResourceType", true, true);
+                Resource.Type r_type;
+                s = readString(reader, true);
+                if ("Metadata".equals(s)) {
+                    r_type = Resource.Type.METADATA;
+                } else if ("Resource".equals(s)) {
+                    r_type = Resource.Type.RESOURCE;
+                } else {
+                    throw new XMLStreamException("invalid value for element " +
+                            "'ResourceType', expected either 'Metadata' " +
+                            "or 'Resource'");
+                }
+                readStart(reader, "ResourceRef", true, true);
+                vc.getResources().add(
+                        new Resource(r_type, readString(reader, false)));
+            } while (readStart(reader, "Resource", false, true));
+        }
+        if (readStart(reader, "GeneratedBy", false, true)) {
+            readStart(reader, "Description", true, true);
+            GeneratedBy generatedBy =
+                new GeneratedBy(readString(reader, false));
+            if (readStart(reader, "URI", false, true)) {
+                generatedBy.setURI(readString(reader, false));
+            }
+            if (readStart(reader, "Query", false, false)) {
+                s = reader.getAttributeValue(null, "profile");
+                if ((s == null) || s.isEmpty()) {
+                    throw new XMLStreamException("missing or empty " +
+                            "attribute 'profile' on element 'Query'");
+                }
+                reader.next();
+                final GeneratedBy.Query query =
+                    new GeneratedBy.Query(s, readString(reader, false));
+                generatedBy.setQuery(query);
+            }
+            vc.setGeneratedBy(generatedBy);
+        }
+        // skip to end of virtual collection
+        for (int t = reader.getEventType();
+             reader.hasNext();
+             t = reader.next()) {
+            if ((t == XMLStreamConstants.END_ELEMENT) &&
+                    "VirtualCollection".equals(reader.getLocalName())) {
+                break;
+            }
+        }
+        return vc;
+    }
+
+    public void writeCMDI(XMLStreamWriter out, VirtualCollection vc)
+            throws XMLStreamException {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+
+        // FIXME: use FastDateFormat?
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+
+        out.setPrefix(NS_CMDI_PREFIX, NS_CMDI);
+        out.writeStartElement(NS_CMDI, "CMD");
+        out.writeNamespace(NS_CMDI_PREFIX, NS_CMDI);
+        /*
+         * FIXME: Use the correct schema for virtual collections here. For now,
+         * just use minimal-cmdi.xsd. However, XML validation will probably fail
+         * because minimal-cmdi.xsd does neither declare the "targetNamespace"
+         * nor the "elementFormDefault" attributes.
+         */
+        out.writeNamespace("xsi",
+                XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
+        out.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
+                "schemaLocation", NS_CMDI + " " + NS_CMDI_SCHEMA_LOCATION);
+
+        /*
+         * header
+         */
+        out.writeStartElement(NS_CMDI, "Header");
+        out.writeStartElement(NS_CMDI, "MdCreator");
+        out.writeCharacters(vc.getOwner().getName());
+        out.writeEndElement(); // "MdCreator" element
+        out.writeStartElement(NS_CMDI, "MdCreationDate");
+        out.writeCharacters(df.format(vc.getDateCreated()));
+        out.writeEndElement(); // "MdCreationDate" element
+        out.writeStartElement(NS_CMDI, "MdSelfLink");
+
+        out.writeCharacters(vc.getPersistentIdentifier().getActionableURI());
+        out.writeEndElement(); // "MdSelfLink" element
+        out.writeStartElement(NS_CMDI, "MdProfile");
+        out.writeCharacters(NS_CMDI_SCHEMA_LOCATION);
+        out.writeEndElement(); // "MdProfile" element
+        out.writeEndElement(); // "Header" element
+
+        /*
+         * resources
+         */
+        out.writeStartElement(NS_CMDI, "Resources");
+        out.writeStartElement(NS_CMDI, "ResourceProxyList");
+        for (Resource resource : vc.getResources()) {
+            out.writeStartElement(NS_CMDI, "ResourceProxy");
+            out.writeAttribute("id", "r" + resource.getId());
+            out.writeStartElement(NS_CMDI, "ResourceType");
+            switch (resource.getType()) {
+            case METADATA:
+                out.writeCharacters("Metadata");
+                break;
+            case RESOURCE:
+                out.writeCharacters("Resource");
+                break;
+            } // switch
+            out.writeEndElement(); // "ResourceType" element
+            out.writeStartElement(NS_CMDI, "ResourceRef");
+            out.writeCharacters(resource.getRef());
+            out.writeEndElement(); // "ResourceRef" element
+            out.writeEndElement(); // "ResourceProxy" element
+        } // for (resource)
+        out.writeEndElement(); // "ResourceProxyList" element
+        out.writeEmptyElement(NS_CMDI, "JournalFileProxyList");
+        out.writeEmptyElement(NS_CMDI, "ResourceRelationList");
+        out.writeEndElement(); // "Resources"
+
+        /*
+         * components
+         */
+        out.writeStartElement(NS_CMDI, "Components");
+        out.writeStartElement(NS_CMDI, "VirtualCollection");
+        out.writeStartElement(NS_CMDI, "Name");
+        out.writeCharacters(vc.getName());
+        out.writeEndElement(); // "Name" element
+        if (vc.getDescription() != null) {
+            out.writeStartElement(NS_CMDI, "Description");
+            out.writeCharacters(vc.getDescription());
+            out.writeEndElement(); // "Description" element
+        }
+        out.writeStartElement(NS_CMDI, "CreationDate");
+        out.writeCharacters(df.format(vc.getCreationDate()));
+        out.writeEndElement(); // "CreationDate" element
+        if ((vc.getCreators() != null) || !vc.getCreators().isEmpty()) {
+            out.writeStartElement(NS_CMDI, "Creators");
+            for (Creator creator : vc.getCreators()) {
+                // make sure there is anything set in creator
+                if ((creator.getPerson() != null) ||
+                        (creator.getEMail() != null) ||
+                        (creator.getOrganisation() != null)) {
+                    out.writeStartElement(NS_CMDI, "Creator");
+                    if (creator.getPerson() != null) {
+                        out.writeStartElement(NS_CMDI, "Person");
+                        out.writeCharacters(creator.getPerson());
+                        out.writeEndElement(); // "Person" element
+                    }
+                    if (creator.getAddress() != null) {
+                        out.writeStartElement(NS_CMDI, "Address");
+                        out.writeCharacters(creator.getAddress());
+                        out.writeEndElement(); // "Address" element
+                    }
+                    if (creator.getEMail() != null) {
+                        out.writeStartElement(NS_CMDI, "Email");
+                        out.writeCharacters(creator.getEMail());
+                        out.writeEndElement(); // "Email" element
+                    }
+                    if (creator.getOrganisation() != null) {
+                        out.writeStartElement(NS_CMDI, "Organisation");
+                        out.writeCharacters(creator.getOrganisation());
+                        out.writeEndElement(); // "Organisation" element
+                    }
+                    if (creator.getTelephone() != null) {
+                        out.writeStartElement(NS_CMDI, "Telephone");
+                        out.writeCharacters(creator.getTelephone());
+                        out.writeEndElement(); // "Telephone" element
+                    }
+                    if (creator.getWebsite() != null) {
+                        out.writeStartElement(NS_CMDI, "Website");
+                        out.writeCharacters(creator.getWebsite());
+                        out.writeEndElement(); // "Website" element
+                    }
+                    if (creator.getRole() != null) {
+                        out.writeStartElement(NS_CMDI, "Role");
+                        out.writeCharacters(creator.getRole());
+                        out.writeEndElement(); // "Role" element
+                    }
+                    out.writeEndElement(); // "Creator" element
+                }
+            } // for (Creator ...)
+            out.writeEndElement(); // "Creators" element
+        }
+        if (vc.getPurpose() != null) {
+            out.writeStartElement(NS_CMDI, "Purpose");
+            switch (vc.getPurpose()) {
+            case RESEARCH:
+                out.writeCharacters("research");
+                break;
+            case REFERENCE:
+                out.writeCharacters("reference");
+                break;
+            case SAMPLE:
+                out.writeCharacters("sample");
+                break;
+            case FUTURE_USE:
+                out.writeCharacters("future-use");
+                break;
+            } // switch (purpose)
+            out.writeEndElement(); // "Purpose" element
+        }
+        if (vc.getReproducibility() != null) {
+            out.writeStartElement(NS_CMDI, "Reproducibility");
+            switch (vc.getReproducibility()) {
+            case INTENDED:
+                out.writeCharacters("intended");
+                break;
+            case FLUCTUATING:
+                out.writeCharacters("fluctuating");
+                break;
+            case UNTENDED:
+                out.writeCharacters("untended");
+                break;
+            } // switch (purpose)
+            out.writeEndElement(); // "Reproducibility" element
+        }
+        if (vc.getReproducibilityNotice() != null) {
+            out.writeStartElement(NS_CMDI, "ReproducibilityNotice");
+            out.writeCharacters(vc.getReproducibilityNotice());
+            out.writeEndElement(); // "ReproducibilityNotice" element
+        }
+        out.writeEndElement(); // "VirtualCollection" element
+        out.writeEndElement(); // "Components" element
+
+        out.writeEndElement(); // "CMD" element (root)
+    }
+
+    private XMLStreamWriter createWriter(OutputStream out, Format format,
+            boolean validate) throws XMLStreamException {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        if (format == null) {
+            throw new NullPointerException("format == null");
+        }
+
+        switch (format) {
+        case XML:
+            XMLStreamWriter writer =
+                xmlWriterFactory.createXMLStreamWriter(out, ENCODING);
+            if (validate) {
+                ((XMLStreamWriter2) writer).validateAgainst(schema);
+            }
+            return writer;
+        case JSON:
+            return jsonWriterFactory.createXMLStreamWriter(out, ENCODING);
+        default:
+            // should never happen
+            throw new IllegalArgumentException("unsupported input format");
+        } // switch
+    }
+
+    private XMLStreamReader createReader(InputStream in, Format format,
+            String encoding) throws XMLStreamException {
+        if (in == null) {
+            throw new NullPointerException("in == null");
+        }
+        if (format == null) {
+            throw new NullPointerException("format == null");
+        }
+        if (encoding == null) {
+            throw new NullPointerException("encoding == null");
+        }
+
+        switch (format) {
+        case XML:
+            XMLStreamReader reader =
+                xmlReaderFactory.createXMLStreamReader(in, encoding);
+            ((XMLStreamReader2) reader).validateAgainst(schema);
+            return reader;
+        case JSON:
+            // FIXME: json + schema validation?
+            return jsonReaderFactory.createXMLStreamReader(in, encoding);
+        default:
+            // should never happen
+            throw new IllegalArgumentException("unsupported input format");
+        } // switch
+    }
+
+    private static boolean readStart(XMLStreamReader reader, String element,
+            boolean required, boolean advance) throws XMLStreamException {
+        for (int type = reader.getEventType();
+             reader.hasNext();
+             type = reader.next()) {
+            if (type == XMLStreamConstants.START_ELEMENT) {
+                if (element.equals(reader.getLocalName())) {
+                    if (advance) {
+                        reader.next(); // advance to next tag
+                    }
+                    return true;
+                }
+                break;
+            }
+        } // for
+        if (required) {
+            throw new XMLStreamException("expected element '" +  element +
+                    "' at this position");
+        }
+        return false;
+    }
+
+    private static String readString(XMLStreamReader reader, boolean intern)
+            throws XMLStreamException {
+        int type = reader.getEventType();
+        while (type == XMLStreamConstants.SPACE) {
+            type = reader.next();
+        }
+        switch (type) {
+        case XMLStreamConstants.CHARACTERS:
+            /* FALL-TROUGH */
+        case XMLStreamConstants.CDATA:
+            if (!reader.hasNext()) {
+                throw new XMLStreamException("unexpected end of stream");
+            }
+            String s = reader.getText();
+            if (s != null) {
+                s = s.trim();
+                if (intern) {
+                    s = s.intern();
+                }
+            }
+            if (s == null || s.isEmpty()) {
+                throw new XMLStreamException("expected character content " +
+                        "at this position", reader.getLocation());
+            }
+            reader.next();
+            return s;
+        default:
+            throw new XMLStreamException("expected character content " +
+                    "at this position", reader.getLocation());
+        } // switch
+    }
+
+} // class VirtualCollectionRegistryMarshaller
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionNotFoundException.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionNotFoundException.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionNotFoundException.java	(revision 1025)
@@ -0,0 +1,11 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+public class VirtualCollectionNotFoundException extends
+        VirtualCollectionRegistryException {
+    private static final long serialVersionUID = 1L;
+
+    public VirtualCollectionNotFoundException(long id) {
+        super("virtual collection with id " + id + " was not found");
+    }
+
+} // class VirtualCollectionNotFoundException
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistry.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistry.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistry.java	(revision 1025)
@@ -0,0 +1,649 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import javax.persistence.LockModeType;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Order;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.clarin.cmdi.oai.provider.OAIException;
+import eu.clarin.cmdi.oai.provider.impl.OAIProvider;
+import eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifier;
+import eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifierProvider;
+import eu.clarin.cmdi.virtualcollectionregistry.model.User;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollectionList;
+import eu.clarin.cmdi.virtualcollectionregistry.query.ParsedQuery;
+
+public class VirtualCollectionRegistry {
+    private static final Logger logger =
+        LoggerFactory.getLogger(VirtualCollectionRegistry.class);
+    private static final VirtualCollectionRegistry s_instance =
+        new VirtualCollectionRegistry();
+    private AtomicBoolean intialized = new AtomicBoolean(false);
+    private DataStore datastore = null;
+    private PersistentIdentifierProvider pid_provider = null;
+    private VirtualCollectionMarshaller marshaller = null;
+    private Timer timer =
+        new Timer("VirtualCollectionRegistry-Maintenance", true);
+
+    private VirtualCollectionRegistry() {
+        super();
+    }
+
+    public static void initalize(Map<String, String> config)
+            throws VirtualCollectionRegistryException {
+        s_instance.doInitalize(config);
+    }
+
+    private void doInitalize(Map<String, String> config)
+            throws VirtualCollectionRegistryException {
+        if (intialized.get()) {
+            throw new VirtualCollectionRegistryException("already initialized");
+        }
+        logger.debug("initializing virtual collection registry ...");
+        if (config != null) {
+            config = Collections.unmodifiableMap(config);
+        } else {
+            config = Collections.emptyMap();
+        }
+        try {
+            // XXX: the whole config / setup stuff is not very beautiful
+            this.datastore = new DataStore(config);
+            this.pid_provider = PersistentIdentifierProvider
+                    .createProvider(config);
+            this.marshaller = new VirtualCollectionMarshaller();
+            // setup OAIProvider
+            OAIProvider.instance().setRepository(
+                    new VirtualColletionRegistryOAIRepository(this));
+            // setup VCR maintenance task
+            timer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    maintenance(this.scheduledExecutionTime());
+                }
+            }, 60000, 60000);
+            this.intialized.set(true);
+            logger.info("virtual collection registry successfully intialized");
+        } catch (RuntimeException e) {
+            logger.error("error initalizing virtual collection registry", e);
+            throw e;
+        } catch (OAIException e) {
+            logger.error("error initalizing virtual collection registry", e);
+            throw new VirtualCollectionRegistryException(
+                    "setting OAI repository failed", e);
+        } catch (VirtualCollectionRegistryException e) {
+            logger.error("error initalizing virtual collection registry", e);
+            throw e;
+        }
+    }
+
+    public void destroy() throws VirtualCollectionRegistryException {
+        timer.cancel();
+        if (datastore != null) {
+            datastore.destroy();
+        }
+        OAIProvider.instance().shutdown();
+    }
+
+    public static VirtualCollectionRegistry instance() {
+        if (!s_instance.intialized.get()) {
+            throw new InternalError("virtual collection registry failed " +
+                                    "to initialize correctly");
+        }
+        return s_instance;
+    }
+
+    public DataStore getDataStore() {
+        return datastore;
+    }
+
+    public VirtualCollectionMarshaller getMarshaller() {
+        return marshaller;
+    }
+
+    public long createVirtualCollection(Principal principal,
+            VirtualCollection vc) throws VirtualCollectionRegistryException {
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+
+        logger.debug("creating virtual collection");
+
+        VirtualCollectionValidator validator =
+            new VirtualCollectionValidator();
+        validator.validate(vc);
+        try {
+            EntityManager em = datastore.getEntityManager();
+            em.getTransaction().begin();
+
+            // fetch user, if user does not exist create new
+            User user = fetchUser(em, principal);
+            if (user == null) {
+                user = new User(principal.getName());
+                em.persist(user);
+            }
+
+            // store virtual collection
+            vc.setOwner(user);
+            em.persist(vc);
+            em.getTransaction().commit();
+            return vc.getId();
+        } catch (Exception e) {
+            logger.error("error while creating virtual collection", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while creating virtual collection", e);
+        }
+    }
+
+    public long updateVirtualCollection(Principal principal, long id,
+            VirtualCollection vc) throws VirtualCollectionRegistryException {
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        if (id <= 0) {
+            throw new IllegalArgumentException("id <= 0");
+        }
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+
+        logger.debug("updating virtual collection (id={})", id);
+
+        VirtualCollectionValidator validator =
+            new VirtualCollectionValidator();
+        validator.validate(vc);
+
+        try {
+            EntityManager em = datastore.getEntityManager();
+            em.getTransaction().begin();
+            VirtualCollection c = em.find(VirtualCollection.class, new Long(id),
+                    LockModeType.PESSIMISTIC_WRITE);
+            /*
+             * Do not check for deleted state here, as we might want to
+             * resurrect deleted virtual collections.
+             */
+            if (c == null) {
+                logger.debug("virtual collection (id={}) not found", id);
+                throw new VirtualCollectionNotFoundException(id);
+            }
+            if (!c.getOwner().equalsPrincipal(principal)) {
+                throw new VirtualCollectionRegistryPermissionException(
+                        "permission denied for user \"" +
+                        principal.getName() + "\"");
+            }
+            
+            // update virtual collection
+            c.updateFrom(vc);
+
+            validator.validate(c);
+            em.merge(c);
+            em.getTransaction().commit();
+            logger.debug("updated virtual collection (id={})", vc.getId());
+            return c.getId();
+        } catch (VirtualCollectionRegistryException e) {
+            logger.debug("failed updating virtual collecion (id={}): {}", id,
+                    e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            logger.error("error while updating virtual collection", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while updating virtual collection", e);
+        }
+    }
+
+    public long deleteVirtualCollection(Principal principal, long id)
+            throws VirtualCollectionRegistryException {
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        if (id <= 0) {
+            throw new IllegalArgumentException("id <= 0");
+        }
+
+        logger.debug("deleting virtual collection (id={})", id);
+
+        try {
+            EntityManager em = datastore.getEntityManager();
+            em.getTransaction().begin();
+            VirtualCollection vc = em.find(VirtualCollection.class,
+                    new Long(id), LockModeType.PESSIMISTIC_WRITE);
+            if ((vc == null) || vc.isDeleted()) {
+                logger.debug("virtual collection (id={}) not found", id);
+                throw new VirtualCollectionNotFoundException(id);
+            }
+            if (!vc.getOwner().equalsPrincipal(principal)) {
+                logger.debug("virtual collection (id={}) not owned by " +
+                        "user '{}'", id, principal.getName());
+                throw new VirtualCollectionRegistryPermissionException(
+                        "permission denied for user \"" +
+                        principal.getName() + "\"");
+            }
+            if (!vc.isPrivate()) {
+                logger.debug("virtual collection (id={}) cannot be " +
+                        "deleted (invalid state)", id);
+                throw new VirtualCollectionRegistryPermissionException(
+                        "virtual collection cannot be deleted");
+            }
+            vc.setState(VirtualCollection.State.DELETED);
+            em.getTransaction().commit();
+            return vc.getId();
+        } catch (VirtualCollectionRegistryException e) {
+            logger.debug("failed deleting virtual collecion (id={}): {}", id,
+                    e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            logger.error("error while deleting virtual collection", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while deleting virtual collection", e);
+        }
+    }
+
+    public VirtualCollection.State getVirtualCollectionState(long id)
+            throws VirtualCollectionRegistryException {
+        if (id <= 0) {
+            throw new IllegalArgumentException("id <= 0");
+        }
+
+        logger.debug("retrieve virtual collection state (id={})", id);
+
+        try {
+            EntityManager em = datastore.getEntityManager();
+            em.getTransaction().begin();
+            VirtualCollection vc =
+                em.find(VirtualCollection.class, new Long(id));
+            em.getTransaction().commit();
+            if ((vc == null) || vc.isDeleted()) {
+                logger.debug("virtual collection (id={}) not found", id);
+                throw new VirtualCollectionNotFoundException(id);
+            }
+            return vc.getState();
+        } catch (VirtualCollectionRegistryException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.error(
+                    "error while retrieving state of virtual collection", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while retrieving state of virtual collection", e);
+        }
+    }
+
+    public void setVirtualCollectionState(Principal principal, long id,
+            VirtualCollection.State state)
+            throws VirtualCollectionRegistryException {
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        if (id <= 0) {
+            throw new IllegalArgumentException("id <= 0");
+        }
+        if (state == null) {
+            throw new NullPointerException("state == null");
+        }
+        if ((state != VirtualCollection.State.PUBLIC_PENDING) &&
+            (state != VirtualCollection.State.PRIVATE)) {
+            throw new IllegalArgumentException(
+                    "only PUBLIC_PENDING or PRIVATE are allowed");
+        }
+
+        logger.debug("setting state virtual collection state (id={}) to '{}'",
+                id, state);
+
+        try {
+            EntityManager em = datastore.getEntityManager();
+            em.getTransaction().begin();
+            VirtualCollection vc = em.find(VirtualCollection.class,
+                    new Long(id), LockModeType.PESSIMISTIC_WRITE);
+            if ((vc == null) || vc.isDeleted()) {
+                logger.debug("virtual collection (id={}) not found", id);
+                throw new VirtualCollectionNotFoundException(id);
+            }
+            if (!vc.getOwner().equalsPrincipal(principal)) {
+                logger.debug("virtual collection (id={}) not owned by " +
+                        "user '{}'", id, principal.getName());
+                throw new VirtualCollectionRegistryPermissionException(
+                        "permission denied for user \"" +
+                        principal.getName() + "\"");
+            }
+
+            /*
+             * XXX: deny update from public to private? 
+             */
+            boolean update = false;
+            switch (state) {
+            case PRIVATE:
+                update =  vc.getState() != state;
+                break;
+            case PUBLIC_PENDING:
+                update =  vc.getState() != VirtualCollection.State.PUBLIC;
+                break;
+            }
+            if (update) {
+                vc.setState(state);
+                em.persist(vc);
+            }
+            em.getTransaction().commit();
+        } catch (VirtualCollectionRegistryException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.error(
+                    "error while setting state of virtual collection", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while setting state of virtual collection", e);
+        }
+    }
+    
+    public VirtualCollection retrieveVirtualCollection(long id)
+            throws VirtualCollectionRegistryException {
+        if (id <= 0) {
+            throw new IllegalArgumentException("id <= 0");
+        }
+
+        logger.debug("retrieve virtual collection (id={})", id);
+
+        try {
+            EntityManager em = datastore.getEntityManager();
+            em.getTransaction().begin();
+            VirtualCollection vc =
+                em.find(VirtualCollection.class, new Long(id));
+            em.getTransaction().commit();
+            if ((vc == null) || vc.isDeleted()) {
+                logger.debug("virtual collection (id={}) not found", id);
+                throw new VirtualCollectionNotFoundException(id);
+            }
+            return vc;
+        } catch (VirtualCollectionRegistryException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.error("error while retrieving virtual collection", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while retrieving virtual collection", e);
+        }
+    }
+
+    public VirtualCollectionList getVirtualCollections(String query,
+            int offset, int count) throws VirtualCollectionRegistryException {
+        EntityManager em = datastore.getEntityManager();
+        try {
+            em.getTransaction().begin();
+
+            // setup queries
+            TypedQuery<Long> cq = null;
+            TypedQuery<VirtualCollection> q = null;
+            if (query != null) {
+                ParsedQuery parsedQuery = ParsedQuery.parseQuery(query);
+                if (logger.isDebugEnabled()) {
+                    logger.debug(parsedQuery.getPrettyPrinted());
+                }
+                cq = parsedQuery.getCountQuery(em, null, VirtualCollection.State.PUBLIC);
+                q = parsedQuery.getQuery(em, null, VirtualCollection.State.PUBLIC);
+            } else {
+                cq = em.createNamedQuery("VirtualCollection.countAllPublic",
+                        Long.class);
+                q = em.createNamedQuery("VirtualCollection.findAllPublic",
+                        VirtualCollection.class);
+            }
+
+            // commence query ...
+            List<VirtualCollection> results = null;
+            long totalCount = cq.getSingleResult();
+
+            // optimization; don't query, if we won't get any results
+            /*
+             *  FIXME: offset == -1 is temporary hack for just fetching
+             *  total count; re-factor to have fetch-count and fetch-data
+             *  methods!
+             */
+            if ((totalCount > 0) && (offset > -1)) {
+                if (offset > 0) {
+                    q.setFirstResult(offset);
+                }
+                if (count > 0) {
+                    q.setMaxResults(count);
+                }
+                results = q.getResultList();
+            }
+            return new VirtualCollectionList(results, offset, (int) totalCount);
+        } catch (Exception e) {
+            logger.error("error while enumerating virtual collections", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while enumerating virtual collections", e);
+        } finally {
+            EntityTransaction tx = em.getTransaction();
+            if ((tx != null) && !tx.getRollbackOnly()) {
+                tx.commit();
+            }
+        }
+    }
+
+    public VirtualCollectionList getVirtualCollections(Principal principal,
+            String query, int offset, int count)
+            throws VirtualCollectionRegistryException {
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        EntityManager em = datastore.getEntityManager();
+        try {
+            List<VirtualCollection> results = null;
+            long totalCount = 0;
+
+            em.getTransaction().begin();
+
+            /*
+             * fetch user. if user is not found, he has not yet registered any
+             * virtual collections, so just return an empty list
+             */
+            User user = fetchUser(em, principal);
+            if (user != null) {
+                // setup queries
+                TypedQuery<Long> cq = null;
+                TypedQuery<VirtualCollection> q = null;
+                if (query != null) {
+                    ParsedQuery parsedQuery = ParsedQuery.parseQuery(query);
+                    if (logger.isDebugEnabled()) {
+                        logger.debug(parsedQuery.getPrettyPrinted());
+                    }
+                    cq = parsedQuery.getCountQuery(em, user, null);
+                    q  = parsedQuery.getQuery(em, user, null);
+                } else {
+                    cq = em.createNamedQuery("VirtualCollection.countByOwner",
+                            Long.class);
+                    cq.setParameter("owner", user);
+                    q = em.createNamedQuery("VirtualCollection.findByOwner",
+                            VirtualCollection.class);
+                    q.setParameter("owner", user);
+                }
+
+                // commence query ...
+                totalCount = cq.getSingleResult();
+
+                // optimization; don't query, if we won't get any results
+                /*
+                 *  FIXME: offset == -1 is temporary hack for just fetching
+                 *  total count; re-factor to have fetch-count and fetch-data
+                 *  methods!
+                 */
+                if ((totalCount > 0) && (offset > -1)) {
+                    if (offset > 0) {
+                        q.setFirstResult(offset);
+                    }
+                    if (count > 0) {
+                        q.setMaxResults(count);
+                    }
+                    results = q.getResultList();
+                }
+            }
+            return new VirtualCollectionList(results, offset, (int) totalCount);
+        } catch (Exception e) {
+            logger.error("error while enumerating virtual collections", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while enumerating virtual collections", e);
+        } finally {
+            EntityTransaction tx = em.getTransaction();
+            if ((tx != null) && !tx.getRollbackOnly()) {
+                tx.commit();
+            }
+        }
+    }
+
+    public int getVirtualCollectionCount(QueryOptions options)
+            throws VirtualCollectionRegistryException {
+        EntityManager em = datastore.getEntityManager();
+        try {
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<Long> cq = cb.createQuery(Long.class);
+            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
+            if (options != null) {
+                Predicate where = options.getWhere(cb, cq, root);
+                if (where != null) {
+                    cq.where(where);
+                }
+            }
+            em.getTransaction().begin();
+            TypedQuery<Long> query =
+                em.createQuery(cq.select(cb.count(root)));
+            final long count = query.getSingleResult();
+            if (count >= Integer.MAX_VALUE) {
+                throw new VirtualCollectionRegistryException(
+                        "resultset too large");
+            }
+            return (int) count;
+        } catch (Exception e) {
+            logger.error("error while counting virtual collections", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while counting virtual collections", e);
+        } finally {
+            EntityTransaction tx = em.getTransaction();
+            if ((tx != null) && tx.isActive() && !tx.getRollbackOnly()) {
+                tx.commit();
+            }
+        }
+    }
+    
+    public List<VirtualCollection> getVirtualCollections(
+            int first, int count, QueryOptions options)
+            throws VirtualCollectionRegistryException {
+        EntityManager em = datastore.getEntityManager();
+        try {
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<VirtualCollection> cq =
+                cb.createQuery(VirtualCollection.class);
+            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
+            if (options != null) {
+                final Predicate where = options.getWhere(cb, cq, root);
+                if (where != null) {
+                    cq.where(where);
+                }
+                final Order[] order = options.getOrderBy(cb, root);
+                if (order != null) {
+                    cq.orderBy(order);
+                }
+            }
+            em.getTransaction().begin();
+            TypedQuery<VirtualCollection> query =
+                em.createQuery(cq.select(root));
+            if (first > -1) {
+                query.setFirstResult(first);
+            }
+            if (count > 0) {
+                query.setMaxResults(count);
+            }
+            return query.getResultList();
+        } catch (Exception e) {
+            logger.error("error while fetching virtual collections", e);
+            throw new VirtualCollectionRegistryException(
+                    "error while fetching virtual collections", e);
+        } finally {
+            EntityTransaction tx = em.getTransaction();
+            if ((tx != null) && tx.isActive() && !tx.getRollbackOnly()) {
+                tx.commit();
+            }
+        }
+    }
+
+    private void maintenance(long now) {
+        // allocate persistent identifier roughly after 30 seconds 
+        final Date nowDateAlloc = new Date(now - 30*1000);
+        // (for now) purge deleted collection roughly after 30 seconds
+        final Date nowDatePurge = new Date(now - 30*1000);
+        
+        EntityManager em = datastore.getEntityManager();
+        try {
+            /*
+             * delayed allocation of persistent identifier
+             */
+            em.getTransaction().begin();
+            TypedQuery<VirtualCollection> q =
+                em.createNamedQuery("VirtualCollection.findAllByState",
+                                    VirtualCollection.class);
+            q.setParameter("state", VirtualCollection.State.PUBLIC_PENDING);
+            q.setParameter("date", nowDateAlloc);
+            q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
+            for (VirtualCollection vc : q.getResultList()) {
+                if (vc.getPersistentIdentifier() == null) {
+                    PersistentIdentifier pid = pid_provider.createIdentifier(vc);
+                    vc.setPersistentIdentifier(pid);
+                }
+                vc.setState(VirtualCollection.State.PUBLIC);
+                em.persist(vc);
+                logger.debug("assigned pid (identifer='{}') to virtual" +
+                        "collection (id={})",
+                        vc.getPersistentIdentifier().getIdentifier(),
+                        vc.getId());
+            }
+            em.getTransaction().commit();
+            
+            /*
+             * delayed purging of deleted virtual collections 
+             */
+            em.getTransaction().begin();
+            q.setParameter("state", VirtualCollection.State.DELETED);
+            q.setParameter("date", nowDatePurge);
+            q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
+            for (VirtualCollection vc : q.getResultList()) {
+                vc.setState(VirtualCollection.State.DEAD);
+                em.remove(vc);
+                logger.debug("purged virtual collection (id={})", vc.getId());
+            }
+            em.getTransaction().commit();
+        } catch (Exception e) {
+            logger.error("error while doing maintenance", e);
+        } finally {
+            datastore.closeEntityManager();
+        }
+    }
+
+    private static User fetchUser(EntityManager em, Principal principal) {
+        User user = null;
+        try {
+            TypedQuery<User> q =
+                em.createNamedQuery("User.findByName", User.class);
+            q.setParameter("name", principal.getName());
+            user = q.getSingleResult();
+        } catch (NoResultException e) {
+            /* IGNORE */
+        }
+        return user;
+    }
+
+} // class VirtualCollectionRegistry
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryException.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryException.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryException.java	(revision 1025)
@@ -0,0 +1,14 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+public class VirtualCollectionRegistryException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public VirtualCollectionRegistryException(String msg) {
+        this(msg, null);
+    }
+
+    public VirtualCollectionRegistryException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+
+} // class VirtualCollectionRegistryException
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryPermissionException.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryPermissionException.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryPermissionException.java	(revision 1025)
@@ -0,0 +1,11 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+public class VirtualCollectionRegistryPermissionException extends
+        VirtualCollectionRegistryException {
+    private static final long serialVersionUID = 1L;
+
+    public VirtualCollectionRegistryPermissionException(String msg) {
+        super(msg);
+    }
+
+} // class VirtualCollectionRegistryPermissionException
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryUsageException.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryUsageException.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionRegistryUsageException.java	(revision 1025)
@@ -0,0 +1,16 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+public class VirtualCollectionRegistryUsageException extends
+        VirtualCollectionRegistryException {
+    private static final long serialVersionUID = 1L;
+
+    public VirtualCollectionRegistryUsageException(String msg) {
+        this(msg, null);
+    }
+
+    public VirtualCollectionRegistryUsageException(String msg,
+            Throwable cause) {
+        super(msg, cause);
+    }
+
+} // class VirtualCollectionUsageException
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionValidator.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionValidator.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionValidator.java	(revision 1025)
@@ -0,0 +1,105 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+import eu.clarin.cmdi.virtualcollectionregistry.model.GeneratedBy;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+public class VirtualCollectionValidator {
+    private Set<Creator> uniqueCreators = new HashSet<Creator>(16);
+    private Set<String> uniqueResourceRefs = new HashSet<String>(512);
+
+    public void validate(VirtualCollection vc)
+            throws VirtualCollectionRegistryException {
+        if (vc == null) {
+            throw new NullPointerException("vc == null");
+        }
+
+        // reset internal state
+        reset();
+
+        // proceed to validate ...
+        if ((vc.getName() == null) || vc.getName().trim().isEmpty()) {
+            throw new VirtualCollectionRegistryUsageException(
+                    "collection has an empty name");
+        }
+        
+        for (Creator creator : vc.getCreators()) {
+            if (uniqueCreators.contains(creator)) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "collection contains non unique creators");
+            }
+            uniqueCreators.add(creator);
+        }
+
+        switch (vc.getType()) {
+        case EXTENSIONAL:
+            if (vc.getResources().isEmpty()) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "extensional collection must contain on or " +
+                        "more resources");
+            }
+            if (vc.getGeneratedBy() != null) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "extensional collection must not contain GeneratedBy");
+            }
+            break;
+        case INTENSIONAL:
+            final GeneratedBy generatedBy = vc.getGeneratedBy();
+            if (generatedBy == null) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "intensional collections needs GeneratedBy");
+            }
+            if (generatedBy.getDescription() == null) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "GeneratedBy has empty description");
+            }
+            final GeneratedBy.Query query = generatedBy.getQuery();
+            if (query != null) {
+                if (query.getProfile() == null) {
+                    throw new VirtualCollectionRegistryUsageException(
+                            "profile of GeneratedBy.Query is empty");
+                }
+                if (query.getValue() == null) {
+                    throw new VirtualCollectionRegistryUsageException(
+                            "query of GeneratedBy.Query is empty");
+                }
+            }
+        }
+
+        if ((vc.getReproducibilityNotice() != null) && 
+                (vc.getReproducibility() == null)) {
+            throw new VirtualCollectionRegistryUsageException(
+                    "reproducibility notice without reproducubility");
+        }
+
+        for (String keyword : vc.getKeywords()) {
+            if ((keyword == null) || (keyword.trim().isEmpty())) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "keyword is empty");
+            }
+        }
+
+        for (Resource resource : vc.getResources()) {
+            String ref = resource.getRef();
+            if (ref == null) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "collection contains resource with empty ResourceRef");
+            }
+            if (uniqueResourceRefs.contains(ref)) {
+                throw new VirtualCollectionRegistryUsageException(
+                        "collection contains non-unique ResourceRefs");
+            }
+            uniqueResourceRefs.add(ref);
+        }
+    }
+
+    private void reset() {
+        uniqueCreators.clear();
+        uniqueResourceRefs.clear();
+    }
+
+} // class VirtualCollectionValidator
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualColletionRegistryOAIRepository.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualColletionRegistryOAIRepository.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualColletionRegistryOAIRepository.java	(revision 1025)
@@ -0,0 +1,411 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.LockModeType;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.clarin.cmdi.oai.provider.DublinCoreAdapter;
+import eu.clarin.cmdi.oai.provider.DublinCoreConverter;
+import eu.clarin.cmdi.oai.provider.MetadataFormat;
+import eu.clarin.cmdi.oai.provider.OAIException;
+import eu.clarin.cmdi.oai.provider.Record;
+import eu.clarin.cmdi.oai.provider.RecordList;
+import eu.clarin.cmdi.oai.provider.Repository;
+import eu.clarin.cmdi.oai.provider.SetSpecDesc;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection_;
+
+class VirtualColletionRegistryOAIRepository implements Repository {
+    private static final Logger logger =
+        LoggerFactory.getLogger(VirtualColletionRegistryOAIRepository.class);
+
+    private final static class CMDIMetadataFormat implements MetadataFormat {
+        @Override
+        public String getPrefix() {
+            return "cmdi";
+        }
+
+        @Override
+        public String getNamespaceURI() {
+            return "http://www.clarin.eu/cmd";
+        }
+
+        @Override
+        public String getSchemaLocation() {
+            // FIXME: for now minimal-cmdi.xsd
+            return "http://www.clarin.eu/cmd/xsd/minimal-cmdi.xsd";
+        }
+
+        @Override
+        public boolean canWriteClass(Class<?> clazz) {
+            return true;
+        }
+
+        @Override
+        public void writeObject(XMLStreamWriter stream, Object item)
+                throws XMLStreamException {
+            final VirtualCollectionRegistry registry =
+                VirtualCollectionRegistry.instance();
+            final VirtualCollection vc = (VirtualCollection) item;
+            registry.getMarshaller().writeCMDI(stream, vc);
+        }
+    } // class CMDIMetadataFormat
+
+    private final VirtualCollectionRegistry registry;
+
+    VirtualColletionRegistryOAIRepository(VirtualCollectionRegistry registry) {
+        this.registry = registry;
+    }
+
+    @Override
+    public String getId() {
+        return "virtualcollectionregistry.cmdi.clarin.eu";
+    }
+
+    @Override
+    public String getName() {
+        return "CLARIN Virtual Collection Registry";
+    }
+
+    @Override
+    public String getDescription() {
+        return "The virtual collection registry is a component of the " +
+            "CLARIN metadata initiative.";
+    }
+
+    @Override
+    public Date getEarliestTimestamp() {
+        Date result = null;
+        try {
+            EntityManager em = registry.getDataStore().getEntityManager();
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+
+            CriteriaQuery<Date> cq = cb.createQuery(Date.class);
+            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
+            cq.select(root.get(VirtualCollection_.dateModified));
+            cq.orderBy(cb.asc(root.get(VirtualCollection_.dateModified)));
+
+            em.getTransaction().begin();
+            TypedQuery<Date> q = em.createQuery(cq);
+            q.setMaxResults(1);
+            q.setLockMode(LockModeType.READ);
+            result = q.getSingleResult();
+            em.getTransaction().commit();
+        } catch (NoResultException e) {
+            /* IGNORE */
+        } catch (Exception e) {
+            logger.debug("error fetching earliest timestamp", e);
+        } finally {
+            registry.getDataStore().closeEntityManager();
+        }
+        return result;
+    }
+
+    @Override
+    public DeletedNotion getDeletedNotion() {
+        return DeletedNotion.TRANSIENT;
+    }
+
+    @Override
+    public Granularity getGranularity() {
+        return Granularity.SECONDS;
+    }
+
+    @Override
+    public int getCompressionMethods() {
+        return 0 /* COMPRESSION_GZIP|COMPRESSION_DEFLATE */;
+    }
+
+    @Override
+    public Set<String> getAdminAddreses() {
+        Set<String> addresses = new HashSet<String>();
+        addresses.add("cmdi@clarin.eu");
+        return addresses;
+    }
+
+    @Override
+    public Object getSampleRecordLocalId() {
+        return new Long(23);
+    }
+
+    @Override
+    public Set<DublinCoreConverter> getDublinCoreConverters() {
+        Set<DublinCoreConverter> converters =
+            new HashSet<DublinCoreConverter>();
+        converters.add(new DublinCoreAdapter() {
+            @Override
+            public boolean canProcessResource(Class<?> clazz) {
+                return clazz.isAssignableFrom(VirtualCollection.class);
+            }
+
+            @Override
+            public String getTitle(Object resource) {
+                return ((VirtualCollection) resource).getName();
+            }
+
+            @Override
+            public String getIdentifier(Object resource) {
+                return ((VirtualCollection) resource)
+                    .getPersistentIdentifier().getActionableURI();
+            }
+
+            @Override
+            public Date getDate(Object resource) {
+                return ((VirtualCollection) resource).getCreationDate();
+            }
+
+            @Override
+            public List<String> getCreators(Object resource) {
+                final VirtualCollection vc = (VirtualCollection) resource;
+                if (!vc.getCreators().isEmpty()) {
+                    List<String> creators = new ArrayList<String>();
+                    for (Creator creator : vc.getCreators()) {
+                        creators.add(creator.getPerson());
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            public String getDescription(Object resource) {
+                return ((VirtualCollection) resource).getDescription();
+            }
+        });
+        return converters;
+    }
+
+    @Override
+    public Set<MetadataFormat> getCustomMetadataFormats() {
+        Set<MetadataFormat> formats = new HashSet<MetadataFormat>();
+        formats.add(new CMDIMetadataFormat());
+        return formats;
+    }
+
+    @Override
+    public Set<SetSpecDesc> getSetDescs() {
+        return null;
+    }
+
+    @Override
+    public Object parseLocalId(String unparsedLocalId) {
+        try {
+            long id = Long.parseLong(unparsedLocalId);
+            if (id > 0) {
+                return new Long(id);
+            }
+        } catch (NumberFormatException e) {
+            /* FALL-THROUGH */
+        }
+        return null;
+    }
+
+    @Override
+    public String unparseLocalId(Object localId) {
+        return ((Long) localId).toString();
+    }
+
+    @Override
+    public Record getRecord(Object localId, boolean headerOnly)
+            throws OAIException {
+        try {
+            long id = (Long) localId;
+            VirtualCollection vc = registry.retrieveVirtualCollection(id);
+            return createRecord(vc, headerOnly);
+        } catch (VirtualCollectionNotFoundException e) {
+            return null;
+        } catch (VirtualCollectionRegistryException e) {
+            throw new OAIException("error", e);
+        }
+    }
+
+    @Override
+    public RecordList getRecords(String prefix, Date from, Date until,
+            String set, int offset, boolean headerOnly) throws OAIException {
+        try {
+            EntityManager em = registry.getDataStore().getEntityManager();
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+
+            CriteriaQuery<Long> cq1 = cb.createQuery(Long.class);
+            Root<VirtualCollection> root1 = cq1.from(VirtualCollection.class);
+            cq1.select(cb.count(root1));
+            cq1.where(buildWhere(cb, cq1, root1, from, until));
+
+            CriteriaQuery<VirtualCollection> cq2 =
+                cb.createQuery(VirtualCollection.class);
+            Root<VirtualCollection> root2 = cq2.from(VirtualCollection.class);
+            cq2.select(root2);
+            cq2.where(buildWhere(cb, cq2, root2, from, until));
+            cq2.orderBy(cb.asc(root2.get(VirtualCollection_.dateModified)));
+            
+            em.getTransaction().begin();
+            TypedQuery<Long> q1 = em.createQuery(cq1);
+            TypedQuery<VirtualCollection> q2 = em.createQuery(cq2);
+            List<VirtualCollection> vcs = null;
+            long count = q1.getSingleResult();
+            if (count > 0) {
+                if (offset > 0) {
+                    q2.setFirstResult(offset);
+                }
+                q2.setMaxResults(100);
+                vcs = q2.getResultList();
+            }
+            em.getTransaction().commit();
+
+            if ((vcs != null) && !vcs.isEmpty()) {
+                List<Record> records = new ArrayList<Record>(vcs.size());
+                for (VirtualCollection vc : vcs) {
+                    records.add(createRecord(vc, headerOnly));
+                    /*
+                     * XXX: force fetching of creators.
+                     */
+                    vc.getCreators().size();
+
+                    /*
+                     *  XXX: force fetching of resources in case of "cmdi"
+                     *  prefix.
+                     */
+                    if ("cmdi".equals(prefix) && !headerOnly) {
+                        vc.getResources().size();
+                    }
+                }
+                boolean hasMore = (offset + vcs.size()) < count;
+                return new RecordList(records, offset, hasMore, (int) count);
+            } else {
+                return null;
+            }
+        } catch (Exception e) {
+            throw new OAIException("error fetching records", e);
+        } finally {
+            registry.getDataStore().closeEntityManager();
+        }
+    }
+
+    private Predicate buildWhere(CriteriaBuilder cb, CriteriaQuery<?> cq,
+            Root<VirtualCollection> root, Date from, Date until) {
+        Predicate where =
+                cb.equal(root.get(VirtualCollection_.state),
+                                  VirtualCollection.State.PUBLIC);
+        if ((from != null) && (until != null)) {
+            where = cb.and(where, cb.between(root
+                    .get(VirtualCollection_.dateModified), from, until));
+        } else if (from != null) {
+            where = cb.and(where, cb.greaterThanOrEqualTo(root
+                    .get(VirtualCollection_.dateModified), from));
+        } else if (until != null) {
+            where = cb.and(where, cb.lessThanOrEqualTo(root
+                    .get(VirtualCollection_.dateModified), until));
+        }
+        return where;
+    }
+
+    private final class RecordFullImpl implements Record {
+        private final VirtualCollection vc;
+
+        RecordFullImpl(VirtualCollection vc) {
+            this.vc = vc;
+        }
+
+        @Override
+        public Object getLocalId() {
+            return vc.getId();
+        }
+
+        @Override
+        public Date getDatestamp() {
+            return vc.getDateModified();
+        }
+
+        @Override
+        public boolean isDeleted() {
+            return vc.getState() == VirtualCollection.State.DELETED;
+        }
+
+        @Override
+        public List<String> getSetSpecs() {
+            return null;
+        }
+
+        @Override
+        public Object getItem() {
+            return vc;
+        }
+
+        @Override
+        public Class<?> getItemClass() {
+            return VirtualCollection.class;
+        }
+    }
+
+    private final class RecordHeaderImpl implements Record {
+        private final Long id;
+        private final Date datestamp;
+        private final boolean deleted;
+
+        RecordHeaderImpl(VirtualCollection vc) {
+            this.id = vc.getId();
+            this.datestamp = vc.getDateModified();
+            this.deleted = (vc.getState() == VirtualCollection.State.DELETED);
+        }
+
+        @Override
+        public Object getLocalId() {
+            return id;
+        }
+
+        @Override
+        public Date getDatestamp() {
+            return datestamp;
+        }
+
+        @Override
+        public boolean isDeleted() {
+            return deleted;
+        }
+
+        @Override
+        public List<String> getSetSpecs() {
+            return null;
+        }
+
+        @Override
+        public Object getItem() {
+            return null;
+        }
+
+        @Override
+        public Class<?> getItemClass() {
+            return VirtualCollection.class;
+        }
+    }
+
+    private Record createRecord(VirtualCollection vc, boolean headerOnly) {
+        if (vc != null) {
+            if (headerOnly) {
+                logger.debug("creating header-only wrapper for vc {}",
+                        vc.getId());
+                return new RecordHeaderImpl(vc);
+            } else {
+                logger.debug("creating full wrapper for vc {}", vc.getId());
+                return new RecordFullImpl(vc);
+            }
+        }
+        return null;
+    }
+} // VirtualColletionRegistryOAIRepository
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/Application.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/Application.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/Application.java	(revision 1025)
@@ -0,0 +1,109 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.authentication.AuthenticatedWebApplication;
+import org.apache.wicket.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authorization.strategies.role.Roles;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.session.pagemap.LeastRecentlyAccessedEvictionStrategy;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.pages.AdminPage;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.pages.BrowsePrivateCollectionsPage;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.pages.CreateVirtualCollectionPage;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.pages.BrowsePublicCollectionsPage;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.pages.LoginPage;
+
+public class Application extends AuthenticatedWebApplication {
+    private static final String CONFIG_PARAM_ADMINDB = "admindb";
+    private Set<String> adminUsers =
+        new HashSet<String>();
+
+    @Override
+    protected void init() {
+        super.init();
+
+        String s = getServletContext().getInitParameter(CONFIG_PARAM_ADMINDB);
+        if (s != null) {
+            try {
+                loadAdminDatabase(s);
+            } catch (IOException e ) {
+                // FIXME: handle error
+            }
+        }
+        if (adminUsers.isEmpty()) {
+            // FIXME: better logging
+            System.err.println("WARNING: no admin users have been defined");
+        }
+        getMarkupSettings().setDefaultMarkupEncoding("utf-8");
+        getRequestCycleSettings().setResponseRequestEncoding("utf-8");
+        getSessionSettings().setMaxPageMaps(3);
+        getSessionSettings().setPageMapEvictionStrategy(
+                new LeastRecentlyAccessedEvictionStrategy(3));
+        if (getConfigurationType() != DEPLOYMENT) {
+            getMarkupSettings().setStripWicketTags(true);
+            getMarkupSettings().setStripComments(true);
+        }
+        mountBookmarkablePage("/public",
+                BrowsePublicCollectionsPage.class);
+        mountBookmarkablePage("/private",
+                BrowsePrivateCollectionsPage.class);
+        mountBookmarkablePage("/create", CreateVirtualCollectionPage.class);
+        mountBookmarkablePage("/admin", AdminPage.class);
+    }
+
+    @Override
+    public Class<? extends Page> getHomePage() {
+        return BrowsePublicCollectionsPage.class;
+    }
+
+    @Override
+    protected Class<? extends WebPage> getSignInPageClass() {
+        return LoginPage.class;
+    }
+
+    @Override
+    protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
+        return ApplicationSession.class;
+    }
+
+    public boolean hasAnyRole(String[] roles) {
+        if (roles != null) {
+            final Roles sessionRoles = AuthenticatedWebSession.get().getRoles();
+            if (sessionRoles != null) {
+                for (String role : roles) {
+                    if (sessionRoles.hasRole(role)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean isAdmin(String user) {
+        return adminUsers.contains(user);
+    }
+
+    private void loadAdminDatabase(String filename) throws IOException {
+        adminUsers.clear();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(
+                new FileInputStream(filename)));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            line = line.trim();
+            if (line.isEmpty() || line.startsWith("#")) {
+                continue;
+            }
+            adminUsers.add(line);
+        } // while
+        reader.close();
+    }
+
+} // class Application
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/Application.properties
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/Application.properties	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/Application.properties	(revision 1025)
@@ -0,0 +1,21 @@
+State.PUBLIC: public
+State.PUBLIC_PENDING: publishing
+State.PRIVATE: private
+State.DELETED: deleted
+State.DEAD: dead
+Type.EXTENSIONAL: extensional
+Type.INTENSIONAL: intensional
+Purpose.RESEARCH: research
+Purpose.REFERENCE: reference
+Purpose.SAMPLE: sample
+Purpose.FUTURE_USE: future-use
+Reproducibility.INTENDED: intended
+Reproducibility.FLUCTUATING: fluctuating
+Reproducibility.UNTENDED: untended
+Type.METADATA: Metadata
+Type.RESOURCE: Resource
+collections.publishconfirm: Do you really want to publish "${name}"?\nThe \
+  virtual collection will then be publicly available and you will not be able \
+  to change nor to delete it anymore.
+collections.deleteconfirm: Do you really want to delete "${name}"?\nThis \
+  operation is permanent and cannot be undone.
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/ApplicationSession.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/ApplicationSession.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/ApplicationSession.java	(revision 1025)
@@ -0,0 +1,106 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui;
+
+import java.security.Principal;
+import java.util.regex.Pattern;
+
+import org.apache.wicket.Request;
+import org.apache.wicket.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authorization.strategies.role.Roles;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.auth.AuthPrincipal;
+
+
+@SuppressWarnings("serial")
+public class ApplicationSession extends AuthenticatedWebSession {
+    private static final String[] ATTRIBUTE_NAMES_NAME =
+        { "cn", "commonName", "displayName" };
+    private static final Pattern PERSITENT_ID_REGEX =
+        Pattern.compile("^[^!]+![^!]+![^!]+$");
+    private static final Roles ROLES_USER =
+        new Roles(Roles.USER);
+    private static final Roles ROLES_ADMIN =
+        new Roles(new String[] { Roles.USER, Roles.ADMIN}); 
+    private String user;
+    private boolean isAdmin;
+    private String userDisplay;
+    
+    public ApplicationSession(Request request) {
+        super(request);
+    }
+
+    public boolean signIn(Principal principal) {
+        boolean result = false;
+        if (principal != null) {
+            result = signIn(principal.getName(), null);
+            if (result) {
+                user = principal.getName();
+                isAdmin = ((Application) getApplication()).isAdmin(user);
+                userDisplay = findDisplayName(principal);
+            }
+        }
+        return result; 
+    }
+
+    @Override
+    public boolean authenticate(String username, String password) {
+        if (username != null) {
+            replaceSession();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Roles getRoles() {
+        if (isSignedIn()) {
+            return isAdmin ? ROLES_ADMIN : ROLES_USER;
+        }
+        return null;
+    }
+
+    public Principal getPrincipal() {
+        return new Principal() {
+            @Override
+            public String getName() {
+                return user;
+            }
+        };
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public String getUserDisplay() {
+        if (userDisplay != null) {
+            return userDisplay;
+        }
+        if (PERSITENT_ID_REGEX.matcher(user).matches()) {
+            return "Authenticated via Shibboleth";
+        }
+        return user;
+    }
+
+    public static ApplicationSession get() {
+        return (ApplicationSession) AuthenticatedWebSession.get();
+    }
+
+    private static String findDisplayName(Principal p) {
+        if (p instanceof AuthPrincipal) {
+            final AuthPrincipal principal = (AuthPrincipal) p;
+            for (String attr : ATTRIBUTE_NAMES_NAME ) {
+                String name = principal.getAttibute(attr);
+                if (name != null) {
+                    return name;
+                }
+            }
+            String givenName = principal.getAttibute("givenName");
+            String surname = principal.getAttibute("surname");
+            if ((givenName != null) && (surname != null)) {
+                return givenName + " " + surname;
+            }
+        }
+        return null;
+    }
+
+} // class ApplicationSession
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/CharacterEncodingFilter.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/CharacterEncodingFilter.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/CharacterEncodingFilter.java	(revision 1025)
@@ -0,0 +1,32 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class CharacterEncodingFilter implements Filter {
+    private static final String DEFAULT_ENCODING = "UTF-8";
+
+    @Override
+    public void init(FilterConfig config) throws ServletException {
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        if (request.getCharacterEncoding() == null) {
+            request.setCharacterEncoding(DEFAULT_ENCODING);
+        }
+        chain.doFilter(request, response);
+    }
+
+} // class CharacterEncodingFilter
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/DetachableVirtualCollectionModel.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/DetachableVirtualCollectionModel.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/DetachableVirtualCollectionModel.java	(revision 1025)
@@ -0,0 +1,63 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui;
+
+import org.apache.wicket.model.IDetachable;
+import org.apache.wicket.model.IModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+public class DetachableVirtualCollectionModel implements
+    IModel<VirtualCollection>, IDetachable {
+    private long id;
+    private transient VirtualCollection vc;
+
+    public DetachableVirtualCollectionModel(VirtualCollection vc) {
+        if (vc == null) {
+            throw new IllegalArgumentException("vc == null");
+        }
+        this.id = vc.getId();
+        this.vc = vc;
+    }
+
+    public DetachableVirtualCollectionModel(long id) {
+        this.id = vc.getId();
+    }
+
+    @Override
+    public VirtualCollection getObject() {
+        if (!isAttached()) {
+            this.vc = load();
+        }
+        return this.vc;
+    }
+
+    @Override
+    public void setObject(VirtualCollection vc) {
+        throw new UnsupportedOperationException("Model " + getClass() +
+                " does not support setObject(Object)");
+    }
+
+    @Override
+    public void detach() {
+        if (isAttached()) {
+            this.vc = null;
+        }
+    }
+
+    public boolean isAttached() {
+        return vc != null;
+    }
+
+    protected VirtualCollection load() {
+        try {
+            VirtualCollectionRegistry vcr =
+                VirtualCollectionRegistry.instance();
+            return vcr.retrieveVirtualCollection(this.id);
+        } catch (VirtualCollectionRegistryException e) {
+            return null;
+        }
+    }
+
+} // class DetachableVirtualCollectionModel
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthFilter.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthFilter.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthFilter.java	(revision 1025)
@@ -0,0 +1,217 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.auth;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+public final class AuthFilter implements Filter {
+    private final class RequestWrapper extends HttpServletRequestWrapper {
+        private final AuthStrategy.Result result;
+
+        public RequestWrapper(HttpServletRequest request,
+                AuthStrategy.Result result) {
+            super(request);
+            this.result = result;
+        }
+
+        @Override
+        public String getAuthType() {
+            return result.isAuthenticated() ? strategy.getAuthType() : null;
+        }
+
+        @Override
+        public Principal getUserPrincipal() {
+            return result.isAuthenticated() ? result.getPrincipal() : null;
+        }
+
+        @Override
+        public String getRemoteUser() {
+            return result.isAuthenticated()
+                 ? result.getPrincipal().getName()
+                 : null;
+        }
+    } // class RequestWrapper
+
+    private final class ResponseWrapper extends HttpServletResponseWrapper {
+        private boolean authRequested = false;
+        
+        public ResponseWrapper(HttpServletResponse response) {
+            super(response);
+            
+        }
+
+        @Override
+        public void setStatus(int sc, String sm) {
+            if (sc == HttpServletResponse.SC_UNAUTHORIZED) {
+                authRequested = true;
+            }
+            super.setStatus(sc, sm);
+        }
+
+        @Override
+        public void setStatus(int sc) {
+            if (sc == HttpServletResponse.SC_UNAUTHORIZED) {
+                authRequested = true;
+            }
+            super.setStatus(sc);
+        }
+        
+        public boolean isAuthRequested() {
+            return authRequested;
+        }
+    } // class ResponseWrapper
+
+    private static final String CONFIG_PARAM_AUTH_STRATEGY =
+        "authfilter.strategy";
+    private static final String STRATEGY_BASIC = "basic";
+    private static final String STRATEGY_SHIBBOLETH = "shibboleth";
+    private static final String AUTH_ACTION_PARAM = "authAction";
+    private static final String AUTH_ACTION_LOGIN = "LOGIN";
+    private static final String SESSION_PARAM_FORCED_AUTH = "authfilter.force";
+    private AuthStrategy strategy;
+
+
+    @Override
+    public void init(FilterConfig config) throws ServletException {
+        Map<String, String> cfg = new HashMap<String, String>();
+        Enumeration<?> i = config.getServletContext().getInitParameterNames();
+        while (i.hasMoreElements()) {
+            String name = (String) i.nextElement();
+            String value = config.getServletContext().getInitParameter(name);
+            if ((value != null) && !value.isEmpty()) {
+                cfg.put(name, value);
+            }
+        }
+        String s = cfg.get(CONFIG_PARAM_AUTH_STRATEGY);
+        if (s != null) {
+            if (STRATEGY_BASIC.equalsIgnoreCase(s)) {
+                strategy = new BasicAuthStrategy();
+            } else if (STRATEGY_SHIBBOLETH.equalsIgnoreCase(s)) {
+                strategy = new ShibbolethAuthStrategy();
+            } else {
+                throw new UnavailableException("invalid value for init " +
+                        "parameter '" + CONFIG_PARAM_AUTH_STRATEGY +
+                        "' (" + s + ")");
+            }
+            try {
+                strategy.init(config, cfg);
+            } catch (ServletException e) {
+                throw new UnavailableException("error initalizing auth " +
+                        "filter: " + e.getMessage());
+            }
+        } else {
+            throw new UnavailableException("missing init parameter '" +
+            		CONFIG_PARAM_AUTH_STRATEGY + "'");
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request0, ServletResponse response0,
+            FilterChain chain) throws IOException, ServletException {
+        if (strategy == null) {
+            throw new UnavailableException("no auth strategy configured");
+        }
+        final HttpServletRequest request = (HttpServletRequest) request0;
+        final HttpServletResponse response = (HttpServletResponse) response0;
+        final HttpSession session = request.getSession();
+
+        /*
+         * Forced-Auth is a two step process: check if authAction query
+         * parameter is set and if so, set an internal cookie and redirect
+         * to the same URL without the authAction query parameter. On the
+         * second request, ask the AuthStrategy to request authentication.
+         * This is needed, to avoid an request auth loop, because the query
+         * parameter was never deleted.
+         */
+        String action = request.getParameter(AUTH_ACTION_PARAM);
+        if (action != null) {
+            if (AUTH_ACTION_LOGIN.equalsIgnoreCase(action)) {
+                session.setAttribute(SESSION_PARAM_FORCED_AUTH, Boolean.TRUE);
+                StringBuffer url = request.getRequestURL();
+                boolean firstParam = true;
+                Iterator<?> params =
+                    request.getParameterMap().entrySet().iterator();
+                while (params.hasNext()) {
+                    @SuppressWarnings("unchecked")
+                    Map.Entry<String, String[]> entry =
+                        (Map.Entry<String, String[]>) params.next();
+                    if (AUTH_ACTION_PARAM.equals(entry.getKey())) {
+                        continue;
+                    }
+                    for (String value : entry.getValue()) {
+                        if (firstParam) {
+                            url.append('?');
+                            firstParam = false;
+                        } else {
+                            url.append('&');
+                        }
+                        url.append(entry.getKey()).append('=').append(value);
+                    }
+                }
+                response.sendRedirect(url.toString());
+                return;
+            }
+        }
+        if (session.getAttribute(SESSION_PARAM_FORCED_AUTH) == Boolean.TRUE) {
+            session.removeAttribute(SESSION_PARAM_FORCED_AUTH);
+            strategy.requestAuth(request, response);
+            return;
+        }
+
+        AuthStrategy.Result result =
+            strategy.handleAuth(request, response);
+        if (result == null) {
+            throw new UnavailableException(
+                    "auth strategy returned null result");
+        }
+        switch (result.getAction()) {
+        case CONTINUE_AUTHENTICATED:
+            /* FALL-TROUGH */
+        case CONTINUE_UNAUTHENTICATED:
+            final RequestWrapper request2 =
+                new RequestWrapper(request, result);
+            final ResponseWrapper response2 =
+                new ResponseWrapper(response);
+            chain.doFilter(request2, response2);
+            /*
+             * lazy auth: if request returned a status of 401 (unauthorized),
+             * request strategy to perform authorization
+             */
+            if (response2.isAuthRequested()) {
+                strategy.requestAuth(request2, response2);
+            }
+            break;
+        case RETRY:
+            strategy.requestAuth(request, response);
+            break;
+        case ABORT:
+            response.sendError(HttpServletResponse.SC_FORBIDDEN,
+                    "Authorization failed");
+            break;
+        case ERROR:
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                    "Authorization error due to bad request");
+        } // switch
+    }
+
+} // class AuthFilter
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthPrincipal.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthPrincipal.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthPrincipal.java	(revision 1025)
@@ -0,0 +1,44 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.auth;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Iterator;
+import java.util.Map;
+
+@SuppressWarnings("serial")
+public final class AuthPrincipal implements Principal, Serializable {
+    private final String name;
+    private final Map<String, String> attributes;
+    
+    AuthPrincipal(String name, Map<String, String> attributes) {
+        if ((name == null) || name.isEmpty()) {
+            throw new IllegalArgumentException("name is invalid");
+        }
+        this.name = name;
+        if ((attributes != null) && !attributes.isEmpty()) {
+            this.attributes = attributes;
+        } else {
+            this.attributes = null;
+        }
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public Iterator<String> getAttributeNames() {
+        if (attributes != null) {
+            return attributes.keySet().iterator();
+        }
+        return null;
+    }
+    
+    public String getAttibute(String name) {
+        if ((attributes != null) && (name != null)) {
+            return attributes.get(name.toLowerCase());
+        }
+        return null;
+    }
+
+} // class AuthPrincipal
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthStrategy.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthStrategy.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/AuthStrategy.java	(revision 1025)
@@ -0,0 +1,60 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.auth;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public interface AuthStrategy {
+    public enum Action {
+        CONTINUE_AUTHENTICATED, CONTINUE_UNAUTHENTICATED, RETRY, ABORT, ERROR;
+    } // enum State
+
+    public class Result {
+        private Action action;
+        private AuthPrincipal principal;
+        
+        protected Result() {
+            this.action = Action.CONTINUE_UNAUTHENTICATED;
+        }
+        
+        public final Action getAction() {
+            return action;
+        }
+        
+        public final void setAction(Action action) {
+            if (action == null) {
+                throw new IllegalArgumentException("action == null");
+            }
+            this.action = action;
+        }
+
+        public final boolean isAuthenticated() {
+            return (this.action == Action.CONTINUE_AUTHENTICATED);
+        }
+
+        public AuthPrincipal getPrincipal() {
+            return principal;
+        }
+        
+        public void setPrinicpal(AuthPrincipal principal) {
+            this.principal = principal;
+        }
+    } // class Result
+
+    public void init(FilterConfig filterconfig, Map<String, String> config)
+            throws ServletException;
+
+    public String getAuthType();
+
+    public void requestAuth(HttpServletRequest request,
+            HttpServletResponse response) throws IOException, ServletException;
+
+    public AuthStrategy.Result handleAuth(HttpServletRequest request,
+            HttpServletResponse response) throws IOException, ServletException;
+
+} // interface AuthStrategy
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/BasicAuthStrategy.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/BasicAuthStrategy.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/BasicAuthStrategy.java	(revision 1025)
@@ -0,0 +1,288 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.auth;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+
+
+final class BasicAuthStrategy implements AuthStrategy {
+    private enum HashMethod {
+        PLAIN, MD5, SHA;
+
+        public static HashMethod fromString(String s) {
+            if ("PLAIN".equalsIgnoreCase(s)) {
+                return HashMethod.PLAIN;
+            } else if ("MD5".equalsIgnoreCase(s)) {
+                return HashMethod.MD5;
+            } else if ("SHA".equalsIgnoreCase(s)) {
+                return HashMethod.SHA;
+            } else {
+                return null;
+            }
+        }
+    } // enum Hash
+
+    private class Entry {
+        private final HashMethod type;
+        private final String username;
+        private final String password;
+        private final Map<String, String> attributes;
+        
+        public Entry(HashMethod type, String username, String password, Map<String, String> attributes) {
+            this.type = type;
+            this.username = username;
+            this.password = password;
+            this.attributes = attributes;
+        }
+        
+        public boolean checkPassword(String pw) {
+            switch (type) {
+                case PLAIN:
+                    return this.password.equals(pw);
+                case MD5:
+                    final String hash1 =
+                        DigestUtils.md5Hex(prepare(pw)).toLowerCase();
+                    return this.password.equals(hash1);
+                case SHA:
+                    final String hash2 =
+                        DigestUtils.shaHex(prepare(pw)).toLowerCase();
+                    return this.password.equals(hash2);
+            } // switch
+            return false;
+        }
+        
+        public Map<String, String> getAttributes() {
+            return attributes;
+        }
+
+        private String prepare(String pw) {
+            // FIXME: salt should include something random
+            return new StringBuilder(this.username)
+                .append(':')
+                .append(pw)
+                .toString();
+        }
+    } // class Entry
+    
+    private static final String CONFIG_PARAM_USERDB_FILE =
+        "authfilter.basic.userdb";
+    private final ReadWriteLock userDbLock =
+        new ReentrantReadWriteLock(true);
+    private final Map<String, Entry> userDb =
+        new HashMap<String, Entry>();
+
+    public void init(FilterConfig filterConfig, Map<String, String> config)
+            throws ServletException {
+        String userdb = config.get(CONFIG_PARAM_USERDB_FILE);
+        if ((userdb == null) || userdb.isEmpty()) {
+            throw new ServletException("missing init parameter '" +
+                    CONFIG_PARAM_USERDB_FILE + "'");
+        }
+        try {
+            InputStream in = new FileInputStream(userdb);
+            loadUserDatabase(in);
+        } catch (IOException e) {
+            throw new ServletException("error initializing user database", e);
+        }
+    }
+    
+    @Override
+    public String getAuthType() {
+        return HttpServletRequest.BASIC_AUTH; 
+    }
+
+    @Override
+    public void requestAuth(HttpServletRequest request,
+            HttpServletResponse response) throws IOException, ServletException {
+      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+      final String realm =
+          "Basic realm=\"CLARIN Virtual Collection Registry\"";
+      response.addHeader("WWW-Authenticate", realm);
+    }
+
+    @Override
+    public Result handleAuth(HttpServletRequest request,
+            HttpServletResponse response) throws IOException, ServletException {
+        String authorization = request.getHeader("Authorization");
+        if (authorization != null) {
+            authorization = authorization.trim();
+            if (authorization.isEmpty()) {
+                authorization = null;
+            }
+        }
+        Result result = new Result();
+        if (authorization != null) {
+            int pos = authorization.indexOf(' ');
+            if (pos != -1) {
+                final String scheme = authorization.substring(0, pos).trim();
+                if (scheme.equalsIgnoreCase("Basic")) {
+                    final String credentials =
+                        decodeBase64(authorization.substring(pos + 1).trim());
+                    pos = credentials.indexOf(':');
+                    if ((pos != -1) && (pos < credentials.length() - 2)) {
+                        final String username = credentials.substring(0, pos);
+                        final String password = credentials.substring(pos + 1);
+                        if (checkCredential(username, password)) {
+                            Map<String, String> attr = getAttributes(username);
+                            // login successful
+                            result.setPrinicpal(
+                                    new AuthPrincipal(username, attr));
+                            result.setAction(Action.CONTINUE_AUTHENTICATED);
+                        } else {
+                            // login failed, retry
+                            result.setAction(Action.RETRY);
+                        }
+                    } else {
+                        result.setAction(Action.ERROR);
+                    }
+                } else {
+                    // unsupported auth method, retry
+                    result.setAction(Action.RETRY);
+                }
+            } else {
+                result.setAction(Action.ERROR);
+            }
+        }
+        return result;
+    }
+
+    private String decodeBase64(String s) throws ServletException {
+        try {
+            byte[] bytes = Base64.decodeBase64(s);
+            return new String(bytes, "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new ServletException("unsupported encoding");
+        }
+    }
+
+    private boolean checkCredential(String username, String password) {
+        Entry entry = null;
+        Lock lock = userDbLock.readLock();
+        lock.lock();
+        try {
+            entry = userDb.get(username);
+        } finally {
+            lock.unlock();
+        }
+        if (entry != null) {
+            return entry.checkPassword(password);
+        }
+        return false;
+    }
+
+    private Map<String, String> getAttributes(String username) {
+        Entry entry = null;
+        Lock lock = userDbLock.readLock();
+        lock.lock();
+        try {
+            entry = userDb.get(username);
+        } finally {
+            lock.unlock();
+        }
+        if (entry != null) {
+            return entry.getAttributes();
+        }
+        return null;
+        
+    }
+
+    private void loadUserDatabase(InputStream in) throws IOException {
+        BufferedReader reader =
+            new BufferedReader(new InputStreamReader(in, "UTF-8"));
+        Lock lock = userDbLock.writeLock();
+        lock.lock();
+        try {
+            userDb.clear();
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (line.isEmpty() || line.startsWith("#")) {
+                    continue;
+                }
+                int pos = line.indexOf(':');
+                if ((pos != -1) && (pos < (line.length() - 2))) {
+                    String username = line.substring(0, pos).trim();
+                    int pos2 = line.indexOf(':', pos + 1);
+                    String password = null;
+                    String attrs = null;
+                    if ((pos2 != -1) && (pos2 < (line.length() -2))) {
+                        password = line.substring(pos + 1, pos2).trim();
+                        attrs = line.substring(pos2 + 1).trim();
+                    } else {
+                        password = line.substring(pos + 1).trim();
+                    }
+                    
+                    if ((username != null) && (password != null)) {
+                        if (!username.isEmpty() && !password.isEmpty()) {
+                            HashMethod method = HashMethod.PLAIN;
+                            if (password.startsWith("{")) {
+                                pos = password.indexOf('}', 1);
+                                if ((pos != -1) &&
+                                        (pos < password.length() - 2)) {
+                                    method = HashMethod.fromString(password
+                                            .substring(1, pos));
+                                    password = password.substring(pos + 1)
+                                            .toLowerCase();
+                                }
+                            }
+                            if (method != null) {
+                                Map<String, String> attributes = null;
+                                if (attrs != null) {
+                                    attributes = parseAttributes(attrs);
+                                }
+                                userDb.put(username, new Entry(method,
+                                        username, password, attributes));
+                                continue;
+                            }
+                        }
+                    }
+                }
+                // FIXME: better logging?
+                System.err.println("MALFORMED LINE: " + line);
+            }
+        } finally {
+            lock.unlock();
+        }
+        reader.close();
+    }
+
+    private Map<String, String> parseAttributes(String s) {
+        Map<String, String> attributes = null;
+        String[] attrs = s.split("\\s*,\\s*");
+        if ((attrs != null) && (attrs.length > 0)) {
+            Pattern pattern = Pattern.compile("(\\S+)\\s*=\\s*\"([^\"]+)\"");
+            for (String attr : attrs) {
+                Matcher m = pattern.matcher(attr);
+                if (m.matches()) {
+                    if (attributes == null) {
+                        attributes = new HashMap<String,String>(attrs.length);
+                    }
+                    attributes.put(m.group(1).toLowerCase(), m.group(2));
+                } else {
+                    System.err.println("MALFORMED ATTRIBUTE: " + attr);
+                }
+            }
+        }
+        return attributes;
+    }
+
+} // class BasicAuthStrategy
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/ShibbolethAuthStrategy.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/ShibbolethAuthStrategy.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/auth/ShibbolethAuthStrategy.java	(revision 1025)
@@ -0,0 +1,274 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.auth;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+
+final class ShibbolethAuthStrategy implements AuthStrategy {
+    private static final String SHIB_PARAM_RETURN = "return";
+    private static final String SHIB_PARAM_TARGET = "target";
+    private static final String SHIB_PARAM_PROVIDER = "providerId";
+    private static final String CONFIG_PARAM_SSO =
+        "authfilter.shibboleth.sso";
+    private static final String CONFIG_PARAM_SLO =
+        "authfilter.shibboleth.slo";
+    private static final String CONFIG_PARAM_PROVIDER =
+        "authfilter.shibboleth.provider";
+    private static final String CONFIG_PARAM_HOST =
+        "authfilter.shibboleth.host";
+    private static final String CONFIG_PARAM_CONTEXT =
+        "authfilter.shibboleth.context";
+    private static final String CONFIG_PARAM_SESSION =
+        "authfilter.shibboleth.session";
+    private static final String CONFIG_PARAM_IDP =
+        "authfilter.shibboleth.idp";
+    private static final String CONFIG_PARAM_TIMESTAMP =
+        "authfilter.shibboleth.timestamp";
+    private static final String CONFIG_PARAM_USERNAME =
+        "authfilter.shibboleth.username";
+    private static final String CONFIG_PARAM_ATTRIBUTES =
+        "authfilter.shibboleth.attributes";
+    private static final String SESSION_PARAM_SID = "shib.sid";
+    private static final String SESSION_PARAM_IDP = "shib.idp";
+    private static final String SESSION_PARAM_TIMESTAMP = "shib.timestamp";
+    private static final String SESSION_PARAM_PRINCIPAL = "shib.principal";
+    private String ssoUrl;
+    private String sloUrl;
+    private String provider;
+    private String host;
+    private String context;
+    private String sessionHeaderName;
+    private String idpHeaderName;
+    private String timestampHeaderName;
+    private String[] usernameHeaderNames;
+    private String[] attributeHeaderNames;
+
+    public void init(FilterConfig filterconfig, Map<String, String> config)
+            throws ServletException {
+        loadConfig(config);
+    }
+
+    @Override
+    public String getAuthType() {
+        return "SHIB";
+    }
+
+    @Override
+    public void requestAuth(HttpServletRequest request,
+            HttpServletResponse response) throws IOException, ServletException {
+        response.sendRedirect(makeSsoUrl(request));
+    }
+
+    @Override
+    public Result handleAuth(HttpServletRequest request,
+            HttpServletResponse response) throws IOException, ServletException {
+        Result result = new Result();
+
+        HttpSession session = request.getSession();
+        String oldSid = (String) session.getAttribute(SESSION_PARAM_SID);
+        String sid    = getHeader(request, sessionHeaderName);
+
+        if (sid != null) {
+            if ((oldSid == null) || !sid.equals(oldSid)) {
+                /*
+                 * FIXME: if sid != oldSid, shib session had was
+                 *        logout or expired; pass that information on
+                 */
+                final AuthPrincipal principal =
+                    refreshPrinicpal(request, session, sid);
+                if (principal != null) {
+                    result.setAction(Action.CONTINUE_AUTHENTICATED);
+                    result.setPrinicpal(principal);
+                }
+            } else if (sid.equals(oldSid)) {
+                final AuthPrincipal principal =
+                    (AuthPrincipal) session.getAttribute(SESSION_PARAM_PRINCIPAL);
+                result.setAction(Action.CONTINUE_AUTHENTICATED);
+                result.setPrinicpal(principal);
+            }
+        }
+        return result;
+    }
+
+    private AuthPrincipal refreshPrinicpal(HttpServletRequest request,
+            HttpSession session, String sid) {
+        final String idp = getHeader(request, idpHeaderName);
+        final String timestamp = getHeader(request, timestampHeaderName);
+        final String username = getFirstHeader(request,
+                usernameHeaderNames);
+        if ((username != null) && (idp != null) && (timestamp != null)) {
+            Map<String, String> attributes = null;
+            if (attributeHeaderNames != null) {
+                for (String name : attributeHeaderNames) {
+                    String value = getHeader(request, name);
+                    if (value != null) {
+                        if (attributes == null) {
+                            attributes = new HashMap<String, String>();
+                        }
+                        attributes.put(name.toLowerCase(), value);
+                    }
+                }
+            }
+            AuthPrincipal principal = new AuthPrincipal(username, attributes);            session.setAttribute(SESSION_PARAM_SID, sid);
+            session.setAttribute(SESSION_PARAM_IDP, idp);
+            session.setAttribute(SESSION_PARAM_TIMESTAMP, timestamp);
+            session.setAttribute(SESSION_PARAM_PRINCIPAL, principal);
+            return principal;
+        }
+        return null;
+    }
+
+    private String getHeader(HttpServletRequest request, String name) {
+        String s = request.getHeader(name);
+        if (s != null) {
+            s = s.trim();
+            if (s.isEmpty()) {
+                s = null;
+            }
+        }
+        return s;
+    }
+
+    private String getFirstHeader(HttpServletRequest request, String[] names) {
+        for (String name : names) {
+            String s = getHeader(request, name);
+            if (s != null) {
+                return s;
+            }
+        }
+        return null;
+    }
+
+    private String makeSsoUrl(HttpServletRequest request) {
+        return makeUrl(ssoUrl, SHIB_PARAM_TARGET, request);
+    }
+
+    @SuppressWarnings("unused")
+    private String makeSloUrl(HttpServletRequest request) {
+        return makeUrl(sloUrl, SHIB_PARAM_RETURN, request);
+    }
+
+    private String makeUrl(String shibUrl, String param, HttpServletRequest request) {
+        StringBuilder target = new StringBuilder();
+        target.append(request.getScheme());
+        target.append("://");
+        if (host != null) {
+            target.append(host);
+        } else {
+            final String scheme = request.getScheme();
+            int port = request.getServerPort();
+            if (port < 0) {
+                port = 80;
+            }
+            target.append(request.getServerName());
+            if ((scheme.equalsIgnoreCase("http") && (port != 80)) ||
+                (scheme.equalsIgnoreCase("https") && (port != 443))) {
+                target.append(':');
+                target.append(port);
+            }
+        }
+        if (context != null) {
+            target.append(context);
+        } else {
+            target.append(request.getContextPath());
+        }
+        String path = request.getServletPath();
+        if ((path != null) && !path.isEmpty()) {
+            target.append(path);
+        }
+        // handle QueryParams
+        boolean firstParam = true;
+        Iterator<?> params = request.getParameterMap().entrySet().iterator();
+        while (params.hasNext()) {
+            @SuppressWarnings("unchecked")
+            Map.Entry<String, String[]> entry =
+                (Map.Entry<String, String[]>) params.next();
+            for (String value : entry.getValue()) {
+                if (firstParam) {
+                    target.append('?');
+                    firstParam = false;
+                } else {
+                    target.append('&');
+                }
+                target.append(entry.getKey()).append('=').append(value);
+            }
+        }
+
+        StringBuilder url = new StringBuilder(shibUrl);
+        url.append('?')
+            .append(param)
+            .append('=')
+            .append(urlEncode(target.toString()));
+        if (SHIB_PARAM_TARGET.equals(param) && (provider != null)) {
+            url.append('&')
+                .append(SHIB_PARAM_PROVIDER)
+                .append('=')
+                .append(provider);
+        }
+        return url.toString();
+    }
+
+    private void loadConfig(Map<String, String> cfg) {
+        ssoUrl               = readProperty(cfg, CONFIG_PARAM_SSO, "sso");
+        sloUrl               = readProperty(cfg, CONFIG_PARAM_SLO, "slo");
+        provider             = readProperty(cfg, CONFIG_PARAM_PROVIDER, null);
+        host                 = readProperty(cfg, CONFIG_PARAM_HOST, null);
+        context              = readProperty(cfg, CONFIG_PARAM_CONTEXT, null);
+        sessionHeaderName    =
+            readProperty(cfg, CONFIG_PARAM_SESSION, "Shib-Session-ID");
+        idpHeaderName        =
+            readProperty(cfg, CONFIG_PARAM_IDP, "Shib-Identity-Provider");
+        timestampHeaderName  = readProperty(cfg, CONFIG_PARAM_TIMESTAMP,
+                                            "Shib-Authentication-Instant");
+        usernameHeaderNames  = readProperties(cfg, CONFIG_PARAM_USERNAME,
+                                              new String[] {
+                                                 "eduPersonPrincipalName" ,
+                                                 "eppn"
+                                              });
+        attributeHeaderNames =
+            readProperties(cfg, CONFIG_PARAM_ATTRIBUTES, null);
+    }
+
+    private static String readProperty(Map<String, String> cfg, String name,
+            String defaulValue) {
+        String s = cfg.get(name);
+        if (s != null) {
+            s = s.trim();
+            if (s.isEmpty()) {
+                s = null;
+            }
+        }
+        return (s != null) ? s : defaulValue;
+    }
+    
+    private static String[] readProperties(Map<String, String> cfg,
+            String name, String[] defaultValue) {
+        String s = readProperty(cfg, name, null);
+        if (s != null) {
+            String[] result = s.split("[,\\s]+");
+            if (result.length > 0) {
+                return result;
+            }
+        }
+        return defaultValue;
+    }
+
+    private static String urlEncode(String s) {
+        try {
+            return URLEncoder.encode(s, "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalArgumentException("bad encoding", e);
+        }
+    }
+
+} // class ShibbolethAuthStrategy
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.html	(revision 1025)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:border>
+  <div wicket:id="border" class="toggleBorder">
+    <div wicket:id="header" class="toggleBorderHeader">
+	  <span wicket:id="title">Title</span>
+	</div>
+	<div wicket:id="content" class="toggleBorderContent">
+      <wicket:body />
+	</div>
+  </div>
+</wicket:border>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.java	(revision 1025)
@@ -0,0 +1,68 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.border;
+
+import org.apache.wicket.ResourceReference;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.border.Border;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.odlabs.wiquery.core.commons.IWiQueryPlugin;
+import org.odlabs.wiquery.core.commons.WiQueryResourceManager;
+import org.odlabs.wiquery.core.javascript.JsStatement;
+
+@SuppressWarnings("serial")
+public class AjaxToggleBorder extends Border implements IWiQueryPlugin {
+    private static final ResourceReference JAVASCRIPT_RESOURCE =
+        new ResourceReference(AjaxToggleBorder.class, "AjaxToggleBorder.js");
+    private final WebMarkupContainer border;
+
+    public AjaxToggleBorder(String id, IModel<String> title,
+            boolean expanded, String cssClass) {
+        super(id);
+        setRenderBodyOnly(true);
+
+        border = new WebMarkupContainer("border");
+        border.setOutputMarkupId(true);
+
+        final WebMarkupContainer header = new WebMarkupContainer("header");
+        header.add(new Label("title", title));
+        border.add(header);
+
+        final WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        content.add(getBodyContainer());
+        border.add(content);
+        add(border);
+
+        if (!expanded) {
+            header.add(new AttributeAppender("class",
+                    new Model<String>("collapsed"), " "));
+            content.add(new AttributeAppender("style",
+                    new Model<String>("display:none"), ";"));
+        }
+        if (cssClass != null) {
+            content.add(new AttributeAppender("class",
+                    new Model<String>(cssClass), " "));
+        }
+    }
+
+    public AjaxToggleBorder(String id, IModel<String> title, String cssClass) {
+        this(id, title, true, cssClass);
+    }
+
+    public AjaxToggleBorder(String id, IModel<String> title) {
+        this(id, title, true, null);
+    }
+
+    @Override
+    public void contribute(WiQueryResourceManager manager) {
+        manager.addJavaScriptResource(JAVASCRIPT_RESOURCE);
+    }
+
+    @Override
+    public JsStatement statement() {
+        return new JsStatement().$(border).append(".ajaxToggleBorder()");
+    }
+
+} // class AjaxToggleBorder
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.js
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.js	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/border/AjaxToggleBorder.js	(revision 1025)
@@ -0,0 +1,17 @@
+(function($) {
+	$.extend($.fn, {
+		ajaxToggleBorder : function() {
+			var o = {
+				speed: 250
+			};
+			var header = $(this).children('div.toggleBorderHeader');
+			var content = $(this).children('div.toggleBorderContent');
+			header.bind('click.ajaxToggleBorder', function() {
+				content.slideToggle(o.speed, function() {
+					header.toggleClass('collapsed');
+				});
+			});
+			return $(this);
+		}
+	});
+})(jQuery);
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog$ButtonBar.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog$ButtonBar.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog$ButtonBar.html	(revision 1025)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <form wicket:id="buttonsForm">
+    <div>
+      <input wicket:id="yesButton"
+             type="submit" class="button yesButton" value="Yes" />
+      <input wicket:id="noButton"
+             type="submit"  class="button noButton" value="No" />
+    </div>         
+  </form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog$Content.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog$Content.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog$Content.html	(revision 1025)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<span wicket:id="message">Message</span>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ConfirmationDialog.java	(revision 1025)
@@ -0,0 +1,122 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.dialog;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.basic.MultiLineLabel;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+@SuppressWarnings("serial")
+public abstract class ConfirmationDialog extends ModalDialogBase {
+    private final class ButtonBar extends Panel {
+        public ButtonBar(String id) {
+            super(id);
+            final Form<Void> form = new Form<Void>("buttonsForm");
+            final AjaxButton yesButton =
+                new AjaxButton("yesButton", new Model<String>("Yes"), form) {
+                @Override
+                protected void onSubmit(AjaxRequestTarget target,
+                        Form<?> form) {
+                    answer = true;
+                    ConfirmationDialog.this.close(target);
+                }
+            };
+            yesButton.setDefaultFormProcessing(false);
+            form.add(yesButton);
+            final AjaxButton noButton =
+                new AjaxButton("noButton", new Model<String>("No"), form) {
+                @Override
+                protected void onSubmit(AjaxRequestTarget target,
+                        Form<?> form) {
+                    answer = false;
+                    ConfirmationDialog.this.close(target);
+                }
+            };
+            noButton.setDefaultFormProcessing(false);
+            form.add(noButton);
+            add(form);
+        }
+    } // class ConfirmationDialog.ButtonBar
+
+    private final class Content extends Panel {
+        private Content(String id) {
+            super(id);
+            messageLabel = new MultiLineLabel("message");
+            add(messageLabel);
+        }
+    } // class ConfirmationDialog.Content
+
+    private boolean answer;
+    private MultiLineLabel messageLabel;
+
+    public ConfirmationDialog(String id, IModel<String> message,
+            final Component updateComponent) {
+        super(id, new Model<String>("Please confirm"));
+        setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+            @Override
+            public void onClose(AjaxRequestTarget target) {
+                if (answer) {
+                    onConfirm(target);
+                    if (updateComponent != null) {
+                        target.addComponent(updateComponent);
+                    }
+                } else {
+                    onCancel(target);
+                }
+            }
+        });
+        if (message != null) {
+            setMessage(message);
+        }
+    }
+
+    public ConfirmationDialog(String id, final Component updateComponent) {
+        this(id, null, updateComponent);
+    }
+
+    public ConfirmationDialog(String id) {
+        this(id, null, null);
+    }
+
+    @Override
+    protected Panel createButtonBar(String id) {
+        return new ButtonBar(id);
+    }
+
+    @Override
+    protected Content createContent(String id) {
+        Content content = new Content(id);
+        content.add(new AttributeAppender("class",
+                new Model<String>("confirmationDialog"), " "));
+        return content;
+    }
+
+    @Override
+    public void show(AjaxRequestTarget target) {
+        answer = false; /* set save default value */
+        super.show(target);
+    }
+
+    public void show(AjaxRequestTarget target, IModel<String> message) {
+        setMessage(message);
+        this.show(target);
+    }
+
+    public void setMessage(IModel<String> message) {
+        if (message == null) {
+            throw new NullPointerException("message == null");
+        }
+        messageLabel.setDefaultModel(message);
+    }
+
+    public abstract void onConfirm(AjaxRequestTarget target);
+
+    public void onCancel(AjaxRequestTarget target) {
+    }
+
+} // abstract class ConfirmationDialog
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalDialogBase$Content.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalDialogBase$Content.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalDialogBase$Content.html	(revision 1025)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div class="modalDialog">
+  <div wicket:id="dialogContent" class="modalDialogContent"></div>
+  <div wicket:id="dialogButtons" class="modalDialogButtons"></div>
+</div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalDialogBase.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalDialogBase.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalDialogBase.java	(revision 1025)
@@ -0,0 +1,42 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.dialog;
+
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+
+@SuppressWarnings("serial")
+public abstract class ModalDialogBase extends ModalWindow {
+    private final class Content extends Panel {
+        public Content(String id) {
+            super(id);
+            setOutputMarkupId(true);
+            Panel content = createContent("dialogContent");
+            if (content == null) {
+                throw new NullPointerException("createContent() == null");
+            }
+            add(content);
+            Panel buttons = createButtonBar("dialogButtons");
+            if (buttons == null) {
+                throw new NullPointerException("createButtonBar() == null");
+            }
+            add(buttons);
+        }
+    } // class ModalDialogBase.Content
+
+    public ModalDialogBase(String id, IModel<String> title) {
+        super(id);
+        if (title == null) {
+            throw new NullPointerException("title == null");
+        }
+        setOutputMarkupId(true);
+        setInitialWidth(350);
+        setUseInitialHeight(false);
+        setTitle(title);
+        setContent(new Content(this.getContentId()));
+    }
+    
+    protected abstract Panel createButtonBar(String id);
+
+    protected abstract Panel createContent(String id);
+    
+} // class ModalDialogBase;
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalEditDialogBase$ButtonBar.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalEditDialogBase$ButtonBar.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalEditDialogBase$ButtonBar.html	(revision 1025)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <input wicket:id="addButton"
+         type="submit" class="button yesButton" value="Add" />
+  <input wicket:id="modifyButton"
+         type="submit" class="button yesButton" value="Modify" />
+  <input wicket:id="cancelButton"
+         type="submit"  class="button noButton" value="Cancel" />
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalEditDialogBase.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalEditDialogBase.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/dialog/ModalEditDialogBase.java	(revision 1025)
@@ -0,0 +1,156 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.dialog;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+@SuppressWarnings("serial")
+public abstract class ModalEditDialogBase<T> extends ModalDialogBase {
+    public abstract class ContentPanel extends Panel {
+        protected ContentPanel(String id) {
+            super(id);
+        }
+        
+        public abstract Form<T> getForm();
+        
+        public abstract FeedbackPanel getFeedbackPanel();
+    } // class ModalEditDialogBase.ContentPanel
+    
+    private final class ButtonBar extends Panel {
+        public ButtonBar(String id, Form<?> form) {
+            super(id);
+            addButton = new AjaxButton("addButton",
+                    new Model<String>("Add"), form) {
+                @Override
+                protected void onError(AjaxRequestTarget target, Form<?> form) {
+                    target.addComponent(contentPanel.getFeedbackPanel());
+                    super.onError(target, form);
+                }
+
+                @SuppressWarnings("unchecked")
+                @Override
+                protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                    ModalEditDialogBase.this.doSubmit(target, (Form<T>) form);
+                }
+            };
+            add(addButton);
+            modifyButton = new AjaxButton("modifyButton",
+                    new Model<String>("Modify"), form) {
+                @Override
+                protected void onError(AjaxRequestTarget target, Form<?> form) {
+                    target.addComponent(contentPanel.getFeedbackPanel());
+                    super.onError(target, form);
+                }
+
+                @SuppressWarnings("unchecked")
+                @Override
+                protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                    ModalEditDialogBase.this.doSubmit(target, (Form<T>) form);
+                }
+            };
+            add(modifyButton);
+            final AjaxButton cancelButton =
+                new AjaxButton("cancelButton",
+                        new Model<String>("Cancel"), form) {
+                @SuppressWarnings("unchecked")
+                @Override
+                protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                    ModalEditDialogBase.this.doCancel(target, (Form<T>) form);
+                }
+            };
+            cancelButton.setDefaultFormProcessing(false);
+            add(cancelButton);
+        }
+    } // class EditCreatorDialog.ButtonBar
+
+    private ContentPanel contentPanel;
+    private AjaxButton addButton;
+    private AjaxButton modifyButton;
+
+    protected ModalEditDialogBase(String id, IModel<String> title) {
+        super(id, title);
+    }
+
+    @Override
+    protected final Panel createContent(String id) {
+        final IModel<T> model = createModel();
+        contentPanel = createContentPanel(id, model);
+        contentPanel.add(new AttributeAppender("class",
+                new AbstractReadOnlyModel<String>() {
+                    public String getObject() {
+                        final String clazz = getCssClass();
+                        if ((clazz != null) && !clazz.isEmpty()) {
+                            StringBuilder sb = new StringBuilder("editDialog");
+                            sb.append(' ');
+                            sb.append(clazz);
+                            return sb.toString();
+                        } else {
+                            return "editDialog";
+                        }
+                    }
+                }, " "));
+        contentPanel.getForm().removePersistentFormComponentValues(true);
+        contentPanel.getFeedbackPanel().setOutputMarkupId(true);
+        return contentPanel;
+    }
+
+    @Override
+    protected final Panel createButtonBar(String id) {
+        return new ButtonBar(id, contentPanel.getForm());
+    }
+
+    @Override
+    public final void show(AjaxRequestTarget target) {
+        this.show(target, null);
+    }
+
+    public final void show(AjaxRequestTarget target, T object) {
+        if (object == null) {
+            object = newObjectInstance();
+            addButton.setVisible(true);
+            modifyButton.setVisible(false);
+        } else {
+            addButton.setVisible(false);
+            modifyButton.setVisible(true);
+        }
+        contentPanel.getForm().setModelObject(object);
+        super.show(target);
+    }
+
+    private final void doSubmit(AjaxRequestTarget target, Form<T> form) {
+        close(target);
+        final T object = form.getModelObject();
+        form.setModelObject(null);
+        onSubmit(target, object);
+    }
+    
+    private final void doCancel(AjaxRequestTarget target, Form<T> form) {
+        close(target);
+        final T object = form.getModelObject();
+        form.setModelObject(null);
+        onCancel(target, object);
+    }
+
+    protected String getCssClass() {
+        return null;
+    }
+
+    protected abstract T newObjectInstance();
+
+    protected abstract IModel<T> createModel();
+
+    protected abstract ContentPanel createContentPanel(String id,
+            IModel<T> model);
+    
+    public abstract void onSubmit(AjaxRequestTarget target, T object);
+
+    public void onCancel(AjaxRequestTarget target, T object) {
+    }
+
+} // class ModalEditDialog
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxLinkMenuItem.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxLinkMenuItem.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxLinkMenuItem.java	(revision 1025)
@@ -0,0 +1,86 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.menu;
+
+import java.io.Serializable;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.model.IModel;
+
+@SuppressWarnings("serial")
+public class AjaxLinkMenuItem<T> implements Serializable, MenuItem {
+    private final IModel<String> label;
+    private final IModel<T> model;
+    private final String cssClass;
+    private boolean visible = true;
+    private boolean enabled = true;
+
+    public AjaxLinkMenuItem(IModel<String> label, IModel<T> model,
+            String cssClass) {
+        this.label = label;
+        this.model = model;
+        this.cssClass = cssClass;
+    }
+
+    public AjaxLinkMenuItem(IModel<String> label, IModel<T> model) {
+        this(label, model, null);
+    }
+
+    public AjaxLinkMenuItem(IModel<String> label, String cssClass) {
+        this(label, null, cssClass);
+    }
+
+    public AjaxLinkMenuItem(IModel<String> label) {
+        this(label, null, null);
+    }
+
+    @Override
+    public IModel<String> getLabel() {
+        return label;
+    }
+
+    @Override
+    public String getCssClass() {
+        return cssClass;
+    }
+
+    @Override
+    public AbstractLink newLink(String componentId) {
+        return new AjaxLink<T>(componentId, model) {
+            @Override
+            public void onClick(AjaxRequestTarget target) {
+                AjaxLinkMenuItem.this.onClick(target, getModel());
+            }
+        };
+    }
+    
+    protected void onClick(AjaxRequestTarget target, IModel<T> model) {
+        onClick(target);
+    }
+
+    protected void onClick(AjaxRequestTarget target) {
+    }
+
+    @Override
+    public MenuItem setVisible(boolean visible) {
+        this.visible = visible;
+        return this;
+    }
+
+    @Override
+    public boolean isVisible() {
+        return visible;
+    }
+
+    @Override
+    public MenuItem setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        return this;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+} // class AjaxLinkMenuItem
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.css
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.css	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.css	(revision 1025)
@@ -0,0 +1,32 @@
+div.popupTrigger {
+	cursor: pointer;
+}
+
+div.popupMenu {
+    position: absolute;
+    color: #000000;
+    background-color: #ffffff;
+    padding: 2px;
+    margin: 0;
+    border: 1px solid #000000;
+    display: none;
+}
+
+div.popupMenu ul {
+    padding: 0;
+    margin: 0;
+}
+
+div.popupMenu li {
+	list-style: none;
+    padding: 0px;
+    margin: 0px;
+}
+
+div.popupMenu li a {
+    padding: 2px 2px;
+    margin: 0;
+    display: block;
+    text-decoration: none;
+    text-align: left;
+}
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.html	(revision 1025)
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head/>
+<body>
+<wicket:panel>
+<div wicket:id="menu" class="popupMenuWrapper">
+  <div class="popupTrigger">
+    <span wicket:id="title" class="title"></span>
+  </div>
+  <div class="popupMenu">
+    <ul>
+      <li wicket:id="items"><a wicket:id="link" href="javascript:void(0);"><span wicket:id="label"></span></a></li>
+    </ul>
+  </div>
+</div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.java	(revision 1025)
@@ -0,0 +1,84 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.menu;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.ResourceReference;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.odlabs.wiquery.core.commons.IWiQueryPlugin;
+import org.odlabs.wiquery.core.commons.WiQueryResourceManager;
+import org.odlabs.wiquery.core.javascript.JsStatement;
+
+@SuppressWarnings("serial")
+public class AjaxPopupMenu extends Panel implements Serializable, IWiQueryPlugin {
+    private static final ResourceReference CSS_RESOURCE =
+        new ResourceReference(AjaxPopupMenu.class, "AjaxPopupMenu.css");
+    private static final ResourceReference JAVASCRIPT_RESOURCE =
+        new ResourceReference(AjaxPopupMenu.class, "AjaxPopupMenu.js");
+    private final WebMarkupContainer menu;
+    private List<MenuItem> items = new ArrayList<MenuItem>();
+
+    public AjaxPopupMenu(String id, IModel<String> title) {
+        super(id);
+        setRenderBodyOnly(true);
+
+        menu = new WebMarkupContainer("menu");
+        menu.setOutputMarkupId(true);
+        menu.add(new Label("title", title));
+        menu.add(new ListView<MenuItem>("items", items) {
+            @Override
+            protected void populateItem(ListItem<MenuItem> item) {
+                final MenuItem menuitem = item.getModelObject();
+                final AbstractLink link = menuitem.newLink("link");
+                link.add(new Label("label", menuitem.getLabel()));
+                String cssClass = menuitem.getCssClass();
+                if (cssClass != null) {
+                    link.add(new SimpleAttributeModifier("class", cssClass));
+                }
+                item.add(link);
+                item.setVisible(menuitem.isVisible());
+                item.setEnabled(menuitem.isEnabled());
+            }
+        });
+        add(menu);
+    }
+
+    public void add(MenuItem item) {
+        items.add(item);
+    }
+
+    @Override
+    public void contribute(WiQueryResourceManager manager) {
+        manager.addJavaScriptResource(JAVASCRIPT_RESOURCE);
+        manager.addCssResource(CSS_RESOURCE);
+    }
+
+    @Override
+    public JsStatement statement() {
+        return new JsStatement().$(menu).append(".ajaxPopupMenu()");
+    }
+
+    @Override
+    protected void onBeforeRender() {
+        super.onBeforeRender();
+        boolean anyVisible = false;
+        for (MenuItem item : items) {
+            if (item.isVisible()) {
+                anyVisible = true;
+                break;
+            }
+        }
+        if (!anyVisible) {
+            setVisible(false);
+        }
+    }
+
+} // class PopupMenu
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.js
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.js	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/AjaxPopupMenu.js	(revision 1025)
@@ -0,0 +1,59 @@
+(function($) {
+	$.extend($.fn, {
+		ajaxPopupMenu: function() {
+			var o = {
+				inSpeed: 250,
+				outSpeed: 75
+			};
+			var trigger = $(this).children('div.popupTrigger');
+			var menu = $(this).children('div.popupMenu');
+			trigger.bind('click.ajaxPopupMenu', function(e) {
+				e.stopPropagation();
+
+				// hide any open context menus that may be showing
+				$('div.popupMenu').fadeOut(o.outSpeed);
+
+				// detect mouse position
+				var d = {}, x, y;
+				if (self.innerHeight) {
+					d.pageYOffset = self.pageYOffset;
+					d.pageXOffset = self.pageXOffset;
+					d.innerHeight = self.innerHeight;
+					d.innerWidth = self.innerWidth;
+				} else if (document.documentElement
+						&& document.documentElement.clientHeight) {
+					d.pageYOffset = document.documentElement.scrollTop;
+					d.pageXOffset = document.documentElement.scrollLeft;
+					d.innerHeight = document.documentElement.clientHeight;
+					d.innerWidth = document.documentElement.clientWidth;
+				} else if (document.body) {
+					d.pageYOffset = document.body.scrollTop;
+					d.pageXOffset = document.body.scrollLeft;
+					d.innerHeight = document.body.clientHeight;
+					d.innerWidth = document.body.clientWidth;
+				}
+				(e.pageX) ? x = e.pageX : x = e.clientX + d.scrollLeft;
+				(e.pageY) ? y = e.pageY : y = e.clientY + d.scrollTop;
+
+				menu.css({
+					top : y,
+					left : x
+				}).fadeIn(o.inSpeed);
+				$(document).bind('keypress.ajaxPopupMenu', function(e) {
+					if (e.keyCode == 27) {
+						$(document).trigger('click');
+					}
+				});
+				setTimeout(function() {
+					$(document).click(function() {
+						$(document).unbind('click.ajaxPopupMenu')
+							.unbind('keypress.ajaxPopupMenu');
+						menu.fadeOut(o.outSpeed);
+					});
+				}, 0);
+				return false;
+			});
+			return $(this);
+		}
+	});
+})(jQuery);
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/MenuItem.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/MenuItem.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/menu/MenuItem.java	(revision 1025)
@@ -0,0 +1,22 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.menu;
+
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.model.IModel;
+
+public interface MenuItem {
+
+    public IModel<String> getLabel();
+    
+    public String getCssClass();
+
+    public AbstractLink newLink(String componentId);
+    
+    public MenuItem setVisible(boolean visible);
+    
+    public boolean isVisible();
+    
+    public MenuItem setEnabled(boolean enabled);
+    
+    public boolean isEnabled();
+
+} // interface MenuItem
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AdminPage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AdminPage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AdminPage.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:extend>
+  <p style="font-size: 120%; text-align: center;">Admin Page</p>
+</wicket:extend>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AdminPage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AdminPage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AdminPage.java	(revision 1025)
@@ -0,0 +1,15 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import org.apache.wicket.authorization.strategies.role.Roles;
+import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeAction;
+import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeInstantiation;
+
+@AuthorizeInstantiation(Roles.ADMIN)
+@AuthorizeAction(action = "ENABLE", roles = { Roles.ADMIN })
+public class AdminPage extends BasePage {
+
+    public AdminPage() {
+        super();
+    }
+    
+} // class AdminPage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AuthenticationStatePanel.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AuthenticationStatePanel.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AuthenticationStatePanel.html	(revision 1025)
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div wicket:id="login"></div>
+<wicket:fragment wicket:id="loginFragment">
+  Anonymous <a wicket:id="loginLink" href="#">[Login]</a>
+</wicket:fragment>
+
+<div wicket:id="logout"></div>
+<wicket:fragment wicket:id="logoutFragment">
+  <span wicket:id="username">username</span>
+  <!-- <a wicket:id="logoutLink" href="#">[Logout]</a>  -->
+</wicket:fragment>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AuthenticationStatePanel.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AuthenticationStatePanel.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/AuthenticationStatePanel.java	(revision 1025)
@@ -0,0 +1,98 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.authentication.AuthenticatedWebSession;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.StatelessLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.ApplicationSession;
+
+@SuppressWarnings("serial")
+public class AuthenticationStatePanel extends Panel {
+
+    public AuthenticationStatePanel(String id) {
+        super(id);
+        setRenderBodyOnly(true);
+        add(new LoginFragment("login"));
+        add(new LogoutFragment("logout"));
+    }
+
+    private class LoginFragment extends Fragment {
+        public LoginFragment(String id) {
+            super(id, "loginFragment", AuthenticationStatePanel.this);
+            setRenderBodyOnly(true);
+            final StatelessLink loginLink = new StatelessLink("loginLink") {
+                @Override
+                public void onClick() {
+                }
+
+                @Override
+                protected CharSequence getURL() {
+                    final Page page = getPage();
+                    StringBuilder url =
+                        new StringBuilder(urlFor(page.getClass(),
+                                page.getPageParameters()));
+                    if (url.indexOf("?") != -1) {
+                        url.append('&');
+                    } else {
+                        url.append('?');
+                    }
+                    url.append("authAction=LOGIN");
+                    return url.toString();
+                }
+            };
+            add(loginLink);
+        }
+
+        @Override
+        public boolean isVisible() {
+            return !((AuthenticatedWebSession) getSession()).isSignedIn();
+        }
+
+        @Override
+        protected boolean getStatelessHint() {
+            return true;
+        }
+    } // private class LoginFragment
+
+    private class LogoutFragment extends Fragment {
+        private final Label usernameLabel;
+        
+        public LogoutFragment(String id) {
+            super(id, "logoutFragment", AuthenticationStatePanel.this);
+            setRenderBodyOnly(true);
+            usernameLabel = new Label("username");
+            usernameLabel.setRenderBodyOnly(true);
+            add(usernameLabel);
+//            final StatelessLink logoutLink = new StatelessLink("logoutLink") {
+//                @Override
+//                public void onClick() {
+//                }
+//            };
+//            add(logoutLink);
+        }
+
+        @Override
+        public boolean isVisible() {
+            return ((AuthenticatedWebSession) getSession()).isSignedIn();
+        }
+
+        @Override
+        protected void onBeforeRender() {
+            ApplicationSession session = (ApplicationSession) getSession();
+            final Model<String> userModel =
+                new Model<String>(session.getUserDisplay());
+            usernameLabel.setDefaultModel(userModel);
+            super.onBeforeRender();
+        }
+    } // private class LogoutFragment
+
+    @Override
+    protected boolean getStatelessHint() {
+        return true;
+    }
+
+} // class AuthenticationStatePanel
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BasePage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BasePage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BasePage.html	(revision 1025)
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+  <head>
+    <title>CLARIN Virtual Collection Registry</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <link href="css/vcr.css" rel="stylesheet" type="text/css" />
+  </head>
+  <body>
+    <noscript>
+      <div id="noscript-warning">Virtual Collection Registry requires JavaScript to be enabled!</div>
+    </noscript>
+    <div id="container">
+      <div id="header">
+        <div id="header-logo">
+          <div id="clarin-logo" />
+          <div id="ids-logo"/>
+          <h1>Virtual Collection Registry</h1>
+        </div>
+        <div id="navigation">
+          <div id="menu" wicket:id="menu"></div>
+          <div id="login">
+            <div wicket:id="authstate"></div>
+          </div>
+        </div>
+      </div>
+      <div id="content"><wicket:child /></div>
+      <div class="push"></div>
+    </div>
+    <div id="footer">
+      Version ${pom.version}      
+    </div>
+  </body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BasePage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BasePage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BasePage.java	(revision 1025)
@@ -0,0 +1,75 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.ApplicationSession;
+
+public class BasePage extends WebPage {
+
+    protected BasePage(IModel<?> model) {
+        super(model);
+        // authentication state
+        add(new AuthenticationStatePanel("authstate"));
+
+        // main navigation menu
+        final Menu menu = new Menu("menu");
+        menu.addMenuItem(new MenuItem<BrowsePublicCollectionsPage>(
+                new Model<String>("Virtual Collections"), 
+                BrowsePublicCollectionsPage.class));
+        menu.addMenuItem(new MenuItem<BrowsePrivateCollectionsPage>(
+                new Model<String>("My Virtual Collections"),
+                BrowsePrivateCollectionsPage.class));
+        menu.addMenuItem(new MenuItem<CreateVirtualCollectionPage>(
+                new Model<String>("Create Virtual Collection"),
+                CreateVirtualCollectionPage.class));
+        menu.addMenuItem(new MenuItem<AdminPage>(
+                new Model<String>("Admin Page"),
+                AdminPage.class));
+        add(menu);
+    }
+
+    protected BasePage() {
+        this(null);
+    }
+
+    @Override
+    protected void onBeforeRender() {
+        // skip lazy auto-auth for login page
+        if (!this.getClass().isInstance(LoginPage.class)) {
+            final HttpServletRequest request =
+                getWebRequestCycle().getWebRequest().getHttpServletRequest();
+            final ApplicationSession session =
+                (ApplicationSession) getSession();
+            if (!session.isSignedIn()) {
+                if (request.getAuthType() != null) {
+                    // FIXME: better logging
+                    System.err.println("Auth, but no authed session -> login");
+                    final Principal principal = request.getUserPrincipal();
+                    if (!session.signIn(principal)) {
+                        throw new RestartResponseException(getApplication()
+                                .getApplicationSettings()
+                                .getAccessDeniedPage());
+                    }
+                }
+            } else {
+                if (request.getAuthType() == null) {
+                    // FIXME: better logging
+                    System.err.println("Lost Session!");
+                    session.invalidate();
+                    throw new RestartResponseException(getApplication()
+                            .getApplicationSettings()
+                            .getPageExpiredErrorPage());
+                }
+            }
+        }
+        super.onBeforeRender();
+    }
+
+} // class BasePage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage$ActionsColumn.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage$ActionsColumn.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage$ActionsColumn.html	(revision 1025)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <div wicket:id="menu"></div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage$ActionsPanel.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage$ActionsPanel.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage$ActionsPanel.html	(revision 1025)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <a wicket:id="publish" href="#" class="publish"><span>[publish]</span></a>
+  <a wicket:id="edit" href="#" class="edit"><span>[edit]</span></a>
+  <a wicket:id="delete" href="#" class="delete"><span>[delete]</span></a>
+  <a wicket:id="details" href="#" class="details"><span>[details]</span></a>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage.html	(revision 1025)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:extend>
+  <div style="display: none" wicket:id="publishCollectionDialog"></div>
+  <div style="display: none" wicket:id="deleteCollectionDialog"></div>
+  <div wicket:id="collectionsTable"></div>
+</wicket:extend>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePrivateCollectionsPage.java	(revision 1025)
@@ -0,0 +1,266 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import java.security.Principal;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.authorization.strategies.role.Roles;
+import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.StringResourceModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.ApplicationSession;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.dialog.ConfirmationDialog;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.menu.AjaxLinkMenuItem;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.menu.AjaxPopupMenu;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.table.VirtualCollectionTable;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection.State;
+
+@AuthorizeInstantiation(Roles.USER)
+@SuppressWarnings("serial")
+public class BrowsePrivateCollectionsPage extends BasePage {
+    private class ActionsColumn extends Panel {
+        public ActionsColumn(String id, IModel<VirtualCollection> model) {
+            super(id, model);
+            setRenderBodyOnly(true);
+
+            final AjaxPopupMenu menu =
+                new AjaxPopupMenu("menu", new Model<String>("[actions]"));
+
+            final AjaxLinkMenuItem<VirtualCollection> publishItem =
+                new AjaxLinkMenuItem<VirtualCollection>(
+                    new Model<String>("Publish"), model, "publish") {
+                @Override
+                protected void onClick(AjaxRequestTarget target,
+                        IModel<VirtualCollection> model) {
+                    doPublish(target, model.getObject());
+                }
+            };
+            menu.add(publishItem);
+
+            final AjaxLinkMenuItem<VirtualCollection> editItem =
+                new AjaxLinkMenuItem<VirtualCollection>(
+                    new Model<String>("Edit"), model, "edit") {
+                @Override
+                protected void onClick(AjaxRequestTarget target,
+                        IModel<VirtualCollection> model) {
+                    doEdit(target, model.getObject());
+                }
+            };
+            menu.add(editItem);
+
+            final AjaxLinkMenuItem<VirtualCollection> deleteItem =
+                new AjaxLinkMenuItem<VirtualCollection>(
+                    new Model<String>("Delete"), model, "delete") {
+                @Override
+                protected void onClick(AjaxRequestTarget target,
+                        IModel<VirtualCollection> model) {
+                    doDelete(target, model.getObject());
+                }
+            };
+            menu.add(deleteItem);
+
+            final AjaxLinkMenuItem<VirtualCollection> detailsItem =
+                new AjaxLinkMenuItem<VirtualCollection>(
+                    new Model<String>("Details"), model, "details") {
+                @Override
+                protected void onClick(AjaxRequestTarget target,
+                        IModel<VirtualCollection> model) {
+                    doDetails(target, model);
+                }
+            };
+            menu.add(detailsItem);
+            add(menu);
+            
+            final VirtualCollection vc = model.getObject();
+            if (vc.isDeleted()) {
+                detailsItem.setVisible(false).setEnabled(false);
+            }
+            if (!vc.isPrivate()) {
+                editItem.setVisible(false).setEnabled(false);
+                publishItem.setVisible(false).setEnabled(false);
+                deleteItem.setVisible(false).setEnabled(false);
+            }
+        }
+    }
+
+    private class ActionsPanel extends Panel {
+        public ActionsPanel(String id, IModel<VirtualCollection> model) {
+            super(id, model);
+            setRenderBodyOnly(true);
+
+            final AjaxLink<VirtualCollection> publishLink =
+                new AjaxLink<VirtualCollection>("publish", model) {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    doPublish(target, getModelObject());
+                }
+            };
+            add(publishLink);
+
+            final AjaxLink<VirtualCollection> editLink =
+                new AjaxLink<VirtualCollection>("edit", model) {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    doEdit(target, getModelObject());
+                }
+            };
+            add(editLink);
+
+            final AjaxLink<VirtualCollection> deleteLink =
+                new AjaxLink<VirtualCollection>("delete", model) {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        doDelete(target, getModelObject());
+                    }
+            };
+            add(deleteLink);
+
+            final AjaxLink<VirtualCollection> detailsLink =
+                new AjaxLink<VirtualCollection>("details", model) {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    doDetails(target, getModel());
+                }
+            };
+            add(detailsLink);
+
+            final VirtualCollection vc = model.getObject();
+            if (vc.isDeleted()) {
+                detailsLink.setVisible(false).setEnabled(false);
+            }
+            if (!vc.isPrivate()) {
+                editLink.setVisible(false).setEnabled(false);
+                publishLink.setVisible(false).setEnabled(false);
+                deleteLink.setVisible(false).setEnabled(false);
+            }
+            boolean isVisible = detailsLink.isVisible() ||
+                    editLink.isVisible() || publishLink.isVisible() ||
+                    deleteLink.isVisible();
+            setVisible(isVisible);
+        }
+    } // class BrowsePrivateCollectionsPage.ActionsPanel
+
+    private final class PublishCollectionDialog extends ConfirmationDialog {
+        private long vcId;
+        
+        public PublishCollectionDialog(String id,
+                final Component updateComponenet) {
+            super(id, updateComponenet);
+            setInitialWidth(400);
+        }
+
+        @Override
+        public void onConfirm(AjaxRequestTarget target) {
+            try {
+                final VirtualCollectionRegistry vcr =
+                    VirtualCollectionRegistry.instance();
+                vcr.setVirtualCollectionState(getUser(), vcId,
+                        State.PUBLIC_PENDING);
+            } catch (VirtualCollectionRegistryException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public void show(AjaxRequestTarget target, VirtualCollection vc) {
+            this.vcId = vc.getId();
+            super.show(target,
+                    new StringResourceModel("collections.publishconfirm",
+                            new Model<VirtualCollection>(vc)));
+        }
+    } // class BrowsePrivateCollectionsPage.PublishCollectionDialog
+
+    private final class DeleteCollectionDialog extends ConfirmationDialog {
+        private long vcId;
+        
+        public DeleteCollectionDialog(String id,
+                final Component updateComponenet) {
+            super(id, updateComponenet);
+            setInitialWidth(400);
+        }
+
+        @Override
+        public void onConfirm(AjaxRequestTarget target) {
+            try {
+                final VirtualCollectionRegistry vcr =
+                    VirtualCollectionRegistry.instance();
+                vcr.deleteVirtualCollection(getUser(), vcId);
+            } catch (VirtualCollectionRegistryException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public void show(AjaxRequestTarget target, VirtualCollection vc) {
+            this.vcId = vc.getId();
+            super.show(target,
+                    new StringResourceModel("collections.deleteconfirm",
+                            new Model<VirtualCollection>(vc)));
+        }
+    } // class BrowsePrivateCollectionsPage.PublishCollectionDialog
+
+    private final PublishCollectionDialog publishDialog;
+    private final DeleteCollectionDialog deleteDialog;
+
+    public BrowsePrivateCollectionsPage() {
+        super();
+        final VirtualCollectionTable table =
+            new VirtualCollectionTable("collectionsTable", true) {
+            @Override
+            protected Panel createActionColumn(String componentId,
+                    IModel<VirtualCollection> model) {
+                return new ActionsColumn(componentId, model);
+            }
+
+            @Override
+            protected Panel createActionPanel(String componentId,
+                    IModel<VirtualCollection> model) {
+                return new ActionsPanel(componentId, model);
+            }
+        };
+        add(table);
+
+        publishDialog = new PublishCollectionDialog("publishCollectionDialog",
+                table);
+        add(publishDialog);
+        deleteDialog = new DeleteCollectionDialog("deleteCollectionDialog",
+                table);
+        add(deleteDialog);
+    }
+
+    private void doEdit(AjaxRequestTarget target,
+            VirtualCollection vc) {
+        setResponsePage(new CreateVirtualCollectionPage(vc, getPage()));
+    };
+
+    private void doPublish(AjaxRequestTarget target,
+            VirtualCollection vc) {
+        publishDialog.show(target, vc);
+    }
+
+    private void doDelete(AjaxRequestTarget target,
+            VirtualCollection vc) {
+        deleteDialog.show(target, vc);
+    }
+
+    private void doDetails(AjaxRequestTarget target,
+            IModel<VirtualCollection> vc) {
+        setResponsePage(new VirtualCollectionDetailsPage(vc, getPage()));
+    }
+
+    private Principal getUser() {
+        ApplicationSession session = (ApplicationSession) getSession();
+        Principal principal = session.getPrincipal();
+        if (principal == null) {
+            throw new WicketRuntimeException("principal == null");
+        }
+        return principal;
+    }
+
+} // class BrowsePrivateCollectionsPage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage$ActionsPanel.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage$ActionsPanel.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage$ActionsPanel.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <a wicket:id="details" href="#" class="details"><span>[details]</span></a>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:extend>
+  <div wicket:id="collectionsTable"></div>
+</wicket:extend>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/BrowsePublicCollectionsPage.java	(revision 1025)
@@ -0,0 +1,53 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.table.VirtualCollectionTable;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+public class BrowsePublicCollectionsPage extends BasePage {
+    private class ActionsPanel extends Panel {
+        public ActionsPanel(String id, IModel<VirtualCollection> model) {
+            super(id, model);
+            setRenderBodyOnly(true);
+
+            final AjaxLink<VirtualCollection> detailsLink =
+                new AjaxLink<VirtualCollection>("details", model) {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    doDetails(target, getModel());
+                }
+            };
+            add(detailsLink);
+        }
+    } // class BrowsePublicCollectionsPage.ActionsPanel
+
+    public BrowsePublicCollectionsPage() {
+        super();
+        final VirtualCollectionTable table =
+            new VirtualCollectionTable("collectionsTable", false) {
+                @Override
+                protected Panel createActionColumn(String componentId,
+                        IModel<VirtualCollection> model) {
+                    return new ActionsPanel(componentId, model);
+                }
+
+                @Override
+                protected Panel createActionPanel(String componentId,
+                        IModel<VirtualCollection> model) {
+                    return new ActionsPanel(componentId, model);
+                }
+        };
+        add(table);
+    }
+
+    private void doDetails(AjaxRequestTarget target,
+            IModel<VirtualCollection> vc) {
+        setResponsePage(new VirtualCollectionDetailsPage(vc, getPage()));
+    }
+
+} // class BrowsePublicCollectionsPage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/CreateVirtualCollectionPage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/CreateVirtualCollectionPage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/CreateVirtualCollectionPage.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:extend>
+	<div wicket:id="wizard"></div>
+</wicket:extend>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/CreateVirtualCollectionPage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/CreateVirtualCollectionPage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/CreateVirtualCollectionPage.java	(revision 1025)
@@ -0,0 +1,76 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import java.security.Principal;
+import java.util.Date;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.authorization.strategies.role.Roles;
+import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeInstantiation;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.ApplicationSession;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.wizard.CreateVirtualCollectionWizard;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@AuthorizeInstantiation(Roles.USER)
+@SuppressWarnings("serial")
+public class CreateVirtualCollectionPage extends BasePage {
+
+    public CreateVirtualCollectionPage() {
+        this(new VirtualCollection(), null);
+    }
+
+    public CreateVirtualCollectionPage(VirtualCollection vc,
+            final Page previousPage) {
+        final CreateVirtualCollectionWizard wizard =
+            new CreateVirtualCollectionWizard("wizard", vc) {
+
+            @Override
+            protected void onCancelWizard() {
+                // proceed to response page
+                if (previousPage != null) {
+                    setResponsePage(previousPage);
+                } else {
+                    setResponsePage(BrowsePrivateCollectionsPage.class);
+                }
+            }
+
+            @Override
+            protected void onFinishWizard(VirtualCollection vc) {
+                try {
+                    VirtualCollectionRegistry vcr =
+                        VirtualCollectionRegistry.instance();
+                    ApplicationSession session =
+                        (ApplicationSession) getSession();
+                    Principal principal = session.getPrincipal();
+                    if (principal == null) {
+                        // XXX: security issue?
+                        throw new WicketRuntimeException("principal == null");
+                        
+                    }
+                    // FIXME: get date from GUI?
+                    if (vc.getId() == null) {
+                        vc.setCreationDate(new Date());
+                        vcr.createVirtualCollection(principal, vc);
+                    } else {
+                        vcr.updateVirtualCollection(principal, vc.getId(), vc);
+                    }
+                } catch (VirtualCollectionRegistryException e) {
+                    // FIXME: handle error
+                    e.printStackTrace();
+                }
+
+                // proceed to response page
+                if (previousPage != null) {
+                    setResponsePage(previousPage);
+                } else {
+                    setResponsePage(BrowsePrivateCollectionsPage.class);
+                }
+            }
+        };
+        add(wizard);
+    }
+
+} // class CreateVirtualCollecionPage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/LoginPage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/LoginPage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/LoginPage.html	(revision 1025)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+  <p style="font-size: 120%; text-align: center;">Login Page</p>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/LoginPage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/LoginPage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/LoginPage.java	(revision 1025)
@@ -0,0 +1,48 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.wicket.RestartResponseAtInterceptPageException;
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.markup.html.WebPage;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.Application;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.ApplicationSession;
+
+public class LoginPage extends WebPage {
+
+    public LoginPage() {
+        super();
+        setStatelessHint(true);
+        setVersioned(false);
+    }
+
+    @Override
+    protected void onBeforeRender() {
+        final HttpServletRequest request =
+            getWebRequestCycle().getWebRequest().getHttpServletRequest();
+        final HttpServletResponse response =
+            getWebRequestCycle().getWebResponse().getHttpServletResponse();
+        if (request.getAuthType() == null) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        } else {
+            final Principal principal = request.getUserPrincipal();
+            ApplicationSession session = (ApplicationSession) getSession();
+            if (session.signIn(principal)) {
+                if (!continueToOriginalDestination()) {
+                    throw new RestartResponseAtInterceptPageException(
+                            Application.get().getHomePage());
+                }
+            } else {
+                throw new RestartResponseException(
+                        Application.get().getApplicationSettings()
+                            .getAccessDeniedPage());
+            }
+        }
+        super.onBeforeRender();
+    }
+
+} // class LoginPage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/Menu.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/Menu.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/Menu.html	(revision 1025)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div id="menu">
+  <ul>
+    <li wicket:id="menuitems"><div wicket:id="menuitem"></div></li>
+  </ul>
+</div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/Menu.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/Menu.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/Menu.java	(revision 1025)
@@ -0,0 +1,33 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+
+@SuppressWarnings("serial")
+public class Menu extends Panel {
+    private final List<MenuItem<? extends WebPage>> menuitems =
+        new ArrayList<MenuItem<? extends WebPage>>();
+    
+    public Menu(String id) {
+        super(id);
+        setRenderBodyOnly(true);
+        add(new ListView<MenuItem<? extends WebPage>>("menuitems", menuitems) {
+            protected void populateItem(
+                    final ListItem<MenuItem<? extends WebPage>> listitem) {
+                final MenuItem<? extends WebPage> menuitem =
+                    listitem.getModelObject();
+                listitem.add(menuitem);
+            }
+        });
+    }
+
+    public void addMenuItem(MenuItem<? extends WebPage> item) {
+        menuitems.add(item);
+    }
+
+} // class Menu
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/MenuItem.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/MenuItem.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/MenuItem.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<a wicket:id="link" href="#"><span wicket:id="title">Title</span></a>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/MenuItem.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/MenuItem.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/MenuItem.java	(revision 1025)
@@ -0,0 +1,67 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import org.apache.wicket.authorization.IAuthorizationStrategy;
+import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeAction;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.util.lang.Classes;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.Application;
+
+@SuppressWarnings("serial")
+public class MenuItem<T extends WebPage> extends Panel {
+    private static final String ENABLE = "ENABLE";
+    private final String pageClassName;
+
+    public MenuItem(final IModel<String> title, final Class<T> pageClass) {
+        super("menuitem");
+        this.pageClassName = pageClass.getName();
+        setRenderBodyOnly(true);
+        final Link<T> link = new BookmarkablePageLink<T>("link", pageClass) {
+            @Override
+            protected void onComponentTag(ComponentTag tag) {
+                super.onComponentTag(tag);
+                if (linksTo(getPage())) {
+                    tag.setName("span");
+                    tag.getAttributes().remove("href");
+                    tag.getAttributes().put("class", "active");
+                }
+            }
+        };
+        link.add(new Label("title", title).setRenderBodyOnly(true));
+        add(link);
+    }
+
+    @Override
+    protected void onBeforeRender() {
+        final IAuthorizationStrategy strategy =
+            getApplication().getSecuritySettings()
+                .getAuthorizationStrategy();
+        final Class<T> pageClass = getPageClass();
+        if (!strategy.isInstantiationAuthorized(pageClass)) {
+            boolean visible = true;
+            AuthorizeAction action =
+                pageClass.getAnnotation(AuthorizeAction.class);
+            if ((action != null) && ENABLE.equalsIgnoreCase(action.action())) {
+                final Application app = (Application) getApplication();
+                if (app.hasAnyRole(action.deny())) {
+                    visible = false;
+                } else {
+                    visible = app.hasAnyRole(action.roles());
+                }
+            }
+            setVisible(visible);
+        }
+        super.onBeforeRender();
+    }
+
+    private Class<T> getPageClass() {
+        return Classes.resolveClass(pageClassName);
+    }
+
+} // class MenuItem
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/VirtualCollectionDetailsPage.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/VirtualCollectionDetailsPage.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/VirtualCollectionDetailsPage.html	(revision 1025)
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:extend>
+  <div wicket:id="generalBorder">
+    <div class="property">
+      <label>Name</label>
+      <div wicket:id="name"></div>
+    </div>
+    <div class="property">
+       <label>Type</label>
+       <div wicket:id="type"></div>
+    </div>
+    <div class="property">
+      <label>Creation Date</label>
+      <div wicket:id="creationDate"></div>
+    </div>
+    <wicket:enclosure child="description">
+      <div class="property">
+        <label>Description</label>
+        <div wicket:id="description"></div>
+      </div>
+    </wicket:enclosure>
+    <wicket:enclosure child="purpose">
+      <div class="property">
+        <label>Purpose</label>
+        <div wicket:id="purpose"></div>
+      </div>
+    </wicket:enclosure>
+    <wicket:enclosure child="reproducibility">
+      <div class="property">
+        <label>Reproducibility</label>
+        <div wicket:id="reproducibility"></div>
+      </div>
+    </wicket:enclosure>
+    <wicket:enclosure child="reproducibilityNotice">
+      <div class="property">
+        <label>Reproducibility Notice</label>
+        <div wicket:id="reproducibilityNotice"></div>
+      </div>
+    </wicket:enclosure>
+    <wicket:enclosure child="keywords">
+      <div class="property">
+        <label>Keywords</label>
+        <div>
+          <ul class="keywords"><li wicket:id="keywords"><span wicket:id="keyword"></span></li></ul>
+        </div>
+      </div>
+    </wicket:enclosure>
+  </div>
+
+  <div wicket:id="creatorsBorder">
+    <ul class="creators">
+      <li wicket:id="creators">
+        <div class="property">
+          <label>Person</label>
+          <div wicket:id="person"></div>
+        </div>
+        <wicket:enclosure child="address">
+          <div class="property">
+            <label>Address</label>
+            <div wicket:id="address"></div>
+          </div>
+        </wicket:enclosure>
+        <wicket:enclosure child="email">
+          <div class="property">
+            <label>Email</label>
+            <div wicket:id="email"></div>
+          </div>
+        </wicket:enclosure>
+        <wicket:enclosure child="organisation">
+          <div class="property">
+            <label>Organisation</label>
+            <div wicket:id="organisation"></div>
+          </div>
+        </wicket:enclosure>
+        <wicket:enclosure child="telephone">
+          <div class="property">
+            <label>Telephone</label>
+            <div wicket:id="telephone"></div>
+          </div>
+        </wicket:enclosure>
+        <wicket:enclosure child="website">
+          <div class="property">
+            <label>Website</label>
+            <div><a wicket:id="website"></a></div>
+          </div>
+        </wicket:enclosure>
+        <wicket:enclosure child="role">
+          <div class="property">
+            <label>Role</label>
+            <div wicket:id="role"></div>
+          </div>
+        </wicket:enclosure>
+      </li>
+    </ul>
+  </div>
+
+  <div wicket:id="resourcesBorder">
+    <table wicket:id="resourcesTable"></table>
+  </div>
+
+  <div wicket:id="generatedByBorder">
+    <div class="property">
+      <label>Description</label>
+      <div wicket:id="generatedBy.description"></div>
+    </div>
+    <wicket:enclosure child="generatedBy.uri">
+      <div class="property">
+        <label>URI</label>
+        <div wicket:id="generatedBy.uri"></div>
+      </div>
+    </wicket:enclosure>
+    <wicket:enclosure child="generatedBy.query.profile">
+      <div class="property">
+        <label>Profile</label>
+        <div wicket:id="generatedBy.query.profile"></div>
+      </div>
+    </wicket:enclosure>
+    <wicket:enclosure child="generatedBy.query.value">
+      <div class="property">
+        <label>Value</label>
+        <div wicket:id="generatedBy.query.value"></div>
+      </div>
+    </wicket:enclosure>
+  </div>
+
+  <div class="collectionDetails buttons">
+    <a wicket:id="back" href="#">[return to previous page]</a>
+  </div>
+</wicket:extend>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/VirtualCollectionDetailsPage.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/VirtualCollectionDetailsPage.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/pages/VirtualCollectionDetailsPage.java	(revision 1025)
@@ -0,0 +1,276 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.pages;
+
+import java.sql.Date;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.lang.time.FastDateFormat;
+import org.apache.wicket.Application;
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.behavior.AbstractBehavior;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.basic.MultiLineLabel;
+import org.apache.wicket.markup.html.border.Border;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.link.PopupSettings;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.list.OddEvenListItem;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.ComponentPropertyModel;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.util.convert.IConverter;
+import org.apache.wicket.util.string.Strings;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.border.AjaxToggleBorder;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection.Type;
+
+
+@SuppressWarnings("serial")
+public class VirtualCollectionDetailsPage extends BasePage {
+    private static final IConverter convEnum = new IConverter() {
+        @Override
+        public String convertToString(Object o, Locale locale) {
+            final Enum<?> enumObj = (Enum<?>) o;
+            final String key =
+                enumObj.getDeclaringClass().getSimpleName() + "." +
+                enumObj.name();
+            final String value = Application.get().getResourceSettings()
+                    .getLocalizer().getString(key, null);
+            return Strings.escapeMarkup(value).toString();
+        }
+
+        @Override
+        public Object convertToObject(String s, Locale locale) {
+            return null;
+        }
+    };
+    private static final IConverter convDate = new IConverter() {
+        @Override
+        public String convertToString(Object o, Locale locale) {
+            return FastDateFormat.getInstance("yyyy-MM-dd")
+                .format((Date) o);
+        }
+
+        @Override
+        public Object convertToObject(String s, Locale locale) {
+            return null;
+        }
+    };
+    private static final String CSS_CLASS = "collectionDetails";
+
+    /*
+     * Actually, we really want the behavior to hide the component on
+     * the beforeRender() call, but we always get an exception from Wicket
+     * that we are not supposed to change the page hierarchy anymore. This
+     * class is a hack to avoid this exception.
+     */
+    private final class HideIfEmptyBehavior extends AbstractBehavior {
+        private final List<Component> components = new LinkedList<Component>();
+        
+        @Override
+        public void bind(Component component) {
+            super.bind(component);
+            components.add(component);
+        }
+
+        public void hideEmptyComponents() {
+            for (Component component : components) {
+              Object obj = component.getDefaultModelObject();
+              if (obj == null) {
+                  component.setVisible(false);
+              } else {
+                  if (obj instanceof Collection<?>) {
+                      if (((Collection<?>) obj).isEmpty()) {
+                          component.setVisible(false);
+                      }
+                  }
+              }
+            }
+        }
+
+        @Override
+        public void cleanup() {
+            super.cleanup();
+            components.clear();
+        }
+    } // class VirtualCollectionDetailsPage.HideIfEmptyBehavior
+    
+    private static class CustomLabel extends Label {
+
+        public CustomLabel(String id) {
+            super(id);
+        }
+
+        @Override
+        public IConverter getConverter(Class<?> type) {
+            if (VirtualCollection.Type.class.isAssignableFrom(type) ||
+                VirtualCollection.Purpose.class.isAssignableFrom(type) ||
+                VirtualCollection.Reproducibility.class.isAssignableFrom(type)) {
+                return convEnum;
+            }
+            if (Date.class.isAssignableFrom(type)) {
+                return convDate;
+            }
+            return super.getConverter(type);
+        }
+    } // class VirtualCollectionDetailsPage.TypeLabel
+
+    
+    private final HideIfEmptyBehavior hideIfEmpty = new HideIfEmptyBehavior();
+
+    
+    public VirtualCollectionDetailsPage(final IModel<VirtualCollection> model,
+            final Page previousPage) {
+        super(new CompoundPropertyModel<VirtualCollection>(model.getObject()));
+        if (previousPage == null) {
+            throw new IllegalArgumentException("previousPage == null");
+        }
+        final Link<Void> backLink = new Link<Void>("back") {
+            @Override
+            public void onClick() {
+                setResponsePage(previousPage);
+            }
+        };
+        add(backLink);
+
+        final Border general = new AjaxToggleBorder("generalBorder",
+                new Model<String>("General"), CSS_CLASS);
+        add(general);
+        general.add(new Label("name"));
+        general.add(new CustomLabel("type"));
+        general.add(new CustomLabel("creationDate"));
+        general.add(new MultiLineLabel("description").add(hideIfEmpty));
+        general.add(new CustomLabel("purpose").add(hideIfEmpty));
+        general.add(new CustomLabel("reproducibility").add(hideIfEmpty));
+        general.add(new Label("reproducibilityNotice").add(hideIfEmpty));
+        final ListView<String> keywords = new ListView<String>("keywords") {
+            @Override
+            protected void populateItem(ListItem<String> item) {
+                item.add(new Label("keyword", item.getModelObject()));
+            }
+        };
+        keywords.add(hideIfEmpty);
+        general.add(keywords);
+
+        final Border creators = new AjaxToggleBorder("creatorsBorder",
+                new Model<String>("Creators"), CSS_CLASS);
+        add(creators);
+        creators.add(new ListView<Creator>("creators") {
+            @Override
+            protected void populateItem(ListItem<Creator> item) {
+                item.add(new Label("person"));
+                item.add(new MultiLineLabel("address").add(hideIfEmpty));
+                item.add(new Label("organisation").add(hideIfEmpty));
+                item.add(new Label("email").add(hideIfEmpty));
+                item.add(new Label("telephone").add(hideIfEmpty));
+                final IModel<String> siteModel =
+                    new ComponentPropertyModel<String>("website");
+                item.add(new ExternalLink("website", siteModel, siteModel)
+                        .setPopupSettings(new PopupSettings())
+                        .add(hideIfEmpty));
+                item.add(new Label("role").add(hideIfEmpty));
+            }
+
+            @Override
+            protected IModel<Creator> getListItemModel(
+                    IModel<? extends List<Creator>> listViewModel, int index) {
+                final List<Creator> creators = listViewModel.getObject();
+                return new CompoundPropertyModel<Creator>(creators.get(index));
+            }
+
+            @Override
+            protected ListItem<Creator> newItem(int index) {
+                final IModel<Creator> model =
+                    getListItemModel(getModel(), index);
+                return new OddEvenListItem<Creator>(index, model) {
+                    @Override
+                    protected void onComponentTag(ComponentTag tag) {
+                        super.onComponentTag(tag);
+                        if (getIndex() == 0) {
+                            tag.append("class", "first", " ");
+                        }
+                    }
+                };
+            }
+        });
+
+        final Border resources = new AjaxToggleBorder("resourcesBorder",
+                new Model<String>("Resources"), CSS_CLASS + " resources");
+        add(resources);
+        
+        final VirtualCollection vc = model.getObject();
+        @SuppressWarnings("rawtypes")
+        final IColumn[] cols = new IColumn[2];
+        cols[0] = new PropertyColumn<Resource>(
+                new Model<String>("Type"), "type") {
+            @Override
+            public void populateItem(Item<ICellPopulator<Resource>> item,
+                    String componentId, IModel<Resource> model) {
+                final Resource.Type type = model.getObject().getType();
+                item.add(new Label(componentId,
+                        convEnum.convertToString(type, getLocale())));
+            }
+        };
+        cols[1] = new PropertyColumn<Resource>(
+                new Model<String>("Reference"), "ref");
+
+        @SuppressWarnings("unchecked")
+        final DataTable<Resource> resourcesTable =
+            new AjaxFallbackDefaultDataTable<Resource>("resourcesTable",
+                    cols,
+                    new SortableDataProvider<Resource>() {
+                        @Override
+                        public Iterator<? extends Resource>
+                            iterator(int first, int count) {
+                            return vc.getResources().listIterator(first);
+                        }
+
+                        @Override
+                        public IModel<Resource> model(Resource resource) {
+                            return new Model<Resource>(resource);
+                        }
+
+                        @Override
+                        public int size() {
+                            return vc.getResources().size();
+                        }
+                    },
+                    64);
+        resources.add(resourcesTable);
+        resources.setVisible(model.getObject().getType() == Type.EXTENSIONAL);
+
+        final Border generated = new AjaxToggleBorder("generatedByBorder",
+                new Model<String>("Intensional Collection Query"), CSS_CLASS);
+        add(generated);
+        generated.add(new Label("generatedBy.description"));
+        generated.add(new Label("generatedBy.uri").add(hideIfEmpty));
+        generated.add(new Label("generatedBy.query.profile").add(hideIfEmpty));
+        generated.add(new Label("generatedBy.query.value").add(hideIfEmpty));
+        generated.setVisible(model.getObject().getType() == Type.INTENSIONAL);
+    }
+
+    @Override
+    protected void onBeforeRender() {
+        super.onBeforeRender();
+        hideIfEmpty.hideEmptyComponents();
+    }
+
+} // class VirtualCollectionDetailsPage
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnActions.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnActions.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnActions.java	(revision 1025)
@@ -0,0 +1,30 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.HeaderlessColumn;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+final class ColumnActions extends HeaderlessColumn<VirtualCollection> {
+    private final VirtualCollectionTable table;
+
+    public ColumnActions(VirtualCollectionTable table) {
+        super();
+        this.table = table;
+    }
+
+    @Override
+    public void populateItem(Item<ICellPopulator<VirtualCollection>> item,
+            String componentId, IModel<VirtualCollection> model) {
+        item.add(table.createActionColumn(componentId, model));
+    }
+
+    @Override
+    public String getCssClass() {
+        return "action";
+    }
+
+} // class ColumnActions
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnCreated.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnCreated.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnCreated.java	(revision 1025)
@@ -0,0 +1,34 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import org.apache.commons.lang.time.FastDateFormat;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+final class ColumnCreated extends AbstractColumn<VirtualCollection> {
+    private static final FastDateFormat df =
+        FastDateFormat.getInstance("yyyy-MM-dd");
+
+    ColumnCreated(VirtualCollectionTable table) {
+        super(new ResourceModel("column.created", "Created"), "created");
+    }
+
+    @Override
+    public void populateItem(Item<ICellPopulator<VirtualCollection>> item,
+            String componentId, IModel<VirtualCollection> model) {
+        item.add(new Label(componentId,
+                df.format(model.getObject().getCreationDate())));
+    }
+
+    @Override
+    public String getCssClass() {
+        return "created";
+    }
+
+} // class ColumnCreated
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName$ItemCell.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName$ItemCell.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName$ItemCell.html	(revision 1025)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <div wicket:id="nameColumn">
+    <div wicket:id="name" class="name"></div>
+    <div wicket:id="details" class="details">
+      <div wicket:id="desc" class="desc"></div>
+      <div class="actions">
+        <div wicket:id="actionsPanel"></div>
+      </div>
+    </div>
+  </div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName.java	(revision 1025)
@@ -0,0 +1,95 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ResourceReference;
+import org.apache.wicket.behavior.AbstractBehavior;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.basic.MultiLineLabel;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+import org.odlabs.wiquery.core.commons.IWiQueryPlugin;
+import org.odlabs.wiquery.core.commons.WiQueryResourceManager;
+import org.odlabs.wiquery.core.javascript.JsStatement;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+final class ColumnName extends AbstractColumn<VirtualCollection> {
+    private static final ResourceReference JAVASCRIPT_RESOURCE = 
+        new ResourceReference(ColumnName.class, "ColumnName.js");
+    private final VirtualCollectionTable table;
+    
+    private final class ItemCell extends Panel implements IWiQueryPlugin {
+        private final WebMarkupContainer nameColumn;
+        
+        public ItemCell(String id, IModel<VirtualCollection> model) {
+            super(id);
+            setRenderBodyOnly(true);
+            
+            nameColumn = new WebMarkupContainer("nameColumn");
+            nameColumn.setOutputMarkupId(true);
+            final VirtualCollection vc = model.getObject();
+            nameColumn.add(new Label("name", vc.getName()));
+
+            final WebMarkupContainer details =
+                new WebMarkupContainer("details");
+            final String desc = vc.getDescription();
+            final MultiLineLabel descLabel = new MultiLineLabel("desc", desc);
+            if (desc == null) {
+                descLabel.setVisible(false);
+            }
+            details.add(descLabel);
+            final Panel actionsPanel =
+                table.createActionPanel("actionsPanel", model);
+            details.add(actionsPanel);
+            details.add(new AbstractBehavior() {
+
+                @Override
+                public void bind(Component component) {
+                    component.setVisible(actionsPanel.isVisible());
+                }
+            });
+
+            // move to css?
+            details.add(new AttributeAppender("style",
+                    new Model<String>("display:none"), ";"));
+            nameColumn.add(details);
+            add(nameColumn);
+        }
+        
+        @Override
+        public void contribute(WiQueryResourceManager manager) {
+            manager.addJavaScriptResource(JAVASCRIPT_RESOURCE);
+        }
+
+        @Override
+        public JsStatement statement() {
+            return new JsStatement().$(nameColumn).append(".detailsToggle()");
+        }
+
+    } // class ColumnName.ItemCell
+
+    ColumnName(VirtualCollectionTable table) {
+        super(new ResourceModel("column.name", "Name"), "name");
+        this.table = table;
+    }
+
+    @Override
+    public void populateItem(Item<ICellPopulator<VirtualCollection>> item,
+            String componentId, IModel<VirtualCollection> model) {
+        item.add(new ItemCell(componentId, model));
+    }
+
+    @Override
+    public String getCssClass() {
+        return "name";
+    }
+
+} // class ColumnName
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName.js
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName.js	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnName.js	(revision 1025)
@@ -0,0 +1,21 @@
+(function($) {
+	$.extend($.fn, {
+		detailsToggle : function() {
+			var o = {
+				speed: 250
+			};
+			var name = $(this).children('div.name');
+			var details = $(this).children('div.details');
+			if (details.length > 0) {
+				name.bind('click.detailsToggle', function() {
+					details.slideToggle(o.speed, function() {
+						name.toggleClass('detailsShown');
+					});
+				});
+			} else {
+				name.addClass('noDetails');
+			}
+			return $(this);
+		}
+	});
+})(jQuery);
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnState.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnState.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnState.java	(revision 1025)
@@ -0,0 +1,36 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+final class ColumnState extends AbstractColumn<VirtualCollection> {
+    private final EnumChoiceRenderer<VirtualCollection.State> renderer;
+
+    ColumnState(VirtualCollectionTable table) {
+        super(new ResourceModel("column.state", "State"), "state");
+        this.renderer = new EnumChoiceRenderer<VirtualCollection.State>(table);
+    }
+
+    @Override
+    public void populateItem(
+            Item<ICellPopulator<VirtualCollection>> item,
+            String componentId, IModel<VirtualCollection> model) {
+        final VirtualCollection.State state = model.getObject().getState();
+        final String label = renderer.getDisplayValue(state).toString();
+        item.add(new Label(componentId, label));
+    }
+
+    @Override
+    public String getCssClass() {
+        return "state";
+    }
+
+} // class ColumnState
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnType.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnType.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/ColumnType.java	(revision 1025)
@@ -0,0 +1,35 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+final class ColumnType extends AbstractColumn<VirtualCollection> {
+    private final EnumChoiceRenderer<VirtualCollection.Type> renderer;
+
+    ColumnType(VirtualCollectionTable table) {
+        super(new ResourceModel("column.type", "Type"), "type");
+        this.renderer = new EnumChoiceRenderer<VirtualCollection.Type>(table);
+    }
+
+    @Override
+    public void populateItem(Item<ICellPopulator<VirtualCollection>> item,
+            String componentId, IModel<VirtualCollection> model) {
+        final VirtualCollection.Type type = model.getObject().getType();
+        final String label = renderer.getDisplayValue(type).toString();
+        item.add(new Label(componentId, label));
+    }
+
+    @Override
+    public String getCssClass() {
+        return "type";
+    }
+
+} // class ColumnType
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterForm.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterForm.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterForm.html	(revision 1025)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <form wicket:id="form" class="filterForm wizard">
+   <fieldset>
+    <div class="optional">
+      <label>Name</label>
+      <select wicket:id="nameMode"></select>
+      <input wicket:id="name" type="text" class="name"
+             size="64" maxlength="255"/>
+    </div>
+    <div class="optional">
+      <label>Description</label>
+      <select wicket:id="descriptionMode"></select>
+      <input wicket:id="description" type="text" class="desc"
+             size="64" maxlength="255"/>
+    </div>
+    <div wicket:id="state" class="optional">
+      <label>State</label>
+      <select wicket:id="state"></select>
+    </div>
+    <div class="optional">
+      <label>Type</label>
+      <select wicket:id="type"></select>
+    </div>
+    <div class="optional">
+      <label>Created</label>
+      <select wicket:id="createdRelation"></select>
+      <input wicket:id="created" type="text" class="created"
+             size="11" maxlength="11"/>
+    </div>
+    <input wicket:id="filter" type="submit" class="filter" value="filter" />
+    <input wicket:id="clear" type="submit" class="clear" value="clear" />
+    </fieldset>
+    <div wicket:id="feedback" class="collectionsTableFeedback"></div>
+  </form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterForm.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterForm.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterForm.java	(revision 1025)
@@ -0,0 +1,120 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.extensions.markup.html.form.DateTextField;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilterStateLocator;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.validation.validator.StringValidator;
+
+import eu.clarin.cmdi.virtualcollectionregistry.QueryOptions;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+public class FilterForm extends Panel {
+    private static final List<FilterState.SearchMode> MODE_VALUES =
+        Arrays.asList(FilterState.SearchMode.values());
+    private static final List<VirtualCollection.State> STATE_VALUES =
+        Arrays.asList(VirtualCollection.State.PUBLIC,
+                VirtualCollection.State.PRIVATE,
+                VirtualCollection.State.DELETED,
+                VirtualCollection.State.PUBLIC_PENDING);
+    private static final List<VirtualCollection.Type> TYPE_VALUES =
+        Arrays.asList(VirtualCollection.Type.values());
+    private static final List<QueryOptions.Relation> CREATED_RELATIONS =
+        Arrays.asList(QueryOptions.Relation.LT,
+                QueryOptions.Relation.LE,
+                QueryOptions.Relation.EQ,
+                QueryOptions.Relation.GE,
+                QueryOptions.Relation.GT);
+    
+    public FilterForm(String id, IFilterStateLocator<FilterState> locator,
+            final DataTable<VirtualCollection> table, boolean privateMode) {
+        super(id);
+        setRenderBodyOnly(true);
+
+        final IModel<FilterState> model =
+            new CompoundPropertyModel<FilterState>(locator.getFilterState());
+        final Form<FilterState> form = new Form<FilterState>("form", model);
+        form.setOutputMarkupId(true);
+
+        final EnumChoiceRenderer<FilterState.SearchMode> searchModeRenderer =
+            new EnumChoiceRenderer<FilterState.SearchMode>(this);
+        form.add(new DropDownChoice<FilterState.SearchMode>("nameMode",
+                MODE_VALUES, searchModeRenderer));
+        form.add(new TextField<String>("name")
+                .add(new StringValidator.MaximumLengthValidator(255)));
+        form.add(new DropDownChoice<FilterState.SearchMode>("descriptionMode",
+                MODE_VALUES, searchModeRenderer));
+        form.add(new TextField<String>("description")
+                .add(new StringValidator.MaximumLengthValidator(255)));
+        final WebMarkupContainer state = new WebMarkupContainer("state");
+        state.add(new DropDownChoice<VirtualCollection.State>("state",
+                STATE_VALUES,
+                new EnumChoiceRenderer<VirtualCollection.State>(this)));
+        state.setVisible(privateMode);
+        form.add(state);
+        form.add(new DropDownChoice<VirtualCollection.Type>("type",
+                TYPE_VALUES,
+                new EnumChoiceRenderer<VirtualCollection.Type>(this)));
+        final DropDownChoice<QueryOptions.Relation> createdRelationChoice =
+            new DropDownChoice<QueryOptions.Relation>("createdRelation",
+                    CREATED_RELATIONS,
+                    new EnumChoiceRenderer<QueryOptions.Relation>(this));
+        createdRelationChoice.setEscapeModelStrings(false);
+        form.add(createdRelationChoice);
+        form.add(new DateTextField("created", "yyyy-MM-dd"));
+
+        final AjaxButton goButton = new AjaxButton("filter",
+                new ResourceModel("button.filter")) {
+            @Override
+            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                target.addComponent(form);
+                target.addComponent(table);
+            }
+
+            @Override
+            protected void onError(AjaxRequestTarget target, Form<?> form) {
+                super.onError(target, form);
+                target.addComponent(form);
+            }
+        };
+        form.add(goButton);
+        final AjaxButton clearButton = new AjaxButton("clear",
+                new ResourceModel("button.clear")) {
+            @Override
+            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                @SuppressWarnings("unchecked")
+                final FilterState state =
+                    ((Form<FilterState>) form).getModelObject();
+                state.clear();
+                target.addComponent(form);
+                target.addComponent(table);
+            }
+
+            @Override
+            protected void onError(AjaxRequestTarget target, Form<?> form) {
+                super.onError(target, form);
+                target.addComponent(form);
+            }
+        };
+        form.add(clearButton);
+
+        form.add(new FeedbackPanel("feedback"));
+        add(form);
+    }
+
+} // class FilterForm
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterState.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterState.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/FilterState.java	(revision 1025)
@@ -0,0 +1,154 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import eu.clarin.cmdi.virtualcollectionregistry.QueryOptions;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+final class FilterState implements Serializable {
+    public enum SearchMode {
+        CONTAINS,
+        STARTS_WITH,
+        ENDS_WITH
+    } // enum FilterState.SearchMode
+    private SearchMode nameMode;
+    private String name;
+    private SearchMode descriptionMode;
+    private String description;
+    private VirtualCollection.Type type;
+    private VirtualCollection.State state;
+    private QueryOptions.Relation createdRelation;
+    private Date created;
+
+    public FilterState() {
+        clear();
+    }
+
+    public SearchMode getNameMode() {
+        return nameMode;
+    }
+    
+    public void setNameMode(SearchMode nameMode) {
+        if (nameMode == null) {
+            throw new IllegalArgumentException("nameMode == null");
+        }
+        this.nameMode = nameMode;
+    }
+    
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean hasName() {
+        return name != null;
+    }
+
+    public String getNameWithWildcard() {
+        return addWildcards(name, nameMode);
+    }
+    
+    public SearchMode getDescriptionMode() {
+        return descriptionMode;
+    }
+
+    public void setDescriptionMode(SearchMode descriptionMode) {
+        if (descriptionMode == null) {
+            throw new IllegalArgumentException("descriptionMode == null");
+        }
+        this.descriptionMode = descriptionMode;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public VirtualCollection.Type getType() {
+        return type;
+    }
+
+    public void setType(VirtualCollection.Type type) {
+        this.type = type;
+    }
+
+    public boolean hasType() {
+        return type != null;
+    }
+
+    public VirtualCollection.State getState() {
+        return state;
+    }
+
+    public void setType(VirtualCollection.State state) {
+        this.state = state;
+    }
+
+    public boolean hasState() {
+        return state != null;
+    }
+
+    public boolean hasDescription() {
+        return description != null;
+    }
+
+    public String getDescriptionWithWildcard() {
+        return addWildcards(description, descriptionMode);
+    }
+
+    public QueryOptions.Relation getCreatedRelation() {
+        return createdRelation;
+    }
+    
+    public void setCreatedRelation(QueryOptions.Relation createdRelation) {
+        if (createdRelation == null) {
+            throw new IllegalArgumentException("createdRelation == null");
+        }
+        this.createdRelation = createdRelation;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+    
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public boolean hasCreated() {
+        return created != null;
+    }
+
+    public void clear() {
+        nameMode = SearchMode.CONTAINS;
+        name = null;
+        descriptionMode = SearchMode.CONTAINS;
+        description = null;
+        type = null;
+        state = null;
+        createdRelation = QueryOptions.Relation.EQ;
+        created = null;
+    }
+
+    private static String addWildcards(String s, SearchMode mode) {
+        s = s.replaceAll("\\*+", "*");
+        StringBuilder sb = new StringBuilder();
+        if ((mode != SearchMode.STARTS_WITH) && !s.startsWith("*")) {
+            sb.append('*');
+        }
+        sb.append(s);
+        if ((mode != SearchMode.ENDS_WITH) && !s.endsWith("*")) {
+            sb.append('*');
+        }
+        return sb.toString();
+    }
+
+} // class FilterState
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/Provider.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/Provider.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/Provider.java	(revision 1025)
@@ -0,0 +1,135 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilterStateLocator;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.model.IModel;
+
+import eu.clarin.cmdi.virtualcollectionregistry.QueryOptions;
+import eu.clarin.cmdi.virtualcollectionregistry.QueryOptions.Property;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.ApplicationSession;
+import eu.clarin.cmdi.virtualcollectionregistry.gui.DetachableVirtualCollectionModel;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+class Provider extends
+        SortableDataProvider<VirtualCollection> implements
+        IFilterStateLocator<FilterState> {
+    private final boolean privateMode;
+    private FilterState filterstate = new FilterState();
+
+    public Provider(boolean privateMode) {
+        this.privateMode = privateMode;
+        setSort("created", false);
+    }
+
+    @Override
+    public FilterState getFilterState() {
+        return filterstate;
+    }
+
+    @Override
+    public void setFilterState(FilterState state) {
+        this.filterstate = state;
+    }
+
+    @Override
+    public IModel<VirtualCollection> model(VirtualCollection vc) {
+        return new DetachableVirtualCollectionModel(vc);
+    }
+
+    @Override
+    public int size() {
+        try {
+            final VirtualCollectionRegistry vcr =
+                VirtualCollectionRegistry.instance();
+            return vcr.getVirtualCollectionCount(getFilter());
+        } catch (VirtualCollectionRegistryException e) {
+            throw new WicketRuntimeException(e);
+        }
+    }
+
+    @Override
+    public Iterator<? extends VirtualCollection> iterator(int first,
+            int count) {
+        try {
+            final VirtualCollectionRegistry vcr =
+                VirtualCollectionRegistry.instance();
+            final List<VirtualCollection> results =
+                vcr.getVirtualCollections(first, count, getFilter());
+            return results.iterator();
+        } catch (VirtualCollectionRegistryException e) {
+            throw new WicketRuntimeException(e);
+        }
+    }
+
+    private QueryOptions getFilter() {
+        QueryOptions options = new QueryOptions();
+
+        QueryOptions.Filter filter = options.and();
+        if (privateMode) {
+            ApplicationSession session = ApplicationSession.get();
+            filter.add(QueryOptions.Property.VC_OWNER,
+                       QueryOptions.Relation.EQ,
+                       session.getUser());
+        } else {
+            filter.add(QueryOptions.Property.VC_STATE,
+                       QueryOptions.Relation.EQ,
+                       VirtualCollection.State.PUBLIC);
+        }
+        if (filterstate.hasName()) {
+            filter.add(QueryOptions.Property.VC_NAME,
+                       QueryOptions.Relation.EQ,
+                       filterstate.getNameWithWildcard());
+        }
+        if (filterstate.hasType()) {
+            filter.add(QueryOptions.Property.VC_TYPE,
+                       QueryOptions.Relation.EQ,
+                       filterstate.getType());
+        }
+        if (filterstate.hasState()) {
+            filter.add(QueryOptions.Property.VC_STATE,
+                       QueryOptions.Relation.EQ,
+                       filterstate.getState());
+        }
+        if (filterstate.hasDescription()) {
+            filter.add(QueryOptions.Property.VC_DESCRIPTION,
+                       QueryOptions.Relation.EQ,
+                       filterstate.getDescriptionWithWildcard());
+        }
+        if (filterstate.hasCreated()) {
+            filter.add(QueryOptions.Property.VC_CREATION_DATE,
+                       filterstate.getCreatedRelation(),
+                       filterstate.getCreated());
+        }
+        options.setFilter(filter);
+
+        final SortParam s = getSort();
+        if (s != null) {
+            final String p = s.getProperty();
+            Property property = null;
+            if ("name".equals(p)) {
+                property = Property.VC_NAME;
+            } else if ("type".equals(p)) {
+                property = Property.VC_TYPE;
+            } else if ("state".equals(p)) {
+                property = Property.VC_STATE;
+            } else if ("description".equals(p)) {
+                property = Property.VC_DESCRIPTION;
+            } else if ("created".equals(p)) {
+                property = Property.VC_CREATION_DATE;
+            }
+            if (property != null) {
+                options.addSortProperty(property, s.isAscending());
+            }
+        }
+        return options;
+    }
+
+} // class VirtualCollectionProvider
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.html	(revision 1025)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <div wicket:id="border">
+    <div wicket:id="filterForm"></div>
+  </div>
+  <table wicket:id="table" class="collectionsTable"></table>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.java	(revision 1025)
@@ -0,0 +1,67 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.table;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxNavigationToolbar;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.border.AjaxToggleBorder;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+public abstract class VirtualCollectionTable extends Panel {
+
+    public VirtualCollectionTable(String id, final boolean privateMode) {
+        super(id);
+        setOutputMarkupId(true);
+
+        // setup table provider
+        List<IColumn<VirtualCollection>> columns =
+            new ArrayList<IColumn<VirtualCollection>>();
+        columns.add(new ColumnName(this));
+        if (privateMode) {
+            columns.add(new ColumnState(this));
+        }
+        columns.add(new ColumnType(this));
+        columns.add(new ColumnCreated(this));
+        columns.add(new ColumnActions(this));
+        Provider provider = new Provider(privateMode);
+
+        // setup table
+        final DataTable<VirtualCollection> table =
+            new AjaxFallbackDefaultDataTable<VirtualCollection>("table",
+                columns, provider, 30);
+        table.add(new AttributeAppender("class",
+                new AbstractReadOnlyModel<String>() {
+                    @Override
+                    public String getObject() {
+                        return privateMode ? "private" : "public";
+                    }
+                }, " "));
+        table.addBottomToolbar(new AjaxNavigationToolbar(table));
+
+        // setup filter
+        final AjaxToggleBorder border =
+            new AjaxToggleBorder("border", new Model<String>("Filter"));
+        final FilterForm form =
+            new FilterForm("filterForm", provider, table, privateMode);
+        border.add(form);
+        add(border);
+        add(table);
+    }
+
+    protected abstract Panel createActionColumn(String componentId,
+            IModel<VirtualCollection> model);
+
+    protected abstract Panel createActionPanel(String componentId,
+            IModel<VirtualCollection> model);
+
+} // class VirtualCollectionTable
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.properties
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.properties	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/table/VirtualCollectionTable.properties	(revision 1025)
@@ -0,0 +1,15 @@
+column.name: Name
+column.type: Type
+column.state: State
+column.description: Description
+column.created: Created
+Relation.LT: less-than
+Relation.LE: less-than or equals
+Relation.EQ: equals
+Relation.GE: greater-than or equals
+Relation.GT: greater-than
+SearchMode.CONTAINS: contains
+SearchMode.STARTS_WITH: starts with
+SearchMode.ENDS_WITH: ends with
+button.filter: Filter
+button.clear: Clear
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddKeywordDialog$Content.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddKeywordDialog$Content.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddKeywordDialog$Content.html	(revision 1025)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<form wicket:id="addKeywordForm">
+  <fieldset>
+    <div class="required">
+      <label for="add_keyword_keyword">Keyword</label>
+      <input wicket:id="keyword" id="edit_keyword_keyword" type="text" />
+    </div>
+  </fieldset>
+  <div wicket:id="feedback" class="feedback"></div>
+</form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddKeywordDialog.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddKeywordDialog.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddKeywordDialog.java	(revision 1025)
@@ -0,0 +1,69 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.wizard;
+
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.RequiredTextField;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.validation.validator.StringValidator;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.dialog.ModalEditDialogBase;
+
+@SuppressWarnings("serial")
+public abstract class AddKeywordDialog extends ModalEditDialogBase<String> {
+    private final class Content extends
+            ModalEditDialogBase<String>.ContentPanel {
+        private final Form<String> form;
+        private final FeedbackPanel feedbackPanel;
+        
+        public Content(String id, IModel<String> model) {
+            super(id);
+            form = new Form<String>("addKeywordForm", model);
+            final TextField<String> keywordField =
+                new RequiredTextField<String>("keyword", form.getModel());
+            keywordField.add(new StringValidator.MaximumLengthValidator(255));
+            form.add(keywordField);
+            feedbackPanel = new FeedbackPanel("feedback");
+            form.add(feedbackPanel);
+            add(form);
+        }
+
+        @Override
+        public Form<String> getForm() {
+            return form;
+        }
+
+        @Override
+        public FeedbackPanel getFeedbackPanel() {
+            return feedbackPanel;
+        }
+    } // class EditCreatorDialog.Content
+
+    public AddKeywordDialog(final String id) {
+        super(id, new Model<String>("Add Keyword"));
+        setInitialWidth(400);
+    }
+
+    @Override
+    protected ModalEditDialogBase<String>.ContentPanel
+        createContentPanel(String id, IModel<String> model) {
+        return new Content(id, model);
+    }
+
+    @Override
+    protected final String newObjectInstance() {
+        return new String();
+    }
+    
+    @Override
+    protected final IModel<String> createModel() {
+        return new Model<String>();
+    }
+
+    @Override
+    protected String getCssClass() {
+        return "addKeywordDialog";
+    }
+
+} // class AddKeywordDialog
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog$ButtonBar.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog$ButtonBar.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog$ButtonBar.html	(revision 1025)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <input wicket:id="addButton"
+         type="submit" class="button yesButton" value="Add" />
+  <input wicket:id="cancelButton"
+         type="submit"  class="button noButton" value="Cancel" />
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog$Content.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog$Content.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog$Content.html	(revision 1025)
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<form wicket:id="addResourcesForm">
+<fieldset>
+  <div class="required">
+    <label for="add_resources_type">Type</label>
+    <select wicket:id="type" id="add_resources_type"></select>
+  </div>
+  <div class="required">
+    <label for="add_resources_refs">References</label>
+    <textarea wicket:id="references"
+              id="add_resources_refs" rows="24" cols="30"></textarea>
+  </div>
+</fieldset>
+<div wicket:id="feedback"></div>
+</form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/AddResourcesDialog.java	(revision 1025)
@@ -0,0 +1,185 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.wizard;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.html.form.AbstractTextComponent;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.validation.IValidatable;
+import org.apache.wicket.validation.Validatable;
+import org.apache.wicket.validation.ValidationError;
+import org.apache.wicket.validation.validator.AbstractValidator;
+import org.apache.wicket.validation.validator.UrlValidator;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.dialog.ModalDialogBase;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+
+@SuppressWarnings("serial")
+public abstract class AddResourcesDialog extends ModalDialogBase {
+    private final class Data implements Serializable {
+        private Resource.Type type;
+        private String[] references;
+        
+        public Resource[] getResources() {
+            Resource[] value = null;
+            if ((type != null) && (references != null) &&
+                    (references.length > 0)) {
+                value = new Resource[references.length];
+                int i = 0;
+                for (String ref : references) {
+                    value[i++] = new Resource(type, ref);
+                }
+            }
+            return value;
+        }
+    } // class AddResourcesDialog.Data
+
+    private final class Content extends Panel {
+        private final Form<Data> form;
+        private final FeedbackPanel feedbackPanel;
+
+        public Content(String id) {
+            super(id);
+            add(new AttributeAppender("class",
+                    new Model<String>("editDialog addResourcesDialog"), " "));
+            form = new Form<Data>("addResourcesForm",
+                    new CompoundPropertyModel<Data>(null));
+            final DropDownChoice<Resource.Type> typeChoice =
+                new DropDownChoice<Resource.Type>("type",
+                        Arrays.asList(Resource.Type.values()),
+                        new EnumChoiceRenderer<Resource.Type>(this));
+            typeChoice.setRequired(true);
+            form.add(typeChoice);
+            final AbstractTextComponent<String[]> referencesArea =
+                new AbstractTextComponent<String[]>("references") {
+                    @Override
+                    protected void convertInput() {
+                        final String input = getRawInput();
+                        if (input != null) {
+                            final String[] refs = input.split("[,;\\s]+");
+                            setConvertedInput(refs);
+                        } else {
+                            setConvertedInput(null);
+                        }
+                    }
+                };
+            referencesArea.setRequired(true);
+            referencesArea.add(new AbstractValidator<String[]>() {
+                @Override
+                protected void onValidate(IValidatable<String[]> input) {
+                    String[] refs = input.getValue();
+                    if (refs != null) {
+                        UrlValidator v = new UrlValidator();
+                        for (String ref : refs) {
+                            if (ref.length() > 255) {
+                                ValidationError ve = new ValidationError();
+                                ve.setMessage("'" + ref +
+                                        "' is larger than 255 characters");
+                                input.error(ve);
+                                continue;
+                            }
+                            Validatable<String> w =
+                                new Validatable<String>(ref);
+                            v.validate(w);
+                            if (!w.isValid()) {
+                                ValidationError ve = new ValidationError();
+                                ve.setMessage("'" + ref +
+                                        "' is not valid uri");
+                                input.error(ve);
+                            }
+                        }
+                    }
+                }
+            });
+            form.add(referencesArea);
+            feedbackPanel = new FeedbackPanel("feedback");
+            feedbackPanel.setOutputMarkupId(true);
+            form.add(feedbackPanel);
+            add(form);
+        }
+
+        public Form<Data> getForm() {
+            return form;
+        }
+        
+        public FeedbackPanel getFeedbackPanel() {
+            return feedbackPanel;
+        }
+    } // class AddResourcesDialog.Content
+
+    private final class ButtonBar extends Panel {
+        public ButtonBar(String id, Form<?> form) {
+            super(id);
+            final AjaxButton addButton = new AjaxButton("addButton",
+                    new Model<String>("Add"), form) {
+                @Override
+                protected void onError(AjaxRequestTarget target, Form<?> form) {
+                    target.addComponent(contentPanel.getFeedbackPanel());
+                    super.onError(target, form);
+                }
+
+                @Override
+                protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                    AddResourcesDialog.this.close(target);
+                    final Data data = (Data) form.getModelObject();
+                    final Resource[] resources = data.getResources();
+                    if (resources != null) {
+                        AddResourcesDialog.this.onSubmit(target, resources);
+                    }
+                    form.setModelObject(null);
+                }
+            };
+            add(addButton);
+            final AjaxButton cancelButton =
+                new AjaxButton("cancelButton",
+                        new Model<String>("Cancel"), form) {
+                @Override
+                protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                    AddResourcesDialog.this.close(target);
+                    form.setModelObject(null);
+                }
+            };
+            cancelButton.setDefaultFormProcessing(false);
+            add(cancelButton);
+        }
+    } // class EditCreatorDialog.ButtonBar
+
+    private Content contentPanel;
+
+    public AddResourcesDialog(final String id) {
+        super(id, new Model<String>("Add Multiple Resources"));
+        setOutputMarkupId(true);
+        setInitialWidth(600);
+    }
+
+    @Override
+    protected Panel createContent(String id) {
+        contentPanel = new Content(id);
+        contentPanel.getForm().removePersistentFormComponentValues(true);
+        return contentPanel;
+    }
+
+    @Override
+    protected Panel createButtonBar(String id) {
+        return new ButtonBar(id, contentPanel.getForm());
+    }
+
+    @Override
+    public void show(AjaxRequestTarget target) {
+        contentPanel.getForm().setModelObject(new Data());
+        super.show(target);
+    }
+
+    public abstract void onSubmit(AjaxRequestTarget target,
+            Resource[] resources);
+
+} // AddResourcesDialog
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$CreatorsStep$ActionsPanel.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$CreatorsStep$ActionsPanel.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$CreatorsStep$ActionsPanel.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <a wicket:id="edit" href="#" class="edit"><span>[edit]</span></a>
+  <a wicket:id="delete" href="#" class="delete"><span>[delete]</span></a>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$CreatorsStep.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$CreatorsStep.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$CreatorsStep.html	(revision 1025)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div style="display: none" wicket:id="editCreatorDialog"></div>
+<div style="display: none" wicket:id="deleteCreatorDialog"></div>
+<div class="fieldset">
+  <label>Creators<br /><a href="#" wicket:id="add" class="add"><span>[add]</span></a></label>
+  <table wicket:id="creatorsTable" class="creatorsTable"></table>
+</div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$GeneralStep.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$GeneralStep.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$GeneralStep.html	(revision 1025)
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div style="display: none" wicket:id="addKeywordDialog"></div>
+<div style="display: none" wicket:id="deleteKeywordDialog"></div>
+<fieldset>
+  <div class="notes">
+    <h4>Some Notes</h4>
+    <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.</p>
+    <p class="last">Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.</p>
+  </div>
+  <div class="required">
+    <label for="gen_vc_name">Name</label>
+    <input wicket:id="vc.name"
+           id="gen_vc_name" type="text" size="64" maxlength="255"/>
+  </div>
+  <div class="required">
+    <label for="gen_vc_type">Type</label>
+    <select wicket:id="vc.type" id="gen_vc_type"></select>
+  </div>
+  <div class="optional">
+    <label for="gen_vc_desc">Description</label>
+    <textarea wicket:id="vc.description"
+              id="gen_vc_desc" cols="80" rows="8"></textarea>
+  </div>
+  <div class="optional">
+    <label for="gen_vc_purpose">Purpose</label>
+    <select wicket:id="vc.purpose" id="gen_vc_purpose"></select>
+  </div>
+  <div class="optional">
+    <label for="gen_vc_reproducibility">Reproducibility</label>
+    <select wicket:id="vc.reproducibility"
+            id="gen_vc_reproducibility"></select>
+  </div>
+  <div class="optional">
+    <label for="gen_vc_reproducibility_notice">Reproducibility Notice</label>
+    <textarea wicket:id="vc.reproducibilityNotice"
+              id="gen_vc_reproducibility_notice" cols="80" rows="5"></textarea>
+  </div>
+  <div class="optional">
+    <label>Keywords<br /><a href="#"
+      wicket:id="keywordsAdd" class="add"><span>[add]</span></a></label>
+    <div wicket:id="keywordsList" class="keywordsList">
+      <ul>
+        <li wicket:id="keywords">
+          <span wicket:id="itemText">item text</span>
+          <a wicket:id="itemRemove" href="#"><span class="delete">[delete]</span></a>
+        </li>
+      </ul>
+    </div>
+  </div>
+</fieldset>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$GeneratedByStep.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$GeneratedByStep.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$GeneratedByStep.html	(revision 1025)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<fieldset>
+  <div class="notes">
+    <h4>Note</h4>
+    <p class="last">
+        Intensional virtual collections are very experimental. Use at own risk!
+    </p>
+  </div>
+  <div class="required">
+     <label for="gen_desc">Description</label>
+     <textarea wicket:id="vc.generatedBy.description"
+               id="gen_desc" rows="5" cols="80"></textarea>
+  </div>
+  <div class="optional">
+     <label for="gen_uri">URI</label>
+     <input wicket:id="vc.generatedBy.uri"
+            id="gen_uri" type="text" size="64" maxlength="255" />
+  </div>
+  <div class="optional">
+     <label for="gen_query_profile">Query Profile</label>
+     <input wicket:id="vc.generatedBy.query.profile"
+            id="gen_query_profile" type="text" size="64" maxlength="255" />
+  </div>
+  <div class="optional">
+     <label for="gen_query_value">Query value</label>
+     <textarea wicket:id="vc.generatedBy.query.value"
+               id="gen_query_value" rows="5" cols="80"></textarea>
+  </div>
+</fieldset>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$ResourcesStep$ActionsPanel.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$ResourcesStep$ActionsPanel.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$ResourcesStep$ActionsPanel.html	(revision 1025)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+  <a wicket:id="edit" href="#" class="edit"><span>[edit]</span></a>
+  <a wicket:id="delete" href="#" class="delete"><span>[delete]</span></a>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$ResourcesStep.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$ResourcesStep.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard$ResourcesStep.html	(revision 1025)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div style="display: none" wicket:id="editResourceDialog"></div>
+<div style="display: none" wicket:id="deleteResourceDialog"></div>
+<div style="display: none" wicket:id="addResourcesDialog"></div>
+<div class="fieldset">
+  <label class="required">Resources<br /><a wicket:id="add" href="#" class="add"><span>[add]</span></a><br /><a wicket:id="addMore" href="#" class="addMore"><span>[add more]</span></a></label>
+  <table wicket:id="resourcesTable" class="resourcesTable"></table>
+</div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard.java	(revision 1025)
@@ -0,0 +1,623 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.wizard;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.HeaderlessColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.extensions.wizard.dynamic.DynamicWizardModel;
+import org.apache.wicket.extensions.wizard.dynamic.DynamicWizardStep;
+import org.apache.wicket.extensions.wizard.dynamic.IDynamicWizardStep;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.FormComponent;
+import org.apache.wicket.markup.html.form.RequiredTextField;
+import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.form.validation.AbstractFormValidator;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.list.OddEvenListItem;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.validation.validator.StringValidator;
+import org.apache.wicket.validation.validator.UrlValidator;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.dialog.ConfirmationDialog;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+@SuppressWarnings("serial")
+public abstract class CreateVirtualCollectionWizard extends WizardBase {
+    private final class GeneralStep extends DynamicWizardStep {
+        private final class DeleteKeywordDialog extends ConfirmationDialog {
+            private String keyword;
+            
+            public DeleteKeywordDialog(String id,
+                    final Component updateComponent) {
+                super(id, updateComponent);
+            }
+
+            @Override
+            public void onConfirm(AjaxRequestTarget target) {
+                vc.getKeywords().remove(keyword);
+            }
+            
+            public void show(AjaxRequestTarget target, String keyword) {
+                this.keyword = keyword;
+                super.show(target, new StringResourceModel("keywords.deleteconfirm", null, new Object[] { keyword }));
+            }
+        } // class CreateVirtualCollectionWizard.GeneralStep.DeleteKeywordDialog
+
+        private final class KeywordsList extends WebMarkupContainer {
+            private final ListView<String> itemsView;
+
+            public KeywordsList(String id, final List<String> items) {
+                super(id);
+                setOutputMarkupId(true);
+
+                itemsView = new ListView<String>("keywords", items) {
+                    @Override
+                    protected void populateItem(final ListItem<String> item) {
+                        final IModel<String> model = item.getModel();
+                        item.add(new Label("itemText", model.getObject()));
+                        item.add(new AjaxLink<String>("itemRemove") {
+                            @Override
+                            public void onClick(AjaxRequestTarget target) {
+                                deleteKeywordDialog.show(target,
+                                        model.getObject());
+                            }
+                        });
+                    }
+
+                    @Override
+                    protected ListItem<String> newItem(int index) {
+                        final IModel<String> model =
+                            getListItemModel(getModel(), index);
+                        return new OddEvenListItem<String>(index, model) {
+                            @Override
+                            protected void onComponentTag(ComponentTag tag) {
+                                super.onComponentTag(tag);
+                                if (getIndex() == 0) {
+                                    tag.append("class", "first", " ");
+                                }
+                            }
+                        };
+                    }
+                };
+                add(itemsView);
+            }
+        } // class CreateVirtualCollectionWizard.GeneralStep.KeywordsList
+
+        private final AddKeywordDialog addKeywordDialog;
+        private final DeleteKeywordDialog deleteKeywordDialog;
+
+        public GeneralStep() {
+            super(null, "General", null);
+            final TextField<String> nameField =
+                new RequiredTextField<String>("vc.name");
+            nameField.add(new StringValidator.MaximumLengthValidator(255));
+            add(nameField);
+            final DropDownChoice<VirtualCollection.Type> typeChoice =
+                new DropDownChoice<VirtualCollection.Type>("vc.type",
+                        Arrays.asList(VirtualCollection.Type.values()),
+                        new EnumChoiceRenderer<VirtualCollection.Type>(this));
+            typeChoice.setRequired(true);
+            add(typeChoice);
+            add(new TextArea<String>("vc.description"));
+            final DropDownChoice<VirtualCollection.Purpose> purposeChoice =
+                new DropDownChoice<VirtualCollection.Purpose>("vc.purpose",
+                        Arrays.asList(VirtualCollection.Purpose.values()),
+                        new EnumChoiceRenderer<VirtualCollection.Purpose>(this));
+            add(purposeChoice);
+            final DropDownChoice<VirtualCollection.Reproducibility> reproducibilityChoice =
+                new DropDownChoice<VirtualCollection.Reproducibility>("vc.reproducibility",
+                        Arrays.asList(VirtualCollection.Reproducibility.values()),
+                        new EnumChoiceRenderer<VirtualCollection.Reproducibility>(this));
+            add(reproducibilityChoice);
+            final TextArea<String> reproducibilityNoticeArea =
+                new TextArea<String>("vc.reproducibilityNotice");
+            add(reproducibilityNoticeArea);
+            
+            final KeywordsList keywordList =
+                new KeywordsList("keywordsList", vc.getKeywords());
+            add(keywordList);
+            add(new AjaxLink<String>("keywordsAdd") {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    addKeywordDialog.show(target);
+                }
+            });
+
+            addKeywordDialog = new AddKeywordDialog("addKeywordDialog") {
+                @Override
+                public void onSubmit(AjaxRequestTarget target, String keyword) {
+                    if (!vc.getKeywords().contains(keyword)) {
+                        vc.getKeywords().add(keyword);
+                    }
+                    target.addComponent(keywordList);
+                }
+            };
+            add(addKeywordDialog);
+
+            deleteKeywordDialog =
+                new DeleteKeywordDialog("deleteKeywordDialog", keywordList);
+            add(deleteKeywordDialog);
+        }
+
+        @Override
+        public boolean isLastStep() {
+            return false;
+        }
+
+        @Override
+        public IDynamicWizardStep next() {
+            return new CreatorsStep(this);
+        }
+
+        @Override
+        public void applyState() {
+            super.applyState();
+            switch (vc.getType()) {
+            case EXTENSIONAL:
+                vc.setGeneratedBy(null);
+                break;
+            case INTENSIONAL:
+                vc.getResources().clear();
+                break;
+            }
+        }
+    } // class CreateVirtualCollectionWizard.GeneralStep
+
+    private final class CreatorsStep extends DynamicWizardStep {
+        private final class ActionsPanel extends Panel {
+            public ActionsPanel(String id, final IModel<Creator> model) {
+                super(id, model);
+                setRenderBodyOnly(true);
+                final AjaxLink<Creator> editLink =
+                    new AjaxLink<Creator>("edit") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        final Creator creator = model.getObject();
+                        editCreatorDialog.show(target, creator);
+                    }
+                };
+                add(editLink);
+                final AjaxLink<Creator> deleteLink =
+                    new AjaxLink<Creator>("delete") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        final Creator creator = model.getObject();
+                        deleteCreatorDialog.show(target, creator);
+                    }
+                };
+                add(deleteLink);
+            }
+        } // class CreateVirtualCollectionWizard.CreatorsStep.ActionsPanel
+
+        private final class DeleteCreatorDialog extends ConfirmationDialog {
+            private Creator creator;
+
+            public DeleteCreatorDialog(String id,
+                    final Component updateComponent) {
+                super(id, updateComponent);
+                setInitialWidth(400);
+            }
+
+            @Override
+            public void onConfirm(AjaxRequestTarget target) {
+                if (creator != null) {
+                    vc.getCreators().remove(creator);
+                }
+            }
+
+            public void show(AjaxRequestTarget target, Creator creator) {
+                this.creator = creator;
+                super.show(target,
+                        new StringResourceModel("creators.deleteconfirm",
+                                new Model<Creator>(creator)));
+            }
+        } // class CreateVirtualCollectionWizard.CreatorsStep.DeleteCreatorDialog
+
+        private final EditCreatorDialog editCreatorDialog;
+        private final DeleteCreatorDialog deleteCreatorDialog;
+
+        public CreatorsStep(IDynamicWizardStep previousStep) {
+            super(previousStep, "Creators", null);
+            final DataTable<Creator> creatorsTable =
+                new AjaxFallbackDefaultDataTable<Creator>("creatorsTable",
+                        createColumns(),
+                        new SortableDataProvider<Creator>() {
+                            @Override
+                            public Iterator<? extends Creator>
+                                iterator(int first, int count) {
+                                return vc.getCreators().listIterator(first);
+                            }
+
+                            @Override
+                            public IModel<Creator> model(Creator creator) {
+                                return new Model<Creator>(creator);
+                            }
+                            @Override
+                            public int size() {
+                                return vc.getCreators().size();
+                            }
+                        },
+                        8);
+            creatorsTable.setOutputMarkupId(true);
+            add(creatorsTable);
+
+            editCreatorDialog = new EditCreatorDialog("editCreatorDialog") {
+                @Override
+                public void onSubmit(AjaxRequestTarget target, Creator creator) {
+                    if (!vc.getCreators().contains(creator)) {
+                        vc.getCreators().add(creator);
+                    }
+                    target.addComponent(creatorsTable);
+                }
+            };
+            add(editCreatorDialog);
+
+            deleteCreatorDialog =
+                new DeleteCreatorDialog("deleteCreatorDialog", creatorsTable);
+            add(deleteCreatorDialog);
+
+            add(new AjaxLink<Object>("add") {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    editCreatorDialog.show(target);
+                }
+            });
+        }
+
+        @Override
+        public boolean isLastStep() {
+            return false;
+        }
+
+        @Override
+        public IDynamicWizardStep next() {
+            switch (vc.getType()) {
+            case EXTENSIONAL:
+                return new ResourcesStep(this);
+            case INTENSIONAL:
+                return new GeneratedByStep(this);
+            default:
+                throw new InternalError("bad vc type");
+            } // switch
+        }
+
+        @SuppressWarnings("unchecked")
+        private IColumn<Creator>[] createColumns() {
+            final IColumn<?>[] columns = new IColumn<?>[] {
+                    new PropertyColumn<Creator>(new Model<String>("Person"),
+                            "person"),
+                    new PropertyColumn<Creator>(new Model<String>("EMail"),
+                            "email"),
+                    new PropertyColumn<Creator>(new Model<String>(
+                            "Organisation"), "organisation"),
+                    new HeaderlessColumn<Creator>() {
+                        @Override
+                        public void populateItem(
+                                Item<ICellPopulator<Creator>> item,
+                                String compontentId, IModel<Creator> model) {
+                            item.add(new ActionsPanel(compontentId, model));
+                        }
+
+                        @Override
+                        public String getCssClass() {
+                            return "action";
+                        }
+                    }
+            };
+            return (IColumn<Creator>[]) columns;
+        }
+    } // class CreateVirtualCollectionWizard.CreatorsStep
+
+    private final class ResourcesStep extends DynamicWizardStep {
+        private final class ActionsPanel extends Panel {
+            public ActionsPanel(String id, final IModel<Resource> model) {
+                super(id, model);
+                setRenderBodyOnly(true);
+                final AjaxLink<Resource> editLink =
+                    new AjaxLink<Resource>("edit") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        final Resource resource = model.getObject();
+                        editResourceDialog.show(target, resource);
+                    }
+                };
+                add(editLink);
+                final AjaxLink<Resource> deleteLink =
+                    new AjaxLink<Resource>("delete") {
+                    @Override
+                    public void onClick(AjaxRequestTarget target) {
+                        final Resource resource = model.getObject();
+                        deleteResourceDialog.show(target, resource);
+                    }
+                };
+                add(deleteLink);
+            }
+        } // class CreateVirtualCollectionWizard.ResourcesStep.ActionsPanel
+
+        private final class DeleteResourceDialog extends ConfirmationDialog {
+            private Resource resource;
+
+            public DeleteResourceDialog(String id,
+                    final Component updateComponenet) {
+                super(id, updateComponenet);
+                setInitialWidth(400);
+            }
+
+            @Override
+            public void onConfirm(AjaxRequestTarget target) {
+                if (resource != null) {
+                    vc.getResources().remove(resource);
+                }
+            }
+
+            public void show(AjaxRequestTarget target, Resource resource) {
+                this.resource = resource;
+                super.show(target,
+                        new StringResourceModel("resources.deleteconfirm",
+                        new Model<Resource>(resource)));
+            }
+        } // class CreateVirtualCollectionWizard.CreatorsStep.DeleteResourceDialog
+
+        private final EditResourceDialog editResourceDialog;
+        private final DeleteResourceDialog deleteResourceDialog;
+
+        public ResourcesStep(IDynamicWizardStep previousStep) {
+            super(previousStep, "Resources", null);
+            final DataTable<Resource> resourcesTable =
+                new AjaxFallbackDefaultDataTable<Resource>("resourcesTable",
+                        createColumns(),
+                        new SortableDataProvider<Resource>() {
+                            @Override
+                            public Iterator<? extends Resource>
+                                iterator(int first, int count) {
+                                return vc.getResources().listIterator(first);
+                            }
+
+                            @Override
+                            public IModel<Resource> model(Resource resource) {
+                                return new Model<Resource>(resource);
+                            }
+
+                            @Override
+                            public int size() {
+                                return vc.getResources().size();
+                            }
+                        },
+                        64);
+            resourcesTable.setOutputMarkupId(true);
+            add(resourcesTable);
+
+            editResourceDialog = new EditResourceDialog("editResourceDialog") {
+                @Override
+                public void onSubmit(AjaxRequestTarget target,
+                        Resource resource) {
+                    if (!vc.getResources().contains(resource)) {
+                        vc.getResources().add(resource);
+                    }
+                    target.addComponent(resourcesTable);
+                }
+            };
+            add(editResourceDialog);
+
+            deleteResourceDialog = new DeleteResourceDialog(
+                    "deleteResourceDialog", resourcesTable);
+            add(deleteResourceDialog);
+
+            final AddResourcesDialog addResourcesDialog =
+                new AddResourcesDialog("addResourcesDialog") {
+                @Override
+                public void onSubmit(AjaxRequestTarget target,
+                        Resource[] resources) {
+                    for (Resource resource : resources) {
+                        if (!vc.getResources().contains(resource)) {
+                            vc.getResources().add(resource);
+                        }
+                    }
+                    target.addComponent(resourcesTable);
+                }
+
+            };
+            add(addResourcesDialog);
+
+            add(new AjaxLink<Object>("add") {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    editResourceDialog.show(target);
+                }
+            });
+
+            add(new AjaxLink<Object>("addMore") {
+                @Override
+                public void onClick(AjaxRequestTarget target) {
+                    addResourcesDialog.show(target);
+                }
+            });
+
+            add(new AbstractFormValidator() {
+                @Override
+                public FormComponent<?>[] getDependentFormComponents() {
+                    return null;
+                }
+
+                @Override
+                public void validate(Form<?> form) {
+                    if (vc.getResources().isEmpty()) {
+                        form.error("An extensional collection requires at "
+                                + "least one resource.");
+                    }
+                }
+            });
+        }
+
+        @Override
+        public boolean isLastStep() {
+            return true;
+        }
+
+        @Override
+        public IDynamicWizardStep next() {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        private IColumn<Resource>[] createColumns() {
+            final IColumn<?>[] columns = new IColumn<?>[] {
+                    new AbstractColumn<Resource>(new Model<String>("Type")) {
+                        @Override
+                        public void populateItem(
+                                Item<ICellPopulator<Resource>> item,
+                                String componentId, IModel<Resource> model) {
+                            final Resource resource = model.getObject();
+                            switch (resource.getType()) {
+                            case METADATA:
+                                item.add(new Label(componentId, "Metadata"));
+                                break;
+                            case RESOURCE:
+                                item.add(new Label(componentId, "Resource"));
+                                break;
+                            }
+                        }
+
+                        @Override
+                        public String getCssClass() {
+                            return "type";
+                        }
+                    },
+                    new PropertyColumn<Resource>(
+                            new Model<String>("Reference"), "ref"),
+                    new HeaderlessColumn<Resource>() {
+                        @Override
+                        public void populateItem(
+                                Item<ICellPopulator<Resource>> item,
+                                String compontentId, IModel<Resource> model) {
+                            item.add(new ActionsPanel(compontentId, model));
+                        }
+
+                        @Override
+                        public String getCssClass() {
+                            return "action";
+                        }
+                    }
+            };
+            return (IColumn<Resource>[]) columns;
+        }
+    } // class CreateVirtualCollectionWizard.ResourcesStep
+
+    private final class GeneratedByStep extends DynamicWizardStep {
+        public GeneratedByStep(IDynamicWizardStep previousStep) {
+            super(previousStep, "Intensional Collection Query", null);
+            final TextArea<String> descriptionArea =
+                new TextArea<String>("vc.generatedBy.description");
+            descriptionArea.setRequired(true);
+            add(descriptionArea);
+            final TextField<String> uriField =
+                new TextField<String>("vc.generatedBy.uri");
+            uriField.add(new StringValidator.MaximumLengthValidator(255));
+            uriField.add(new UrlValidator(UrlValidator.NO_FRAGMENTS));
+            add(uriField);
+            final TextField<String> queryProfileField =
+                new TextField<String>("vc.generatedBy.query.profile");
+            add(queryProfileField);
+            final TextArea<String> queryValueArea =
+                new TextArea<String>("vc.generatedBy.query.value");
+            add(queryValueArea);
+            
+            add(new AbstractFormValidator() {
+                @Override
+                public FormComponent<?>[] getDependentFormComponents() {
+                    return new FormComponent[] { queryProfileField,
+                                                 queryValueArea };
+                }
+
+                @Override
+                public void validate(Form<?> form) {
+                    final String profile = queryProfileField.getInput();
+                    final String value   = queryValueArea.getInput();
+                    if (profile.isEmpty() && !value.isEmpty()) {
+                        form.error("profile is required with value");
+                        queryProfileField.invalid();
+                    }
+                    if (!profile.isEmpty() && value.isEmpty()) {
+                        form.error("value is required with profile");
+                        queryValueArea.invalid();
+                    }
+                    if (profile.isEmpty() && value.isEmpty()) {
+                        /*
+                         * XXX: clear Query object as side-effect. This is
+                         * more a hack than a clean solution.
+                         */
+                        if (vc.getGeneratedBy() != null) {
+                            vc.getGeneratedBy().setQuery(null);
+                        }
+                    }
+                }
+            });
+        }
+
+        @Override
+        public boolean isLastStep() {
+            return true;
+        }
+
+        @Override
+        public IDynamicWizardStep next() {
+            return null;
+        }
+    } // class CreateVirtualCollectionWizard.GeneratedByStep
+
+    private static final VirtualCollection EMPTY_VC = new VirtualCollection();
+    private final VirtualCollection vc;
+
+    public CreateVirtualCollectionWizard(String id, VirtualCollection vc) {
+        super(id);
+        if (vc == null) {
+            throw new IllegalArgumentException("vc == null");
+        }
+        this.vc = vc;
+        setDefaultModel(new CompoundPropertyModel<VirtualCollection>(this));
+        init(new DynamicWizardModel(new GeneralStep()));
+    }
+
+    @Override
+    public final void onCancel() {
+        if (!EMPTY_VC.equals(vc)) {
+            System.err.println("XXX: VC was modified!");
+        }
+        onCancelWizard();
+    }
+
+    @Override
+    public final void onFinish() {
+        onFinishWizard(vc);
+    }
+
+    protected abstract void onCancelWizard();
+    
+    protected abstract void onFinishWizard(VirtualCollection vc);
+
+} // class CreateVirtualCollectionWizard
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard.properties
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard.properties	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/CreateVirtualCollectionWizard.properties	(revision 1025)
@@ -0,0 +1,3 @@
+keywords.deleteconfirm: Do you really want to delete "{0}"?
+creators.deleteconfirm: Do you really want to delete "${person}"?
+resources.deleteconfirm: Do you really want to delete "${ref}"?
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditCreatorDialog$Content.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditCreatorDialog$Content.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditCreatorDialog$Content.html	(revision 1025)
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<form wicket:id="editCreatorForm">
+  <fieldset>
+    <div class="required">
+      <label for="edit_creator_person">Person</label>
+      <input wicket:id="person"
+             id="edit_creator_person" type="text" size="32" maxlength="255" />
+    </div>
+    <div class="optional">
+      <label for="edit_creator_address">Address</label>
+      <textarea wicket:id="address"
+              id="edit_creator_address" rows="4" cols="24"></textarea>
+    </div>
+    <div class="optional">
+      <label for="edit_creator_email">E-Mail</label>
+      <input wicket:id="email"
+             id="edit_creator_email" type="text"  size="32" maxlength="255" />
+    </div>
+    <div class="optional">
+      <label for="edit_creator_org">Organisation</label>
+      <input wicket:id="organisation"
+             id="edit_creator_org" type="text" size="32" maxlength="255" />
+    </div>
+    <div class="optional">
+      <label for="edit_creator_telephone">Telephone</label>
+      <input wicket:id="telephone"Ž
+             id="edit_creator_telephone" type="text" size="32" maxlength="255" />
+    </div>
+    <div class="optional">
+      <label for="edit_creator_website">Website</label>
+      <input wicket:id="website"
+             id="edit_creator_website" type="text" size="32" maxlength="255" />
+    </div>
+    <div class="optional">
+      <label for="edit_creator_role">Role</label>
+      <input wicket:id="role"
+             id="edit_creator_role" type="text" size="32" maxlength="255" />
+    </div>
+  </fieldset>
+  <div wicket:id="feedback" class="feedback"></div>
+</form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditCreatorDialog.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditCreatorDialog.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditCreatorDialog.java	(revision 1025)
@@ -0,0 +1,101 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.wizard;
+
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.RequiredTextField;
+import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.validation.validator.EmailAddressValidator;
+import org.apache.wicket.validation.validator.StringValidator;
+import org.apache.wicket.validation.validator.UrlValidator;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.dialog.ModalEditDialogBase;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+
+@SuppressWarnings("serial")
+public abstract class EditCreatorDialog extends ModalEditDialogBase<Creator> {
+    private final class Content extends
+            ModalEditDialogBase<Creator>.ContentPanel {
+        private final Form<Creator> form;
+        private final FeedbackPanel feedbackPanel;
+        
+        public Content(String id, IModel<Creator> model) {
+            super(id);
+            form = new Form<Creator>("editCreatorForm", model);
+            final TextField<String> personField =
+                new RequiredTextField<String>("person");
+            personField.add(new StringValidator.MaximumLengthValidator(255));
+            form.add(personField);
+            final TextArea<String> addressArea =
+                new TextArea<String>("address");
+            addressArea.add(new StringValidator.MaximumLengthValidator(255));
+            form.add(addressArea);
+            final TextField<String> emailField =
+                new TextField<String>("email");
+            emailField.add(new StringValidator.MaximumLengthValidator(255));
+            emailField.add(EmailAddressValidator.getInstance());
+            form.add(emailField);
+            final TextField<String> organisationField =
+                new TextField<String>("organisation");
+            organisationField.add(
+                    new StringValidator.MaximumLengthValidator(255));
+            form.add(organisationField);
+            final TextField<String> telephoneField =
+                new TextField<String>("telephone");
+            telephoneField.add(new StringValidator.MaximumLengthValidator(255));
+            form.add(telephoneField);
+            final TextField<String> websiteField =
+                new TextField<String>("website");
+            websiteField.add(new StringValidator.MaximumLengthValidator(255));
+            websiteField.add(new UrlValidator());
+            form.add(websiteField);
+            final TextField<String> roleField =
+                new TextField<String>("role");
+            roleField.add(new StringValidator.MaximumLengthValidator(255));
+            form.add(roleField);
+            feedbackPanel = new FeedbackPanel("feedback");
+            form.add(feedbackPanel);
+            add(form);
+        }
+
+        @Override
+        public Form<Creator> getForm() {
+            return form;
+        }
+
+        @Override
+        public FeedbackPanel getFeedbackPanel() {
+            return feedbackPanel;
+        }
+    } // class EditCreatorDialog.Content
+
+    public EditCreatorDialog(final String id) {
+        super(id, new Model<String>("Add/Edit Creator"));
+        setInitialWidth(400);
+    }
+
+    @Override
+    protected ModalEditDialogBase<Creator>.ContentPanel
+        createContentPanel(String id, IModel<Creator> model) {
+        return new Content(id, model);
+    }
+
+    @Override
+    protected final Creator newObjectInstance() {
+        return new Creator();
+    }
+    
+    @Override
+    protected final IModel<Creator> createModel() {
+        return new CompoundPropertyModel<Creator>(null);
+    }
+
+    @Override
+    protected String getCssClass() {
+        return "editCreatorDialog";
+    }
+
+} // EditCreatorDialog
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditResourceDialog$Content.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditResourceDialog$Content.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditResourceDialog$Content.html	(revision 1025)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<form wicket:id="editResourceForm">
+  <fieldset>
+    <div class="required">
+      <label for="edit_resource_type">Type</label>
+      <select wicket:id="type" id="edit_resource_type"></select>
+    </div>
+    <div class="required">
+      <label for="edit_resource_ref">Reference</label>
+      <input wicket:id="ref" id="edit_resource_ref" type="text" />
+    </div>
+  </fieldset>
+  <div wicket:id="feedback" class="feedback"></div>
+</form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditResourceDialog.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditResourceDialog.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/EditResourceDialog.java	(revision 1025)
@@ -0,0 +1,84 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.wizard;
+
+import java.util.Arrays;
+
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.RequiredTextField;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.validation.validator.StringValidator;
+import org.apache.wicket.validation.validator.UrlValidator;
+
+import eu.clarin.cmdi.virtualcollectionregistry.gui.dialog.ModalEditDialogBase;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+
+@SuppressWarnings("serial")
+public abstract class EditResourceDialog extends ModalEditDialogBase<Resource> {
+    private final class Content extends
+            ModalEditDialogBase<Resource>.ContentPanel {
+        private final Form<Resource> form;
+        private final FeedbackPanel feedbackPanel;
+        
+        public Content(String id, IModel<Resource> model) {
+            super(id);
+            form = new Form<Resource>("editResourceForm", model);
+            final DropDownChoice<Resource.Type> typeChoice =
+                new DropDownChoice<Resource.Type>("type",
+                        Arrays.asList(Resource.Type.values()),
+                        new EnumChoiceRenderer<Resource.Type>(this));
+            typeChoice.setRequired(true);
+            form.add(typeChoice);
+            final TextField<String> refField =
+                new RequiredTextField<String>("ref");
+            refField.add(new StringValidator.MaximumLengthValidator(255));
+            refField.add(new UrlValidator(UrlValidator.NO_FRAGMENTS));
+            form.add(refField);
+            feedbackPanel = new FeedbackPanel("feedback");
+            form.add(feedbackPanel);
+            add(form);
+        }
+
+        @Override
+        public Form<Resource> getForm() {
+            return form;
+        }
+
+        @Override
+        public FeedbackPanel getFeedbackPanel() {
+            return feedbackPanel;
+        }
+    } // class EditResourceDialog.Content
+
+
+    public EditResourceDialog(final String id) {
+        super(id, new Model<String>("Add/Edit Resource"));
+        setInitialWidth(600);
+    }
+
+    @Override
+    protected ModalEditDialogBase<Resource>.ContentPanel
+        createContentPanel(String id, IModel<Resource> model) {
+        return new Content(id, model);
+    }
+
+    @Override
+    protected final Resource newObjectInstance() {
+        return new Resource();
+    }
+    
+    @Override
+    protected final IModel<Resource> createModel() {
+        return new CompoundPropertyModel<Resource>(null);
+    }
+
+    @Override
+    protected String getCssClass() {
+        return "editResourceDialog";
+    }
+
+} // EditResourceDialog
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase$ButtonBarPanel.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase$ButtonBarPanel.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase$ButtonBarPanel.html	(revision 1025)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<div class="buttonbar">
+  <input wicket:id="previous" class="button previous" type="submit" value="previous" />
+  <input wicket:id="next" class="button next" type="submit" value="next" />
+  <input wicket:id="last" class="button last" type="submit" value="last" />
+  <input wicket:id="cancel" class="button cancel" type="submit" value="cancel" />
+  <input wicket:id="finish" class="button finish" type="submit" value="finish" />
+</div>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase.html
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase.html	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase.html	(revision 1025)
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:wicket="http://wicket.apache.org/">
+<head />
+<body>
+<wicket:panel>
+<form wicket:id="form" class="wizard">
+  <div class="wizard">
+    <div wicket:id="overview"></div>
+    <div wicket:id="header" class="wizardHeader"></div>
+    <div wicket:id="view" class="wizardView"></div>
+    <div wicket:id="feedback" class="wizardFeedback"></div>
+    <div class="wizardButtons">
+      <div wicket:id="buttons"></div>
+    </div>
+  </div>
+</form>
+</wicket:panel>
+</body>
+</html>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/gui/wizard/WizardBase.java	(revision 1025)
@@ -0,0 +1,35 @@
+package eu.clarin.cmdi.virtualcollectionregistry.gui.wizard;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.extensions.wizard.CancelButton;
+import org.apache.wicket.extensions.wizard.FinishButton;
+import org.apache.wicket.extensions.wizard.IWizard;
+import org.apache.wicket.extensions.wizard.LastButton;
+import org.apache.wicket.extensions.wizard.NextButton;
+import org.apache.wicket.extensions.wizard.PreviousButton;
+import org.apache.wicket.extensions.wizard.Wizard;
+import org.apache.wicket.markup.html.panel.Panel;
+
+@SuppressWarnings("serial")
+public class WizardBase extends Wizard {
+    private final class ButtonBarPanel extends Panel {
+        private ButtonBarPanel(String id, IWizard wizard) {
+            super(id);
+            add(new PreviousButton("previous", wizard));
+            add(new NextButton("next", wizard));
+            add(new LastButton("last", wizard));
+            add(new CancelButton("cancel", wizard));
+            add(new FinishButton("finish", wizard));
+        }
+    } // class WizardBase.ButtonBarPanel
+
+    public WizardBase(String id) {
+        super(id);
+    }
+
+    @Override
+    protected Component newButtonBar(String id) {
+        return new ButtonBarPanel(id, this);
+    }
+
+} // class WizardBase
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/Creator.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/Creator.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/Creator.java	(revision 1025)
@@ -0,0 +1,151 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+@Entity
+@Table(name = "creator")
+public class Creator implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id", nullable = false, updatable = false)
+    private Long id;
+    
+    @Column(name = "person", nullable = false, length = 255)
+    private String person;
+
+    @Column(name = "address", length = 255)
+    private String address;
+    
+    @Column(name = "email", length = 255)
+    private String email;
+
+    @Column(name = "organisation", length = 255)
+    private String organisation;
+
+    @Column(name = "telephone", length = 255)
+    private String telephone;
+
+    @Column(name = "website", length = 255)
+    private String website;
+
+    @Column(name = "role", length = 255)
+    private String role;
+    
+    public Creator() {
+        super();
+    }
+
+    public Creator(String person) {
+        super();
+        this.setPerson(person);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getPerson() {
+        return person;
+    }
+
+    public void setPerson(String person) {
+        this.person = person;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+    
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getEMail() {
+        return email;
+    }
+
+    public void setEMail(String email) {
+        this.email = email;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getTelephone() {
+        return telephone;
+    }
+    
+    public void setTelephone(String telephone) {
+        this.telephone = telephone;
+    }
+
+    public String getWebsite() {
+        return website;
+    }
+    
+    public void setWebsite(String website) {
+        this.website = website;
+    }
+
+    public String getRole() {
+        return role;
+    }
+
+    public void setRole(String role) {
+        this.role = role;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof Creator) {
+            final Creator rhs = (Creator) obj;
+            return new EqualsBuilder()
+                .append(this.getPerson(), rhs.getPerson())
+                .append(this.getAddress(), rhs.getAddress())
+                .append(this.getEMail(), rhs.getEMail())
+                .append(this.getOrganisation(), rhs.getOrganisation())
+                .append(this.getTelephone(), rhs.getTelephone())
+                .append(this.getWebsite(), rhs.getWebsite())
+                .append(this.getRole(), rhs.getRole())
+                .isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(32361, 7611)
+            .append(this.getPerson())
+            .append(this.getAddress())
+            .append(this.getEMail())
+            .append(this.getOrganisation())
+            .append(this.getTelephone())
+            .append(this.getWebsite())
+            .append(this.getRole())
+            .toHashCode();
+    }
+
+} // class Creator
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/GeneratedBy.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/GeneratedBy.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/GeneratedBy.java	(revision 1025)
@@ -0,0 +1,182 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Embedded;
+import javax.persistence.FetchType;
+import javax.persistence.Lob;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+@Embeddable
+public class GeneratedBy implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Embeddable
+    public static class Query implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        @Column(name = "generatedby_query_profile", length = 255)
+        private String profile;
+
+        @Lob
+        @Basic(fetch = FetchType.EAGER)
+        @Column(name = "generatedby_query_value", length = 8192)
+        private String value;
+
+        
+        public Query() {
+            super();
+        }
+
+        public Query(String profile, String value) {
+            super();
+            this.setProfile(profile);
+            this.setValue(value);
+        }
+
+        public String getProfile() {
+            return profile;
+        }
+
+        public void setProfile(String profile) {
+            if (profile == null) {
+                throw new NullPointerException("profile == null");
+            }
+            profile = profile.trim();
+            if (profile.isEmpty()) {
+                throw new IllegalArgumentException("profile is empty");
+            }
+            this.profile = profile;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            if (value == null) {
+                throw new NullPointerException("value == null");
+            }
+            // do not trim and allow space only values
+            this.value = value;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (obj == this) {
+                return true;
+            }
+            if (obj instanceof GeneratedBy.Query) {
+                final GeneratedBy.Query rhs = (GeneratedBy.Query) obj;
+                return new EqualsBuilder()
+                    .append(profile, rhs.profile)
+                    .append(value, rhs.value)
+                    .isEquals();
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return new HashCodeBuilder(10021, 32555)
+                .append(profile)
+                .append(value)
+                .toHashCode();
+        }
+
+    } // class GeneratedBy.Query
+
+    @Lob
+    @Basic(fetch = FetchType.EAGER)
+    @Column(name = "generatedby_description", length = 8192)
+    private String description;
+
+    @Column(name = "generatedby_uri", nullable = true, length = 255)
+    private String uri;
+
+    @Embedded
+    private GeneratedBy.Query query;
+
+    public GeneratedBy() {
+        super();
+    }
+
+    public GeneratedBy(String description) {
+        super();
+        this.setDescription(description);
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        if (description == null) {
+            throw new NullPointerException("description == null");
+        }
+        description = description.trim();
+        if (description.isEmpty()) {
+            throw new IllegalArgumentException("description is empty");
+        }
+        this.description = description;
+    }
+
+    public String getURI() {
+        return uri;
+    }
+
+    public void setURI(String uri) {
+        if (uri != null) {
+            uri = uri.trim();
+            if (uri.isEmpty()) {
+                uri = null;
+            }
+        }
+        this.uri = uri;
+    }
+
+    public GeneratedBy.Query getQuery() {
+        return query;
+    }
+
+    public void setQuery(GeneratedBy.Query query) {
+        this.query = query;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof GeneratedBy) {
+            final GeneratedBy rhs = (GeneratedBy) obj;
+            return new EqualsBuilder()
+                .append(this.getDescription(), rhs.getDescription())
+                .append(this.getURI(), rhs.getURI())
+                .append(this.getQuery(), rhs.getQuery())
+                .isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(7587, 16231)
+            .append(this.getDescription())
+            .append(uri)
+            .append(query)
+            .toHashCode();
+    }
+
+} // class GeneratedBy
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/PersistentIdentifier.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/PersistentIdentifier.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/PersistentIdentifier.java	(revision 1025)
@@ -0,0 +1,131 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.NullArgumentException;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+@Entity
+@Table(name = "pid")
+public class PersistentIdentifier implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    public static enum Type {
+        DUMMY, GWDG;
+    } // public enum Type
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id", nullable = false, updatable = false)
+    private Long id;
+
+    @OneToOne(cascade = CascadeType.ALL,
+              fetch = FetchType.LAZY,
+              optional = true)
+    @JoinColumn(name="vc_id",
+                nullable = false,
+                unique = true)
+    private VirtualCollection vc;
+
+    @Column(name = "type")
+    @Enumerated(EnumType.ORDINAL)
+    private Type type;
+
+    @Column(name = "identifier",
+            nullable = false,
+            unique = true,
+            length = 255)
+    private String identifier;
+
+    
+    private PersistentIdentifier() {
+    }
+
+    PersistentIdentifier(VirtualCollection vc, Type type, String identifier) {
+        this();
+        if (vc == null) {
+            throw new NullArgumentException("vc == null");
+        }
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+        if (identifier == null) {
+            throw new NullPointerException("identifier == null");
+        }
+        identifier = identifier.trim();
+        if (identifier.isEmpty()) {
+            throw new IllegalArgumentException("identifier is empty");
+        }
+        this.vc = vc;
+        this.type = type;
+        this.identifier = identifier;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public VirtualCollection getVirtualCollection() {
+        return vc;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    public String getActionableURI() {
+        switch (type) {
+        case DUMMY:
+            return "dummy:identifier-" + vc.getId();
+        case GWDG:
+            return "http://hdl.handle.net/" + identifier;
+        default:
+            throw new InternalError();
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof PersistentIdentifier) {
+            final PersistentIdentifier rhs = (PersistentIdentifier) obj;
+            return new EqualsBuilder()
+                .append(this.getType(), rhs.getType())
+                .append(this.getIdentifier(), rhs.getIdentifier())
+                .isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(26249, 4651)
+            .append(this.getType())
+            .append(this.getIdentifier())
+            .toHashCode();
+    }
+
+} // class PersistentIdentifier
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/PersistentIdentifierProvider.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/PersistentIdentifierProvider.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/PersistentIdentifierProvider.java	(revision 1025)
@@ -0,0 +1,69 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.util.Map;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+
+public abstract class PersistentIdentifierProvider {
+    public static final String BASE_URI       = "pid_provider.base_uri";
+    public static final String PROVIDER_CLASS = "pid_provider.class";
+
+    protected PersistentIdentifierProvider(Map<String, String> config)
+            throws VirtualCollectionRegistryException {
+        super();
+    }
+
+    public abstract PersistentIdentifier createIdentifier(VirtualCollection vc)
+            throws VirtualCollectionRegistryException;
+
+    public abstract void updateIdentifier(String pid, URI target)
+            throws VirtualCollectionRegistryException;
+
+    public abstract void deleteIdentifier(String pid)
+            throws VirtualCollectionRegistryException;
+
+    protected PersistentIdentifier doCreate(VirtualCollection vc,
+            PersistentIdentifier.Type type, String identifier) {
+        return new PersistentIdentifier(vc, type, identifier);
+    }
+
+    protected static String getConfigParameter(Map<String, String> config,
+            String parameter) throws VirtualCollectionRegistryException {
+        String value = config.get(parameter);
+        if (value == null) {
+            throw new VirtualCollectionRegistryException("configuration "
+                    + "parameter \"" + parameter + "\" is not set");
+        }
+        value = value.trim();
+        if (value.isEmpty()) {
+            throw new VirtualCollectionRegistryException("configuration "
+                    + "parameter \"" + parameter + "\" is invalid");
+        }
+        return value;
+    }
+
+    public static PersistentIdentifierProvider createProvider(
+            Map<String, String> config)
+            throws VirtualCollectionRegistryException {
+        /*
+         * XXX: instantiating the pid provider should probably done in a
+         * different and less complicated way.
+         */
+        String clazzName = getConfigParameter(config, PROVIDER_CLASS);
+        try {
+            Class<?> clazz = Class.forName(clazzName);
+            if (PersistentIdentifierProvider.class.isAssignableFrom(clazz)) {
+                Constructor<?> c = clazz.getConstructor(Map.class);
+                return (PersistentIdentifierProvider) c.newInstance(config);
+            }
+        } catch (Exception e) {
+            throw new VirtualCollectionRegistryException("error initalizing "
+                    + "persistent identifier provider", e);
+        }
+        throw new VirtualCollectionRegistryException("invalid persistent "
+                + "identifier provider (" + clazzName + ")");
+    }
+
+} // abstract class PersistentIdentifierProvider
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/Resource.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/Resource.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/Resource.java	(revision 1025)
@@ -0,0 +1,106 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+@Entity
+@Table(name = "resource")
+public class Resource implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    public static enum Type {
+        METADATA,
+        RESOURCE;
+    } // enum Resource.Type
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id", nullable = false, updatable = false)
+    private Long id;
+
+    @Column(name = "type", nullable = false)
+    @Enumerated(EnumType.ORDINAL)
+    private Type type;
+
+    @Column(name = "ref", nullable = false, length = 255)
+    private String ref;
+
+
+    public Resource() {
+        super();
+    }
+
+    public Resource(Resource.Type type, String ref) {
+        super();
+        this.setType(type);
+        this.setRef(ref);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public Resource.Type getType() {
+        return type;
+    }
+
+    public void setType(Resource.Type type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
+        this.type = type;
+    }
+
+    public String getRef() {
+        return ref;
+    }
+
+    public void setRef(String ref) {
+        if (ref == null) {
+            throw new NullPointerException("ref == null");
+        }
+        ref = ref.trim();
+        if (ref.isEmpty()) {
+            throw new IllegalArgumentException("ref is empty");
+        }
+        this.ref = ref;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof Resource) {
+            final Resource rhs = (Resource) obj;
+            return new EqualsBuilder()
+                .append(this.getType(), rhs.getType())
+                .append(this.getRef(), rhs.getRef())
+                .isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(25973, 1815)
+            .append(this.getType())
+            .append(this.getRef())
+            .toHashCode();
+    }
+
+} // class Resource
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/User.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/User.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/User.java	(revision 1025)
@@ -0,0 +1,133 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+@Entity
+@Table(name = "user")
+@NamedQueries({
+    @NamedQuery(name = "User.findByName",
+                query = "SELECT u FROM User u WHERE u.name = :name")
+})
+public class User implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id")
+    private Long id;
+
+    @Column(name = "name", nullable = false, unique = true, length = 255)
+    private String name;
+
+    @Column(name = "display_name", length = 255)
+    private String displayName;
+    
+    @OneToMany(cascade = CascadeType.ALL,
+               fetch = FetchType.LAZY,
+               mappedBy = "owner",
+               orphanRemoval = true)
+    private Set<VirtualCollection> collections =
+        new LinkedHashSet<VirtualCollection>();
+
+    
+    @SuppressWarnings("unused")
+    private User() {
+    }
+
+    public User(String name, String displayName) {
+        super();
+        this.setName(name);
+        this.setDisplayName(displayName);
+    }
+
+    public User(String name) {
+        this(name, null);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+        name = name.trim();
+        if (name.isEmpty()) {
+            throw new IllegalArgumentException("name is empty");
+        }
+        this.name = name;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        if (displayName != null) {
+            displayName = displayName.trim();
+            if (displayName.isEmpty()) {
+                displayName = null;
+            }
+        }
+        this.displayName = displayName;
+    }
+
+    public Set<VirtualCollection> getVirtualCollections() {
+        return collections;
+    }
+
+    public boolean equalsPrincipal(Principal principal) {
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        return name.equals(principal.getName());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof User) {
+            final User rhs = (User) obj;
+            return new EqualsBuilder()
+                .append(this.getName(), rhs.getName())
+                .isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(22391, 295)
+            .append(this.getName())
+            .toHashCode();
+    }
+
+} // class User
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/VirtualCollection.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/VirtualCollection.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/VirtualCollection.java	(revision 1025)
@@ -0,0 +1,490 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.Basic;
+import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Version;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+@Entity
+@Table(name = "virtualcollection")
+@NamedQueries({
+        @NamedQuery(name = "VirtualCollection.findAllPublic",
+                    query = "SELECT c FROM VirtualCollection c " +
+                            "WHERE c.state = eu.clarin.cmdi." +
+                            "virtualcollectionregistry.model." +
+                            "VirtualCollection$State.PUBLIC " +
+                            "ORDER BY c.id"),
+        @NamedQuery(name = "VirtualCollection.countAllPublic",
+                    query = "SELECT COUNT(c) FROM VirtualCollection c " +
+                            "WHERE c.state = eu.clarin.cmdi." +
+                            "virtualcollectionregistry.model." +
+                            "VirtualCollection$State.PUBLIC"),
+        @NamedQuery(name = "VirtualCollection.findByOwner",
+                    query = "SELECT c FROM VirtualCollection c " +
+                            "WHERE c.owner = :owner ORDER BY c.id"),
+        @NamedQuery(name = "VirtualCollection.countByOwner",
+                    query = "SELECT COUNT(c) FROM VirtualCollection c " +
+                            "WHERE c.owner = :owner"),
+        @NamedQuery(name = "VirtualCollection.findAllByState",
+                    query = "SELECT c FROM VirtualCollection c " +
+                            "WHERE c.state = :state AND c.dateModified < :date")
+})
+public class VirtualCollection implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    public static enum State {
+        PRIVATE,
+        PUBLIC_PENDING,
+        PUBLIC,
+        DELETED,
+        DEAD
+    } // enum VirtualCollection.State
+
+    public static enum Type {
+        EXTENSIONAL,
+        INTENSIONAL
+    } // enum VirtualCollecion.Type
+
+    public static enum Purpose {
+        RESEARCH,
+        REFERENCE,
+        SAMPLE,
+        FUTURE_USE
+    } // enum VirtualCollecion.Purpose
+
+    public static enum Reproducibility {
+        INTENDED,
+        FLUCTUATING,
+        UNTENDED
+    } // enum VirtualCollecion.Reproducibility
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id", nullable = false, updatable = false)
+    private Long id;
+
+    @ManyToOne(cascade = { CascadeType.PERSIST,
+                           CascadeType.REFRESH,
+                           CascadeType.MERGE },
+               fetch = FetchType.EAGER)
+    @JoinColumn(name = "owner_id",
+                nullable = false)
+    private User owner;
+
+    @OneToOne(cascade = CascadeType.ALL,
+              fetch = FetchType.EAGER,
+              mappedBy = "vc",
+              optional = true)
+    private PersistentIdentifier persistentId = null;
+
+    @Column(name = "state", nullable = false)
+    private VirtualCollection.State state;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "type", nullable = false)
+    private VirtualCollection.Type type;
+
+    @Column(name = "name", nullable = false, length = 255)
+    private String name;
+
+    @Lob
+    @Basic(fetch = FetchType.EAGER)
+    @Column(name = "description", length = 8192)
+    private String description;
+
+    @Temporal(TemporalType.DATE)
+    @Column(name = "creation_date")
+    private Date creationDate;
+
+    @OneToMany(cascade = CascadeType.ALL,
+               fetch = FetchType.LAZY,
+               orphanRemoval = true)
+    @JoinColumn(name = "vc_id", nullable = false)
+    @OrderBy("id")
+    private List<Creator> creators;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "purpose")
+    private VirtualCollection.Purpose purpose;
+
+    @Enumerated(EnumType.ORDINAL)
+    @Column(name = "reproducibility")
+    private VirtualCollection.Reproducibility reproducibility;
+    
+    @Lob
+    @Basic(fetch = FetchType.EAGER)
+    @Column(name = "reproducibility_notice", length = 8192)
+    private String reproducibilityNotice;
+
+    @ElementCollection
+    @CollectionTable(name = "keyword",
+                     joinColumns = @JoinColumn(name="vc_id"))
+    private List<String> keywords;
+
+    @OneToMany(cascade = CascadeType.ALL,
+               fetch = FetchType.LAZY,
+               orphanRemoval = true)
+    @JoinColumn(name = "vc_id", nullable = false)
+    @OrderBy("id")
+    private List<Resource> resources;
+
+    @Embedded
+    private GeneratedBy generatedBy;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created", nullable = false, updatable = false)
+    private Date dateCreated = new Date();
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Version
+    @Column(name = "modified", nullable = false)
+    private Date dateModified;
+
+    public VirtualCollection() {
+        super();
+        this.setState(VirtualCollection.State.PRIVATE);
+    }
+    
+    public Long getId() {
+        return id;
+    }
+
+    public User getOwner() {
+        return owner;
+    }
+    
+    public void setOwner(User owner) {
+        if (owner == null) {
+            throw new NullPointerException("owner == null");
+        }
+        if (this.owner != null) {
+            this.owner.getVirtualCollections().remove(this);
+        }
+        this.owner = owner;
+        this.owner.getVirtualCollections().add(this);
+    }
+
+    public PersistentIdentifier getPersistentIdentifier() {
+        return persistentId;
+    }
+
+    public void setPersistentIdentifier(PersistentIdentifier persistentId) {
+        if (persistentId == null) {
+            throw new NullPointerException("pid == null");
+        }
+        if ((this.persistentId != null) || (state != State.PUBLIC_PENDING)) {
+            throw new IllegalStateException("illegal state");
+        }
+        this.persistentId = persistentId;
+        this.state = State.PUBLIC;
+    }
+
+    public State getState() {
+        return state;
+    }
+
+    public void setState(State state) {
+        if (state == null) {
+            throw new NullPointerException("state == null");
+        }
+        this.state = state;
+    }
+
+    public boolean isPrivate() {
+        return state == State.PRIVATE;
+    }
+
+    public boolean isPublic() {
+        return (state == State.PUBLIC_PENDING) || (state == State.PUBLIC); 
+    }
+
+    public boolean isDeleted() {
+        return (state == State.DELETED) || (state == State.DEAD); 
+    }
+
+    public VirtualCollection.Type getType() {
+        return type;
+    }
+
+    public void setType(VirtualCollection.Type type) {
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        }
+        name = name.trim();
+        if (name.isEmpty()) {
+            throw new IllegalArgumentException("name is empty");
+        }
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        if (description != null) {
+            description = description.trim();
+            if (description.isEmpty()) {
+                description = null;
+            }
+        }
+        this.description = description;
+    }
+
+    public Date getCreationDate() {
+        return creationDate;
+    }
+
+    public void setCreationDate(Date creationDate) {
+        this.creationDate = creationDate;
+    }
+
+    public List<Creator> getCreators() {
+        if (creators == null) {
+            this.creators = new ArrayList<Creator>();
+        }
+        return creators;
+    }
+
+    public VirtualCollection.Purpose getPurpose() {
+        return purpose;
+    }
+
+    public void setPurpose(VirtualCollection.Purpose purpose) {
+        this.purpose = purpose;
+    }
+
+    public VirtualCollection.Reproducibility getReproducibility() {
+        return reproducibility;
+    }
+
+    public void setReproducibility(
+            VirtualCollection.Reproducibility reproducibility) {
+        this.reproducibility = reproducibility;
+    }
+
+    public String getReproducibilityNotice() {
+        return reproducibilityNotice;
+    }
+
+    public void setReproducibilityNotice(String reproducibilityNotice) {
+        if (reproducibilityNotice != null) {
+            reproducibilityNotice = reproducibilityNotice.trim();
+            if (reproducibilityNotice.isEmpty()) {
+                reproducibilityNotice = null;
+            }
+        }
+        this.reproducibilityNotice = reproducibilityNotice;
+    }
+
+    public List<String> getKeywords() {
+        if (keywords == null) {
+            keywords = new ArrayList<String>();
+        }
+        return keywords;
+    }
+
+    public List<Resource> getResources() {
+        if (resources == null) {
+            resources = new ArrayList<Resource>();
+        }
+        return resources;
+    }
+
+    public GeneratedBy getGeneratedBy() {
+        return generatedBy;
+    }
+
+    public void setGeneratedBy(GeneratedBy generatedBy) {
+        this.generatedBy = generatedBy;
+    }
+
+    public Date getDateCreated() {
+        return dateCreated;
+    }
+
+    public Date getDateModified() {
+        return dateModified;
+    }
+
+    public void setDateModified(Date dateModified) {
+        if (dateModified == null) {
+            throw new NullPointerException("dateModified == null");
+        }
+        this.dateModified = dateModified;
+    }
+
+    public void updateFrom(VirtualCollection vc) {
+        if (this == vc) {
+            return;
+        }
+        if (vc.getPersistentIdentifier() != null) {
+            this.setPersistentIdentifier(vc.getPersistentIdentifier());
+        }
+        this.setState(state);
+        this.setType(vc.getType());
+        this.setName(vc.getName());
+        this.setDescription(vc.getDescription());
+        this.setCreationDate(vc.getCreationDate());
+        
+        // Creators
+        Set<Creator> obsolete_creators =
+            new HashSet<Creator>(this.getCreators());
+        for (Creator creator : vc.getCreators()) {
+            if (!obsolete_creators.contains(creator)) {
+                this.getCreators().add(creator);
+            }
+            obsolete_creators.remove(creator);
+        }
+        if (!obsolete_creators.isEmpty()) {
+            for (Creator creator : obsolete_creators) {
+                this.getCreators().remove(creator);
+            }
+            obsolete_creators = null;
+        }
+
+        this.setPurpose(vc.getPurpose());
+        this.setReproducibility(vc.getReproducibility());
+        this.setReproducibilityNotice(vc.getReproducibilityNotice());
+
+        // Keywords
+        Set<String> obsolete_keywords =
+            new HashSet<String>(this.getKeywords());
+        for (String keyword : vc.getKeywords()) {
+            if (!obsolete_keywords.contains(keyword)) {
+                this.getKeywords().add(keyword);
+            }
+            obsolete_keywords.remove(keyword);
+        }
+        if (!obsolete_keywords.isEmpty()) {
+            for (String keyword : obsolete_keywords) {
+                this.getKeywords().remove(keyword);
+            }
+            obsolete_keywords = null;
+        }
+
+        // Resources
+        Set<Resource> obsolete_resources =
+            new HashSet<Resource>(this.getResources());
+        for (Resource resource : vc.getResources()) {
+            if (!obsolete_resources.contains(resource)) {
+                this.getResources().add(resource);
+            }
+            obsolete_resources.remove(resource);
+        }
+        if (!obsolete_resources.isEmpty()) {
+            for (Resource resource : obsolete_resources) {
+                this.getResources().remove(resource);
+            }
+            obsolete_resources = null;
+        }
+
+        if (vc.generatedBy != null) {
+            final GeneratedBy genBy = vc.generatedBy;
+            if (this.generatedBy == null) {
+                this.generatedBy = new GeneratedBy(genBy.getDescription());
+            } else {
+                this.generatedBy.setDescription(genBy.getDescription());
+            }
+            this.generatedBy.setURI(genBy.getURI());
+            if (genBy.getQuery() != null) {
+                final GeneratedBy.Query q = genBy.getQuery();
+                GeneratedBy.Query query =
+                    new GeneratedBy.Query(q.getProfile(), q.getValue());
+                this.generatedBy.setQuery(query);
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof VirtualCollection) {
+            final VirtualCollection rhs = (VirtualCollection) obj;
+            return new EqualsBuilder()
+                .append(this.getOwner(), rhs.getOwner())
+                .append(this.getPersistentIdentifier(),
+                            rhs.getPersistentIdentifier())
+                .append(this.getState(), rhs.getState())
+                .append(this.getType(), rhs.getType())
+                .append(this.getName(), rhs.getName())
+                .append(this.getDescription(), rhs.getDescription())
+                .append(this.getCreationDate(), rhs.getCreationDate())
+                .append(this.getCreators(), rhs.getCreators())
+                .append(this.getPurpose(), rhs.getPurpose())
+                .append(this.getReproducibility(), rhs.getReproducibility())
+                .append(this.getReproducibilityNotice(),
+                            rhs.getReproducibilityNotice())
+                .append(this.getKeywords(), rhs.getKeywords())
+                .append(this.getResources(), rhs.getResources())
+                .append(this.getGeneratedBy(), rhs.getGeneratedBy())
+                .isEquals();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(1391, 295)
+            .append(this.getOwner())
+            .append(this.getPersistentIdentifier())
+            .append(this.getState())
+            .append(this.getType())
+            .append(this.getName())
+            .append(this.getDescription())
+            .append(this.getCreationDate())
+            .append(this.getCreators())
+            .append(this.getPurpose())
+            .append(this.getReproducibility())
+            .append(this.getReproducibilityNotice())
+            .append(this.getKeywords())
+            .append(this.getResources())
+            .append(this.getGeneratedBy())
+            .toHashCode();
+    }
+
+} // class VirtualCollection
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/VirtualCollectionList.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/VirtualCollectionList.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/model/VirtualCollectionList.java	(revision 1025)
@@ -0,0 +1,40 @@
+package eu.clarin.cmdi.virtualcollectionregistry.model;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class VirtualCollectionList implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private List<VirtualCollection> collections;
+    private int offset;
+    private int totalCount;
+
+    public VirtualCollectionList(List<VirtualCollection> list, int offset,
+            int totalCount) {
+        if (list != null) {
+            this.collections = list;
+        } else {
+            this.collections = Collections.emptyList();
+        }
+        this.offset = offset;
+        this.totalCount = totalCount;
+    }
+
+    public int getOffset() {
+        return offset;
+    }
+
+    public int getTotalCount() {
+        return totalCount;
+    }
+
+    public boolean isPartialList() {
+        return collections.size() < totalCount;
+    }
+
+    public List<VirtualCollection> getItems() {
+        return collections;
+    }
+
+} // class VirtualCollectionList
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/ASTPredicate.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/ASTPredicate.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/ASTPredicate.java	(revision 1025)
@@ -0,0 +1,114 @@
+package eu.clarin.cmdi.virtualcollectionregistry.query;
+
+public class ASTPredicate extends SimpleNode {
+    private int attribute;
+    private int operator;
+    private String value;
+
+    public ASTPredicate(int id) {
+        super(id);
+    }
+
+    public ASTPredicate(QueryParser p, int id) {
+        super(p, id);
+    }
+
+    public Object jjtAccept(QueryParserVisitor visitor, Object data) {
+        return visitor.visit(this, data);
+    }
+    
+    public void setAttribute(int attribute) {
+        this.attribute = attribute;
+    }
+    
+    public int getAttribute() {
+        return attribute;
+    }
+
+    public void setOperator(int operator) {
+        this.operator = operator;
+    }
+    
+    public int getOperator() {
+        return operator;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+    
+    public String getValue() {
+        return value;
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Predicate[");
+        switch (attribute) {
+        case QueryParserConstants.VC_NAME:
+            sb.append("vc_name");
+            break;
+        case QueryParserConstants.VC_DESC:
+            sb.append("vc_desc");
+            break;
+        case QueryParserConstants.VC_STATE:
+            sb.append("vc_state");
+            break;
+        case QueryParserConstants.VC_PURPOSE:
+            sb.append("vc_purpose");
+            break;
+        case QueryParserConstants.VC_REPRODUCIBILITY:
+            sb.append("vc_reproducibility");
+            break;
+        case QueryParserConstants.VC_MODIFIED:
+            sb.append("vc_modified");
+            break;
+        case QueryParserConstants.VC_CREATED:
+            sb.append("vc_created");
+            break;
+        case QueryParserConstants.VC_OWNER:
+            sb.append("vc_owner");
+            break;
+        case QueryParserConstants.CR_PERSON:
+            sb.append("cr_name");
+            break;
+        case QueryParserConstants.CR_EMAIL:
+            sb.append("cr_email");
+            break;
+        case QueryParserConstants.CR_ORGANIZATION:
+            sb.append("cr_organization");
+            break;
+        default:
+            sb.append("0x");
+            sb.append(Integer.toHexString(attribute));
+        } // switch (attribute)
+        sb.append(", ");
+        switch (operator) {
+        case QueryParserConstants.EQ:
+            sb.append("eq");
+            break;
+        case QueryParserConstants.NE:
+            sb.append("ne");
+            break;
+        case QueryParserConstants.GT:
+            sb.append("gt");
+            break;
+        case QueryParserConstants.GE:
+            sb.append("ge");
+            break;
+        case QueryParserConstants.LT:
+            sb.append("lt");
+            break;
+        case QueryParserConstants.LE:
+            sb.append("le");
+            break;
+         default:
+             sb.append("0x");
+             sb.append(Integer.toHexString(operator));
+        } // switch (operator)
+        sb.append(", ");
+        sb.append(value);
+        sb.append("]");
+        return sb.toString();
+    }
+} // class ASTPredicate
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/ParsedQuery.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/ParsedQuery.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/ParsedQuery.java	(revision 1025)
@@ -0,0 +1,104 @@
+package eu.clarin.cmdi.virtualcollectionregistry.query;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryUsageException;
+import eu.clarin.cmdi.virtualcollectionregistry.model.User;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection_;
+
+public class ParsedQuery {
+    private static final WhereClauseBuilder visitor = new WhereClauseBuilder();
+    private final String query;
+    private final ASTStart start;
+
+    private ParsedQuery(String query, ASTStart start) {
+        this.query = query;
+        this.start = start;
+    }
+
+    public TypedQuery<Long> getCountQuery(EntityManager em, User owner,
+            VirtualCollection.State state)
+            throws VirtualCollectionRegistryException {
+        try {
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<Long> cq = cb.createQuery(Long.class);
+            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
+            WhereClauseBuilder.Data data =
+                new WhereClauseBuilder.Data(cb, cq, root);
+            visitor.visit(start, data);
+
+            Predicate where = data.getWhere();
+            if (owner != null) {
+                where = cb.and(where, cb.equal(
+                            root.get(VirtualCollection_.owner), owner));
+            }
+            if (state != null) {
+                where = cb.and(where, cb.equal(
+                            root.get(VirtualCollection_.state), state));
+            }
+            cq.where(where);
+            return em.createQuery(cq.select(cb.count(root)));
+        } catch (Throwable e) {
+            throw new VirtualCollectionRegistryException(
+                    "error building query", e);
+        }
+    }
+
+    public TypedQuery<VirtualCollection> getQuery(EntityManager em, User owner,
+            VirtualCollection.State state)
+            throws VirtualCollectionRegistryException {
+        try {
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<VirtualCollection> cq =
+                cb.createQuery(VirtualCollection.class);
+            Root<VirtualCollection> root = cq.from(VirtualCollection.class);
+            WhereClauseBuilder.Data data =
+                new WhereClauseBuilder.Data(cb, cq, root);
+            visitor.visit(start, data);
+
+            Predicate where = data.getWhere();
+            if (owner != null) {
+                where = cb.and(where, cb.equal(
+                            root.get(VirtualCollection_.owner), owner));
+            }
+            if (state != null) {
+                where = cb.and(where, cb.equal(
+                            root.get(VirtualCollection_.state), state));
+            }
+            cq.where(where);
+            return em.createQuery(cq.select(root));
+        } catch (Throwable e) {
+            throw new VirtualCollectionRegistryException(
+                    "error building query", e);
+        }
+    }
+
+    public String getPrettyPrinted() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("query: ");
+        sb.append(query);
+        sb.append("\n");
+        PrettyPrinter pp = new PrettyPrinter(sb);
+        pp.visit(start, null);
+        return sb.toString();
+    }
+
+    public static ParsedQuery parseQuery(String query)
+        throws VirtualCollectionRegistryUsageException {
+        try {
+            QueryParser parser = new QueryParser(query);
+            return new ParsedQuery(query, parser.start());
+        } catch (Throwable e) {
+            throw new VirtualCollectionRegistryUsageException(
+                        "invalid query", e);
+        }
+    }
+
+} // class ParsedQuery
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/PrettyPrinter.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/PrettyPrinter.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/PrettyPrinter.java	(revision 1025)
@@ -0,0 +1,92 @@
+package eu.clarin.cmdi.virtualcollectionregistry.query;
+
+import java.util.Stack;
+
+public class PrettyPrinter implements QueryParserVisitor {
+    private StringBuilder out;
+    private Stack<Node> parents = new Stack<Node>();
+
+    public PrettyPrinter(StringBuilder out) {
+        if (out == null) {
+            throw new NullPointerException("out == null");
+        }
+        this.out = out;
+    }
+
+    @Override
+    public Object visit(ASTStart node, Object data) {
+        out.append(node);
+        out.append("\n");
+        data = node.childrenAccept(this, data);
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTOr node, Object data) {
+        printNode(node);
+        parents.push(node);
+        data = node.childrenAccept(this, data);
+        parents.pop();
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTAnd node, Object data) {
+        printNode(node);
+        parents.push(node);
+        data = node.childrenAccept(this, data);
+        parents.pop();
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTPredicate node, Object data) {
+        printNode(node);
+        return data;
+    }
+
+    @Override
+    public Object visit(SimpleNode node, Object data) {
+        System.err.println("simpleNode");
+        parents.push(node);
+        data = node.childrenAccept(this, data);
+        parents.pop();
+        return data;
+    }
+
+    private void printNode(Node node) {
+        for (Node parent : parents) {
+            if (isLastSibling(parent)) {
+                out.append("   ");
+            } else {
+                out.append(" | ");
+            }
+        }
+        if (isLastSibling(node)) {
+            out.append(" \\-");
+        } else {
+            out.append(" +-");
+        }
+        if (node instanceof ASTPredicate) {
+            out.append(" ");
+            out.append(node.toString());
+            out.append("\n");
+        } else {
+            out.append("[");
+            out.append(node.toString());
+            out.append("]");
+            out.append("\n");
+        }
+    }
+    
+    private boolean isLastSibling(Node node) {
+        Node parent = node.jjtGetParent();
+        if (parent == null) {
+            return true;
+        } else {
+            int idx = parent.jjtGetNumChildren() -1;
+            Node n = parent.jjtGetChild(idx);
+            return node.equals(n);
+        }
+    }
+} // class PrettyPrinter
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/WhereClauseBuilder.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/WhereClauseBuilder.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/query/WhereClauseBuilder.java	(revision 1025)
@@ -0,0 +1,319 @@
+package eu.clarin.cmdi.virtualcollectionregistry.query;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Stack;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator_;
+import eu.clarin.cmdi.virtualcollectionregistry.model.User_;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection_;
+
+class WhereClauseBuilder implements QueryParserVisitor {
+    static class Data {
+        private CriteriaBuilder cb;
+        private CriteriaQuery<?> cq;
+        private Root<VirtualCollection> root;
+        private Stack<Predicate> stack;
+        
+        public Data(CriteriaBuilder cb, CriteriaQuery<?> cq,
+                Root<VirtualCollection> root) {
+            this.cb = cb;
+            this.cq = cq;
+            this.root = root;
+            this.stack = new Stack<Predicate>();
+        }
+        
+        public CriteriaBuilder getBuilder() {
+            return cb;
+        }
+
+        public CriteriaQuery<?> getQuery() {
+            return cq;
+        }
+        
+        public Root<VirtualCollection> getRoot() {
+            return root;
+        }
+        
+        public Stack<Predicate> getStack() {
+            return stack;
+        }
+        
+        public Predicate getWhere() {
+            return stack.peek();
+        }
+    } // inner class Data
+    
+    @Override
+    public Object visit(ASTStart node, Object d) {
+        final Data data = (Data) d;
+        node.childrenAccept(this, data);
+        if (data.getStack().size() != 1) {
+            throw new InternalError("expected only one element on stack");
+        }
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTOr node, Object d) {
+        final Data data = (Data) d;
+        node.childrenAccept(this, data);
+        Predicate[] predicates = new Predicate[node.jjtGetNumChildren()];
+        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+            predicates[i] = data.getStack().pop();
+        }
+        data.getStack().push(data.getBuilder().or(predicates));
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTAnd node, Object d) {
+        final Data data = (Data) d;
+        node.childrenAccept(this, data);
+        Predicate[] predicates = new Predicate[node.jjtGetNumChildren()];
+        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+            predicates[i] = data.getStack().pop();
+        }
+        data.getStack().push(data.getBuilder().and(predicates));
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTPredicate node, Object d) {
+        final Data data = (Data) d;
+        final Root<VirtualCollection> root = data.getRoot();
+        
+        Predicate predicate = null;
+        switch (node.getAttribute()) {
+        case QueryParserConstants.VC_NAME:
+            predicate = makeStringPredicate(data,
+                    root.get(VirtualCollection_.name),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_DESC:
+            predicate = makeStringPredicate(data,
+                    root.get(VirtualCollection_.description),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_STATE:
+            predicate = makeStatePredicate(data,
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_PURPOSE:
+            predicate = makePurposePredicate(data,
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_REPRODUCIBILITY:
+            predicate = makeReproducibilityPredicate(data,
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_CREATED:
+            predicate = makeDatePredicate(data,
+                    root.get(VirtualCollection_.dateCreated),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_MODIFIED:
+            predicate = makeDatePredicate(data,
+                    root.get(VirtualCollection_.dateModified),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.VC_OWNER:
+            predicate = makeStringPredicate(data,
+                    root.get(VirtualCollection_.owner).get(User_.name),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.CR_PERSON:
+            predicate = makeStringPredicate(data,
+                    root.join(VirtualCollection_.creators).get(Creator_.person),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.CR_EMAIL:
+            predicate = makeStringPredicate(data,
+                    root.join(VirtualCollection_.creators).get(Creator_.email),
+                    node.getOperator(), node.getValue());
+            break;
+        case QueryParserConstants.CR_ORGANIZATION:
+            predicate = makeStringPredicate(data,
+                    root.join(VirtualCollection_.creators)
+                        .get(Creator_.organisation),
+                    node.getOperator(), node.getValue());
+            break;
+        default:
+            /* FALL-TROUGH */
+        } // switch (node.getAttribute())
+
+        if (predicate == null) {
+            throw new InternalError("bad attribute");
+        }
+        data.getStack().push(predicate);
+        return data;
+    }
+
+    @Override
+    public Object visit(SimpleNode node, Object data) {
+        throw new InternalError("unexpected node (" + node.value + ")");
+    }
+
+    private static Predicate makeStringPredicate(Data data,
+            Expression<String> attribute, int operator, String value) {
+        // escape SQL specific stuff
+        value = value.replace("\\", "\\\\");
+        value = value.replace("%", "\\%");
+        value = value.replace("_", "\\_");
+        // replace query language wildcards with add SQL wildcards
+        value = value.replace("*", "%");
+
+        // XXX: HSQLDB/Hibernate seems have trouble with escape char  
+        switch (operator) {
+        case QueryParserConstants.EQ:
+            return data.getBuilder().like(attribute, value/*, '\\'*/);
+        case QueryParserConstants.NE:
+            return data.getBuilder().notLike(attribute, value/*, '\\'*/);
+        default:
+            throw new InternalError("bad operator");
+        } // switch (operator)
+    }
+
+    private static Predicate makeStatePredicate(Data data, int operator,
+            String value) {
+        VirtualCollection.State state = null;
+        if ("public".equalsIgnoreCase(value)) {
+            state = VirtualCollection.State.PUBLIC;
+        } else if ("private".equalsIgnoreCase(value)) {
+            state = VirtualCollection.State.PRIVATE;
+        } else if ("deleted".equalsIgnoreCase(value)) {
+            state = VirtualCollection.State.DELETED;
+        } else {
+            throw new InternalError("bad value");
+        }
+        CriteriaBuilder cb = data.getBuilder();
+        Expression<VirtualCollection.State> attribute =
+            data.getRoot().get(VirtualCollection_.state);
+        switch (operator) {
+        case QueryParserConstants.EQ:
+            // special case for public state
+            if (state == VirtualCollection.State.PUBLIC) {
+                return cb.or(
+                        cb.equal(attribute,
+                                 VirtualCollection.State.PUBLIC),
+                        cb.equal(attribute,
+                                 VirtualCollection.State.PUBLIC_PENDING)); 
+            } else {
+                return data.getBuilder().equal(attribute, state);
+            }
+        case QueryParserConstants.NE:
+            // special case for public state
+            if (state == VirtualCollection.State.PUBLIC) {
+                return cb.and(
+                        cb.notEqual(attribute,
+                                    VirtualCollection.State.PUBLIC),
+                        cb.notEqual(attribute,
+                                    VirtualCollection.State.PUBLIC_PENDING)); 
+                
+            } else {
+                return data.getBuilder().notEqual(attribute, state);
+            }
+        default:
+            throw new InternalError("bad operator");
+        } // switch (operator)
+    }
+
+    private static Predicate makePurposePredicate(Data data, int operator,
+            String value) {
+        VirtualCollection.Purpose purpose = null;
+        if ("research".equalsIgnoreCase(value)) {
+            purpose = VirtualCollection.Purpose.RESEARCH;
+        } else if ("reference".equalsIgnoreCase(value)) {
+            purpose = VirtualCollection.Purpose.REFERENCE;
+        } else if ("sample".equalsIgnoreCase(value)) {
+            purpose = VirtualCollection.Purpose.SAMPLE;
+        } else if ("future-use".equalsIgnoreCase(value)) {
+            purpose = VirtualCollection.Purpose.FUTURE_USE;
+        } else {
+            throw new InternalError("bad value");
+        }
+        Expression<VirtualCollection.Purpose> attribute =
+            data.getRoot().get(VirtualCollection_.purpose);
+        switch (operator) {
+        case QueryParserConstants.EQ:
+            return data.getBuilder().equal(attribute, purpose);
+        case QueryParserConstants.NE:
+            return data.getBuilder().notEqual(attribute, purpose);
+        default:
+            throw new InternalError("bad operator");
+        } // switch (operator)
+    }
+
+    private static Predicate makeReproducibilityPredicate(Data data, 
+            int operator, String value) {
+        VirtualCollection.Reproducibility reproducability = null;
+        if ("intended".equalsIgnoreCase(value)) {
+            reproducability = VirtualCollection.Reproducibility.INTENDED;
+        } else if ("fluctuating".equalsIgnoreCase(value)) {
+            reproducability = VirtualCollection.Reproducibility.FLUCTUATING;
+        } else if ("untended".equalsIgnoreCase(value)) {
+            reproducability = VirtualCollection.Reproducibility.UNTENDED;
+        } else {
+            throw new InternalError("bad value");
+        }
+        Expression<VirtualCollection.Reproducibility> attribute =
+            data.getRoot().get(VirtualCollection_.reproducibility);
+        switch (operator) {
+        case QueryParserConstants.EQ:
+            return data.getBuilder().equal(attribute, reproducability);
+        case QueryParserConstants.NE:
+            return data.getBuilder().notEqual(attribute, reproducability);
+        default:
+            throw new InternalError("bad operator");
+        } // switch (operator)
+    }
+
+    private static Predicate makeDatePredicate(Data data,
+            Expression<Date> attribute, int operator, String value) {
+        Date date = null;
+        try {
+            DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
+            if (value.endsWith("Z")) {
+                value = value.substring(0, value.length() - 1) + "GMT-00:00";
+            } else {
+                int inset = 6;
+                String s0 =
+                    value.substring(0, value.length() - inset);
+                String s1 =
+                    value.substring(value.length() - inset, value.length());
+                value = s0 + "GMT" + s1;
+            }
+            date = df.parse(value);
+        } catch (ParseException e) {
+            throw new RuntimeException("invalid date", e);
+        }
+        
+        CriteriaBuilder cb = data.getBuilder();
+        switch (operator) {
+        case QueryParserConstants.EQ:
+            return cb.equal(attribute, date);
+        case QueryParserConstants.NE:
+            return cb.notEqual(attribute, date);
+        case QueryParserConstants.GT:
+            return cb.greaterThan(attribute, date);
+        case QueryParserConstants.GE:
+            return cb.greaterThanOrEqualTo(attribute, date);
+        case QueryParserConstants.LT:
+            return cb.lessThan(attribute, date);
+        case QueryParserConstants.LE:
+            return cb.lessThanOrEqualTo(attribute, date);
+        default:
+            throw new InternalError("bad operator");
+        }
+    }
+
+} // class WhereClauseBuilder
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/ContextListener.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/ContextListener.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/ContextListener.java	(revision 1025)
@@ -0,0 +1,44 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+
+public class ContextListener implements ServletContextListener {
+
+    @SuppressWarnings("unchecked")
+    public void contextInitialized(ServletContextEvent event) {
+        ServletContext ctx = event.getServletContext();
+        HashMap<String, String> config = new HashMap<String, String>();
+        for (Enumeration i = ctx.getInitParameterNames();
+             i.hasMoreElements();) {
+            String key = (String) i.nextElement();
+            String value = ctx.getInitParameter(key);
+            if ((value != null) && (value.length() > 0)) {
+                config.put(key, value);
+            }
+        }
+        try {
+            VirtualCollectionRegistry.initalize(config);
+        } catch (VirtualCollectionRegistryException e) {
+            ctx.log("error initializing registry", e);
+            throw new RuntimeException("error initializing registry", e);
+        }
+    }
+
+    public void contextDestroyed(ServletContextEvent event) {
+        ServletContext ctx = event.getServletContext();
+        try {
+            VirtualCollectionRegistry.instance().destroy();
+        } catch (VirtualCollectionRegistryException e) {
+            ctx.log("error destroying registry", e);
+        }
+    }
+
+} // class ContextListener
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/IllegalArgumentExceptionMapper.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/IllegalArgumentExceptionMapper.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/IllegalArgumentExceptionMapper.java	(revision 1025)
@@ -0,0 +1,20 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+public class IllegalArgumentExceptionMapper implements
+        ExceptionMapper<IllegalArgumentException> {
+
+    public Response toResponse(IllegalArgumentException e) {
+        RestResponse response = new RestResponse();
+        response.setIsSuccess(false);
+        response.setInfo(e.getMessage());
+        return Response.status(Response.Status.BAD_REQUEST).type(
+                MediaType.APPLICATION_XML).entity(response).build();
+    }
+
+} // class IllegalArgumentExceptionMapper
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/PersistenceFilter.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/PersistenceFilter.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/PersistenceFilter.java	(revision 1025)
@@ -0,0 +1,38 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+
+public class PersistenceFilter implements Filter {
+
+    public void init(FilterConfig config) throws ServletException {
+        // DO NOTHING
+    }
+
+    public void doFilter(ServletRequest req, ServletResponse res,
+            FilterChain chain) throws IOException, ServletException {
+        try {
+            chain.doFilter(req, res);
+        } catch (IOException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ServletException("error while processing request", e);
+        } finally {
+            VirtualCollectionRegistry.instance().getDataStore()
+                    .closeEntityManager();
+        }
+    }
+
+    public void destroy() {
+        // DO NOTHING
+    }
+
+} // class PersistenceFilter
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/RestResponse.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/RestResponse.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/RestResponse.java	(revision 1025)
@@ -0,0 +1,62 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "RegistryResult")
+@XmlAccessorType(XmlAccessType.NONE)
+@XmlType(propOrder = { "id", "info", "errors" })
+public class RestResponse {
+    private boolean isSuccess;
+    private Long id;
+    private String info;
+    private List<String> errors;
+
+    public void setIsSuccess(boolean isSuccess) {
+        this.isSuccess = isSuccess;
+    }
+
+    @XmlAttribute(name = "success")
+    public boolean isSuccess() {
+        return isSuccess;
+    }
+
+    public void setId(long id) {
+        this.id = new Long(id);
+    }
+
+    @XmlElement(name = "VirtualCollectionId")
+    public Long getId() {
+        return id;
+    }
+
+    public void setInfo(String info) {
+        this.info = info;
+    }
+
+    @XmlElement(name = "Info")
+    public String getInfo() {
+        return info;
+    }
+
+    public void setError(List<String> errors) {
+        if ((errors != null) && !errors.isEmpty()) {
+            this.errors = errors;
+        }
+    }
+
+    @XmlElementWrapper(name = "Errors")
+    @XmlElements( { @XmlElement(name = "Error") })
+    public List<String> getErrors() {
+        return errors;
+    }
+
+} // class RestResponse
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/State.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/State.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/State.java	(revision 1025)
@@ -0,0 +1,27 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlEnumValue;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name="State")
+@XmlEnum(String.class)
+public enum State {
+    @XmlEnumValue("private")
+    PRIVATE,
+    @XmlEnumValue("public")
+    PUBLIC;
+    
+    public String toString() {
+        switch (this) {
+        case PRIVATE:
+            return "private";
+        case PUBLIC:
+            return "public";
+        default:
+            throw new InternalError();
+
+        }
+    }
+
+} // enum State
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/VirtualCollectionRegistryExceptionMapper.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/VirtualCollectionRegistryExceptionMapper.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/VirtualCollectionRegistryExceptionMapper.java	(revision 1025)
@@ -0,0 +1,103 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import javax.xml.stream.XMLStreamException;
+
+import org.xml.sax.SAXParseException;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionNotFoundException;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryPermissionException;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryUsageException;
+
+@Provider
+public class VirtualCollectionRegistryExceptionMapper implements
+        ExceptionMapper<VirtualCollectionRegistryException> {
+
+    public Response toResponse(VirtualCollectionRegistryException e) {
+        Status       status = null;
+        List<String> errors = null;
+        if (e instanceof VirtualCollectionRegistryUsageException) {
+            status = Status.BAD_REQUEST;
+            errors = getErrors(e.getCause());
+        } else if (e instanceof VirtualCollectionRegistryPermissionException) {
+            status = Status.FORBIDDEN;
+        } else if (e instanceof VirtualCollectionNotFoundException) {
+            status = Status.NOT_FOUND;
+        } else {
+            if (hasCause(e.getCause(), XMLStreamException.class)) {
+                status = Status.BAD_REQUEST;
+            } else {
+                status = Status.INTERNAL_SERVER_ERROR;
+            }
+            errors = getErrors(e.getCause());
+        }
+        RestResponse response = new RestResponse();
+        response.setIsSuccess(false);
+        response.setInfo(e.getMessage());
+        if (errors != null) {
+            response.setError(errors);
+        }
+        return Response.status(status).type(MediaType.APPLICATION_XML)
+            .entity(response).build();
+    }
+
+    private static List<String> getErrors(Throwable t) {
+        ArrayList<String> errors = new ArrayList<String>();
+        while (t != null) {
+            if (t instanceof SAXParseException) {
+                final SAXParseException e = (SAXParseException) t;
+                String message = e.getMessage();
+                if (message.startsWith("cvc")) {
+                    int pos = message.indexOf(':');
+                    if (pos != 0) {
+                        message = message.substring(pos + 1);
+                    }
+                }
+                errors.add(message.trim() + " [line = " + e.getLineNumber() +
+                        ", column = " + e.getColumnNumber() + "]");
+            } else if (t instanceof XMLStreamException) {
+                final XMLStreamException e = (XMLStreamException) t;
+                String message = e.getMessage();
+                if (message.startsWith("ParseError")) {
+                    int pos = message.indexOf(':');
+                    if (pos != -1) {
+                        pos = message.indexOf(':', pos + 1);
+                        if (pos != -1) {
+                            message = message.substring(pos + 1);
+                        }
+                    }
+                }
+                if (e.getLocation() != null) {
+                    message = message.trim() + " [line = " +
+                            e.getLocation().getLineNumber() + ", column = " +
+                            e.getLocation().getColumnNumber() + "]";
+                }
+                errors.add(message);
+            } else {
+                errors.add(t.getMessage());
+            }
+            t = t.getCause();
+        }
+        ;
+        return errors;
+    }
+
+    private static boolean hasCause(Throwable t, Class<?> clazz) {
+        while (t != null) {
+            if (clazz.isInstance(t)) {
+                return true;
+            }
+            t = t.getCause();
+        }
+        return false;
+    }
+
+} // class VirtualCollectionRegistryExceptionMapper
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/VirtualCollectionRegistryRestService.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/VirtualCollectionRegistryRestService.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/java/eu/clarin/cmdi/virtualcollectionregistry/rest/VirtualCollectionRegistryRestService.java	(revision 1025)
@@ -0,0 +1,333 @@
+package eu.clarin.cmdi.virtualcollectionregistry.rest;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.security.Principal;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.StreamingOutput;
+import javax.ws.rs.core.UriInfo;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionMarshaller.Format;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionNotFoundException;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistry;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryException;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionRegistryUsageException;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollectionList;
+
+@Path("/")
+public class VirtualCollectionRegistryRestService {
+    private final VirtualCollectionRegistry registry =
+        VirtualCollectionRegistry.instance();
+    @Context
+    SecurityContext security;
+    @Context
+    UriInfo uriInfo;
+    @Context
+    HttpHeaders headers;
+
+    @POST
+    @Path("/virtualcollection")
+    @Consumes({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response createVirtualCollection(InputStream input)
+            throws VirtualCollectionRegistryException {
+        Principal principal = security.getUserPrincipal();
+        if (principal == null) {
+            String path = uriInfo.getPath();
+            if (path.endsWith("/")) {
+                /*
+                 * fix bad client request and remove tailing slash
+                 */
+                path = path.substring(0, path.length() - 1);
+                URI uri = uriInfo.getBaseUriBuilder().path(path).build();
+                return Response.temporaryRedirect(uri).build();
+            }
+            /*
+             * should never happen, because servlet container should supply a
+             * valid principal
+             */
+            throw new AssertionError("principal == null");
+        }
+        try {
+            Format format = getInputFormat();
+            String encoding = getInputEncoding();
+            VirtualCollection vc =
+                registry.getMarshaller().unmarshal(input, format, encoding);
+            long id = registry.createVirtualCollection(principal, vc);
+            RestResponse response = new RestResponse();
+            response.setIsSuccess(true);
+            response.setInfo("created");
+            response.setId(id);
+            URI uri =
+                uriInfo.getRequestUriBuilder().path(Long.toString(id)).build();
+            return Response.created(uri).entity(response).build();
+        } catch (IOException e) {
+            throw new VirtualCollectionRegistryException("create", e);
+        }
+    }
+
+    @GET
+    @Path("/virtualcollection/{id}")
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response getVirtualCollection(@PathParam("id") long id)
+            throws VirtualCollectionRegistryException {
+        final VirtualCollection vc = registry.retrieveVirtualCollection(id);
+        // XXX: what about non-public VCs?
+        StreamingOutput writer = new StreamingOutput() {
+            public void write(OutputStream output) throws IOException,
+                    WebApplicationException {
+                final Format format = getOutputFormat();
+                registry.getMarshaller().marshal(output, format, vc);
+                output.close();
+            }
+        };
+        return Response.ok(writer).build();
+    }
+
+    @PUT
+    @Path("/virtualcollection/{id}")
+    @Consumes({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response updateVirtualCollection(@PathParam("id") long id,
+            InputStream input) throws VirtualCollectionRegistryException {
+        Principal principal = security.getUserPrincipal();
+        if (principal == null) {
+            throw new NullPointerException("princial == null");
+        }
+        try {
+            Format format = getInputFormat();
+            String encoding = getInputEncoding();
+            VirtualCollection vc =
+                registry.getMarshaller().unmarshal(input, format, encoding);
+            registry.updateVirtualCollection(principal, id, vc);
+            RestResponse response = new RestResponse();
+            response.setIsSuccess(true);
+            response.setInfo("updated");
+            response.setId(id);
+            return Response.ok(response).build();
+        } catch (IOException e) {
+            throw new VirtualCollectionRegistryException("update", e);
+
+        }
+    }
+
+    @DELETE
+    @Path("/virtualcollection/{id}")
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response deleteVirtualCollection(@PathParam("id") long id)
+            throws VirtualCollectionRegistryException {
+        Principal principal = security.getUserPrincipal();
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        registry.deleteVirtualCollection(principal, id);
+        RestResponse response = new RestResponse();
+        response.setIsSuccess(true);
+        response.setInfo("deleted");
+        response.setId(id);
+        return Response.ok(response).build();
+    }
+
+    @GET
+    @Path("/virtualcollection/{id}/state")
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response getVirtualCollectionState(@PathParam("id") long id)
+        throws VirtualCollectionRegistryException {
+        VirtualCollection.State state = registry.getVirtualCollectionState(id);
+        State result = null;
+        switch (state) {
+        case PUBLIC_PENDING:
+            /* FALL-THROUGH */
+        case PUBLIC:
+            result = State.PUBLIC;
+            break;
+        default:
+            result = State.PRIVATE;
+        } // switch
+        return Response.ok(result).build();
+    }
+
+    @POST
+    @Path("/virtualcollection/{id}/state")
+    @Consumes({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response setVirtualCollectionState(@PathParam("id") long id,
+            State state)
+            throws VirtualCollectionRegistryException {
+        Principal principal = security.getUserPrincipal();
+        if (principal == null) {
+            throw new NullPointerException("principal == null");
+        }
+        if (state == null) {
+            throw new VirtualCollectionRegistryUsageException("invalid state");
+        }
+        VirtualCollection.State vc_state = null;
+        switch (state) {
+        case PUBLIC:
+            vc_state = VirtualCollection.State.PUBLIC_PENDING;
+            break;
+        case PRIVATE:
+            vc_state = VirtualCollection.State.PRIVATE;
+            break;
+        default:
+            throw new VirtualCollectionRegistryUsageException("invalid state");
+        }
+        registry.setVirtualCollectionState(principal, id, vc_state);
+        RestResponse response = new RestResponse();
+        response.setIsSuccess(true);
+        response.setInfo("updated state to '" + state + "'");
+        response.setId(id);
+        return Response.ok(response).build();
+    }
+
+    @GET
+    @Path("/virtualcollections")
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response getVirtualCollections(@QueryParam("q") String query,
+            @DefaultValue("0") @QueryParam("offset") int offset,
+            @DefaultValue("-1") @QueryParam("count") int count)
+            throws VirtualCollectionRegistryException {
+        final VirtualCollectionList vcs = registry.getVirtualCollections(query,
+                (offset > 0) ? offset : 0, count);
+        StreamingOutput writer = new StreamingOutput() {
+            public void write(OutputStream output) throws IOException,
+                    WebApplicationException {
+                final Format format = getOutputFormat();
+                registry.getMarshaller().marshal(output, format, vcs);
+                output.close();
+            }
+        };
+        return Response.ok(writer).build();
+    }
+
+    @GET
+    @Path("/my-virtualcollections")
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML,
+                MediaType.APPLICATION_JSON })
+    public Response getMyVirtualCollections(@QueryParam("q") String query,
+            @DefaultValue("0") @QueryParam("offset") int offset,
+            @DefaultValue("-1") @QueryParam("count") int count)
+            throws VirtualCollectionRegistryException {
+        Principal principal = security.getUserPrincipal();
+        if (principal == null) {
+            String path = uriInfo.getPath();
+            if (path.endsWith("/")) {
+                /*
+                 * fix bad client request and remove tailing slash
+                 */
+                path = path.substring(0, path.length() - 1);
+                URI uri = uriInfo.getBaseUriBuilder().path(path).build();
+                return Response.seeOther(uri).build();
+            }
+            /*
+             * should never happen, because servlet container should supply a
+             * valid principal
+             */
+            throw new AssertionError("principal == null");
+        }
+        final VirtualCollectionList vcs = registry.getVirtualCollections(
+                principal, query, (offset > 0) ? offset : 0, count);
+        StreamingOutput writer = new StreamingOutput() {
+            public void write(OutputStream output) throws IOException,
+                    WebApplicationException {
+                final Format format = getOutputFormat();
+                registry.getMarshaller().marshal(output, format, vcs);
+                output.close();
+            }
+        };
+        return Response.ok(writer).build();
+    }
+
+    @GET
+    @Path("/clarin-virtualcollection/{id}")
+    @Produces({ MediaType.TEXT_XML,
+                MediaType.APPLICATION_XML })
+    public Response getClarinVirtualCollection(@PathParam("id") long id)
+            throws VirtualCollectionRegistryException {
+        final VirtualCollection vc = registry.retrieveVirtualCollection(id);
+        if (!vc.isPublic() || (vc.getPersistentIdentifier() == null)) {
+            throw new VirtualCollectionNotFoundException(id);
+        }
+        StreamingOutput writer = new StreamingOutput() {
+            public void write(OutputStream output) throws IOException,
+                    WebApplicationException {
+                registry.getMarshaller().marshalAsCMDI(output, Format.XML, vc);
+                output.close();
+            }
+        };
+        return Response.ok(writer).build();
+    }
+
+    private Format getInputFormat() {
+        Format format = getMediaType(headers.getMediaType());
+        return (format != null) ? format : Format.UNSUPPORTED;
+    }
+
+    private String getInputEncoding() {
+        String encoding =
+            headers.getMediaType().getParameters().get("encoding");
+        return (encoding != null) ? encoding : "utf-8";
+    }
+
+    private Format getOutputFormat() {
+        for (MediaType type : headers.getAcceptableMediaTypes()) {
+            Format format = getMediaType(type);
+            if (format != null) {
+                return format;
+            }
+        }
+        return Format.UNSUPPORTED;
+    }
+
+    private static Format getMediaType(MediaType type) {
+        if (type.isCompatible(MediaType.APPLICATION_XML_TYPE) ||
+            type.isCompatible(MediaType.TEXT_XML_TYPE)) {
+            return Format.XML;
+        }
+        if (type.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
+            return Format.JSON;
+        }
+        return null;
+    }
+
+} // class VirtualCollectionRegistryRestService
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/jjtree/eu/clarin/cmdi/virtualcollectionregistry/query/QueryParser.jjt
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/jjtree/eu/clarin/cmdi/virtualcollectionregistry/query/QueryParser.jjt	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/jjtree/eu/clarin/cmdi/virtualcollectionregistry/query/QueryParser.jjt	(revision 1025)
@@ -0,0 +1,158 @@
+options {
+    DEBUG_PARSER      = false;
+    MULTI             = true;
+    NODE_DEFAULT_VOID = true;
+    STATIC            = false;
+    VISITOR           = true;
+}
+PARSER_BEGIN(QueryParser)
+package eu.clarin.cmdi.virtualcollectionregistry.query;
+
+public class QueryParser {
+    public QueryParser(String s) {
+        this(new java.io.StringReader(s));
+    }
+} // class QueryParser
+PARSER_END(QueryParser)
+/*
+  (vc.)name:           STRING (EQ, NE)
+  (vc.)description:    STRING (EQ, NE)
+  (vc.)state:          { "public", "private", "deleted" } (EQ, NE)
+  {vc.}purpose:        { "research", "reference",
+                         "sample", "future-use" } (EQ, NE)
+  {vc.}reproducibility { "intended", "fluctuating", "untended" } (EQ, NE)
+  (vc.)created:        ISO-DATE (EQ, NE, GT, GE, LT, LE)
+  {vc.}modified:       ISO-DATE (EQ, NE, GT, GE, LT, LE)
+  {vc.}owner           STRING (EQ, NE)
+  (cr.)creator:        STRING (EQ, NE)
+  {cr.}email:          STRING (EQ, NE)
+  {cr.}organization:   STRING (EQ, NE)
+*/
+SKIP: {
+    " "
+    | "\t"
+    | "\n"
+    | "\r"
+}
+TOKEN: {
+      <VC_NAME: "name">
+    | <VC_DESC: "description">
+    | <VC_CREATED: "created">
+    | <VC_MODIFIED: "modified">
+    | <VC_STATE: "state">
+    | <VC_PURPOSE: "purpose">
+    | <VC_REPRODUCIBILITY: "reproducibility">
+    | <VC_OWNER: "owner">
+    | <CR_PERSON: "person">
+    | <CR_ORGANIZATION: "organization">
+    | <CR_EMAIL: "email">
+    | <AND: "and">
+    | <OR: "or">
+    | <EQ: "=">
+    | <NE: "<>">
+    | <GT: ">">
+    | <GE: ">=">
+    | <LT: "<">
+    | <LE: "<=">
+    | <LPAREN: "(">
+    | <RPAREN: ")">
+    | <STATE_PUBLIC: "public">
+    | <STATE_PRIVATE: "private">
+    | <STATE_DELETED: "deleted">
+    | <PURPOSE_RESEARCH: "research">
+    | <PURPOSE_REFERENCE: "reference">
+    | <PURPOSE_SAMPLE: "sample">
+    | <PURPOSE_FUTURE_USE: "future-use">
+    | <REPRODUCIBILITY_INTENDED: "intended">
+    | <REPRODUCIBILITY_FLUCTUATING: "fluctuating">
+    | <REPRODUCIBILITY_UNTENDED: "untended">
+    | <#DIGIT: ["0"-"9"]>
+    | <ISO_DATE: <DIGIT> <DIGIT> <DIGIT> <DIGIT> "-"
+                 <DIGIT> <DIGIT> "-"
+                 <DIGIT> <DIGIT> "T"
+                 <DIGIT> <DIGIT> ":"
+                 <DIGIT> <DIGIT> ":"
+                 <DIGIT> <DIGIT> "Z">
+    | <QUOTED_STRING: "\"" (~["\""])+ "\"" >
+}
+ASTStart start() #Start : {
+}
+{
+    expressionOr() <EOF>
+    { return jjtThis; }
+}
+private void expressionOr() : {
+}
+{
+    (expressionAnd() (<OR> expressionAnd())*) #Or(>1)
+}
+private void expressionAnd() : {
+}
+{
+    (term() (<AND> term())*) #And(>1)
+}
+private void term() : {
+}
+{
+    predicate()
+    | (<LPAREN> expressionOr() <RPAREN>)
+}
+private void predicate() #Predicate : {
+    Token attribute = null;
+    Token operator = null;
+    Token value = null;
+}
+{
+    (
+     ( ( attribute = <VC_NAME>
+       | attribute = <VC_DESC>
+       | attribute = <VC_OWNER>
+       | attribute = <CR_PERSON>
+       | attribute = <CR_ORGANIZATION>
+       | attribute = <CR_EMAIL> )
+       ( operator = <EQ>
+       | operator = <NE> )
+       value = <QUOTED_STRING> )
+     | ( attribute = <VC_STATE>
+        ( operator = <EQ>
+        | operator = <NE> )
+        ( value = <STATE_PUBLIC>
+        | value = <STATE_PRIVATE>
+        | value = <STATE_DELETED> ) )
+     | ( ( attribute = <VC_CREATED>
+         | attribute = <VC_MODIFIED> )
+         ( operator = <EQ>
+         | operator = <NE>
+         | operator = <GT>
+         | operator = <GE>
+         | operator = <LT>
+         | operator = <LE> )
+         value = <ISO_DATE> )
+     | ( attribute = <VC_PURPOSE>
+        ( operator = <EQ>
+        | operator = <NE> )
+        ( value = <PURPOSE_RESEARCH>
+        | value = <PURPOSE_REFERENCE>
+        | value = <PURPOSE_SAMPLE>
+        | value = <PURPOSE_FUTURE_USE> ) )
+     | ( attribute = <VC_REPRODUCIBILITY>
+        ( operator = <EQ>
+        | operator = <NE> )
+        ( value = <REPRODUCIBILITY_INTENDED>
+        | value = <REPRODUCIBILITY_FLUCTUATING>
+        | value = <REPRODUCIBILITY_UNTENDED> ) )
+    )
+    {
+        if ((attribute == null) || (operator == null) || (value == null)) {
+            throw new ParseException("internal error while parsing query");
+        }
+        // strip quotes from QUOTED_STRING
+        String v = value.image;
+        if (value.kind == QUOTED_STRING) {
+            v = v.substring(1, v.length() - 1);
+        }
+        jjtThis.setAttribute(attribute.kind);
+        jjtThis.setOperator(operator.kind);
+        jjtThis.setValue(v);
+    }
+}
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/resources/META-INF/VirtualCollection.xsd
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/resources/META-INF/VirtualCollection.xsd	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/resources/META-INF/VirtualCollection.xsd	(revision 1025)
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  elementFormDefault="unqualified">
+
+  <xs:element name="VirtualCollections">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element ref="VirtualCollection" minOccurs="0" maxOccurs="unbounded"/>
+      </xs:sequence>
+      <xs:attribute name="totalCount" type="xs:integer"/>
+      <xs:attribute name="offset" type="xs:integer"/>
+      <xs:attribute name="result">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+            <xs:enumeration value="full"/>
+            <xs:enumeration value="partial"/>
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:attribute>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="VirtualCollection">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="Type">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+              <xs:enumeration value="extensional"/>
+              <xs:enumeration value="intensional"/>
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="Name" type="xs:string"/>
+        <xs:element name="Description" type="xs:string" minOccurs="0"/>
+        <xs:element name="CreationDate" type="xs:date" minOccurs="0"/>
+        <xs:element name="Creators" minOccurs="0">
+          <xs:complexType>
+            <xs:sequence>
+              <xs:element name="Creator" minOccurs="1" maxOccurs="unbounded">
+                <xs:complexType>
+                    <xs:sequence>
+                      <xs:element name="Person" type="xs:string"/>
+                      <xs:element name="Address" type="xs:string" minOccurs="0"/>
+                      <xs:element name="Email" type="xs:string" minOccurs="0"/>
+                      <xs:element name="Organisation" type="xs:string" minOccurs="0"/>
+                      <xs:element name="Telephone" type="xs:string" minOccurs="0" />
+                      <xs:element name="Website" type="xs:anyURI" minOccurs="0" />
+                      <xs:element name="Role" type="xs:string" minOccurs="0" />
+                    </xs:sequence>
+                </xs:complexType>
+              </xs:element>
+            </xs:sequence>
+          </xs:complexType>
+        </xs:element>
+        <xs:element name="Purpose" minOccurs="0">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+              <xs:enumeration value="research"/>
+              <xs:enumeration value="reference"/>
+              <xs:enumeration value="sample"/>
+              <xs:enumeration value="future-use"/>
+              <!-- to be extended -->
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="Reproducibility" minOccurs="0">
+          <xs:simpleType>
+            <xs:restriction base="xs:string">
+              <xs:enumeration value="intended"/>
+              <xs:enumeration value="fluctuating"/>
+              <xs:enumeration value="untended"/>
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="ReproducibilityNotice" type="xs:string" minOccurs="0" />
+        <xs:element name="Keywords" minOccurs="0">
+          <xs:complexType>
+        	<xs:sequence>
+        	  <xs:element name="Keyword" type="xs:string" minOccurs="1" maxOccurs="unbounded"/>
+        	</xs:sequence>
+          </xs:complexType>
+        </xs:element>
+        <xs:element name="Resources" minOccurs="0">
+          <xs:complexType>
+            <xs:sequence>
+              <xs:element name="Resource" minOccurs="1" maxOccurs="unbounded">
+                <xs:complexType>
+                  <xs:sequence>
+                    <xs:element name="ResourceType">
+                      <xs:simpleType>
+                        <xs:restriction base="xs:string">
+                          <xs:enumeration value="Metadata"/>
+                          <xs:enumeration value="Resource"/>
+                        </xs:restriction>
+                      </xs:simpleType>
+                    </xs:element>
+                    <xs:element name="ResourceRef">
+                      <xs:simpleType>
+                        <xs:restriction base="xs:anyURI"/>
+                      </xs:simpleType>
+                    </xs:element>
+                  </xs:sequence>
+                </xs:complexType>
+              </xs:element>
+            </xs:sequence>
+          </xs:complexType>
+        </xs:element>
+        <xs:element name="GeneratedBy" minOccurs="0">
+          <xs:complexType>
+            <xs:sequence>
+              <xs:element name="Description" type="xs:string"/>
+              <xs:element name="URI" type="xs:anyURI" minOccurs="0"/>
+              <xs:element name="Query" minOccurs="0">
+                <xs:complexType>
+                  <xs:simpleContent>
+                    <xs:extension base="xs:string">
+                      <xs:attribute name="profile" type="xs:string" use="required"/>
+                    </xs:extension>
+                  </xs:simpleContent>
+                </xs:complexType>
+              </xs:element>
+            </xs:sequence>
+          </xs:complexType>
+        </xs:element>
+      </xs:sequence>
+      <xs:attribute name="id" type="xs:long" use="optional" />
+      <xs:attribute name="persistentId" type="xs:string" use="optional" />
+      <xs:attribute name="state" use="optional">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+            <xs:enumeration value="private" />
+            <xs:enumeration value="public-pending" />
+            <xs:enumeration value="public" />
+            <xs:enumeration value="deleted" />
+            <xs:enumeration value="dead" />
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:attribute>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/resources/META-INF/persistence.xml
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/resources/META-INF/persistence.xml	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/resources/META-INF/persistence.xml	(revision 1025)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
+	version="2.0">
+	<persistence-unit name="VirtualCollectionStore"
+		transaction-type="RESOURCE_LOCAL">
+		<provider>org.hibernate.ejb.HibernatePersistence</provider>
+		<jta-data-source>java:comp/env/jdbc/VirtualCollectionStore</jta-data-source>
+		<class>eu.clarin.cmdi.virtualcollectionregistry.model.User</class>
+		<class>eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection</class>
+		<class>eu.clarin.cmdi.virtualcollectionregistry.model.Creator</class>
+		<class>eu.clarin.cmdi.virtualcollectionregistry.model.Resource</class>
+		<class>eu.clarin.cmdi.virtualcollectionregistry.model.PersistentIdentifier</class>
+		<properties>
+			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
+			<property name="hibernate.max_fetch_depth" value="3" />
+			<property name="hibernate.hbm2ddl.auto" value="update" />
+			<property name="hibernate.show_sql" value="false" />
+			<property name="hibernate.format_sql" value="false" />
+		</properties>
+	</persistence-unit>
+</persistence>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/META-INF/MANIFEST.MF
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/META-INF/MANIFEST.MF	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/META-INF/MANIFEST.MF	(revision 1025)
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path: 
+
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/WEB-INF/web.xml
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/WEB-INF/web.xml	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/WEB-INF/web.xml	(revision 1025)
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  id="WebApp_ID" version="2.5">
+  <display-name>CLARIN Virtual Collection Registry</display-name>
+
+  <listener>
+    <listener-class>eu.clarin.cmdi.virtualcollectionregistry.rest.ContextListener</listener-class>
+  </listener>
+
+  <filter>
+    <filter-name>PersistenceFilter</filter-name>
+    <filter-class>eu.clarin.cmdi.virtualcollectionregistry.rest.PersistenceFilter</filter-class>
+  </filter>
+
+  <filter-mapping>
+    <filter-name>PersistenceFilter</filter-name>
+    <servlet-name>REST-Web-Service</servlet-name>
+  </filter-mapping>
+
+  <filter>
+    <filter-name>CharacterEncodingFilter</filter-name>
+    <filter-class>eu.clarin.cmdi.virtualcollectionregistry.gui.CharacterEncodingFilter</filter-class>
+  </filter>
+
+  <filter-mapping>
+    <filter-name>CharacterEncodingFilter</filter-name>
+    <url-pattern>/app/*</url-pattern>
+  </filter-mapping>
+
+  <filter>
+    <filter-name>AuthFilter</filter-name>
+    <filter-class>eu.clarin.cmdi.virtualcollectionregistry.gui.auth.AuthFilter</filter-class>
+  </filter>
+
+  <filter-mapping>
+    <filter-name>AuthFilter</filter-name>
+    <url-pattern>/app/*</url-pattern>
+  </filter-mapping>
+
+  <filter>
+    <filter-name>VirtualCollectionRegistryApplication</filter-name>
+    <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
+    <init-param>
+        <param-name>applicationClassName</param-name>
+        <param-value>eu.clarin.cmdi.virtualcollectionregistry.gui.Application</param-value>
+    </init-param>
+    <init-param>
+        <param-name>configuration</param-name>
+        <!-- 
+        <param-value>deployment</param-value>
+         -->
+        <param-value>development</param-value>
+    </init-param>
+  </filter>
+
+  <filter-mapping>
+    <filter-name>VirtualCollectionRegistryApplication</filter-name>
+    <url-pattern>/app/*</url-pattern>
+  </filter-mapping>
+
+  <servlet>
+    <display-name>Virtual Collection Registry REST web service</display-name>
+    <servlet-name>REST-Web-Service</servlet-name>
+    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+    <init-param>
+      <param-name>com.sun.jersey.config.property.packages</param-name>
+      <param-value>eu.clarin.cmdi.virtualcollectionregistry.rest</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>REST-Web-Service</servlet-name>
+    <url-pattern>/service/*</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <display-name>Virtual Collection Registry OAI-PMH Provider</display-name>
+    <servlet-name>OAI-PMH-Provider</servlet-name>
+    <servlet-class>eu.clarin.cmdi.oai.provider.ProviderServlet</servlet-class>
+    <load-on-startup>2</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>OAI-PMH-Provider</servlet-name>
+    <url-pattern>/oai</url-pattern>
+  </servlet-mapping>
+
+  <security-constraint>
+    <display-name>REST-Web-Service</display-name>
+    <web-resource-collection>
+      <web-resource-name>REST-Web-Service</web-resource-name>
+      <description></description>
+      <url-pattern>/service/virtualcollection</url-pattern>
+      <http-method>POST</http-method>
+    </web-resource-collection>
+    <web-resource-collection>
+      <web-resource-name>REST-Web-Service</web-resource-name>
+      <description></description>
+      <url-pattern>/service/virtualcollection/*</url-pattern>
+      <http-method>DELETE</http-method>
+      <http-method>POST</http-method>
+      <http-method>PUT</http-method>
+    </web-resource-collection>
+    <web-resource-collection>
+      <web-resource-name>REST-Web-Service</web-resource-name>
+      <description></description>
+      <url-pattern>/service/my-virtualcollections</url-pattern>
+      <http-method>GET</http-method>
+    </web-resource-collection>
+    <web-resource-collection>
+        <web-resource-name>VirtualCollectionRegistryApplication</web-resource-name>
+        <description></description>
+        <url-pattern>/app/login</url-pattern>
+        <http-method>GET</http-method>
+        <http-method>POST</http-method>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>*</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <resource-ref>
+    <description>Virtual Collection Datastore Connection</description>
+    <res-ref-name>jdbc/VirtualCollectionStore</res-ref-name>
+    <res-type>javax.sql.DataSource</res-type>
+    <res-auth>Container</res-auth>
+  </resource-ref>
+
+  <login-config>
+    <auth-method>BASIC</auth-method>
+  </login-config>
+
+  <security-role>
+    <role-name>*</role-name>
+  </security-role>
+
+  <mime-mapping>
+    <extension>xsd</extension>
+    <mime-type>application/xml</mime-type>
+  </mime-mapping>
+
+  <welcome-file-list>
+    <welcome-file>index.jsp</welcome-file>
+  </welcome-file-list>
+</web-app>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/css/vcr.css
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/css/vcr.css	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/css/vcr.css	(revision 1025)
@@ -0,0 +1,933 @@
+* {
+    margin: 0;
+/*    padding: 0; */	
+}
+
+html {
+	overflow-y: scroll
+}
+
+html, body {
+	font-family: Verdana, sans-serif;
+	font-size: 12px;
+	margin: 0 auto;
+	padding: 0;
+	line-height: 1;
+    height: 100%;
+    color: #000000;
+/*    background-color: #E9EEFA; */
+    width: 990px;
+    min-width: 990px;
+    height: 100%;
+}
+
+noscript {
+	margin: 0 auto;
+	padding: 0;
+	width: 1024px;
+}
+
+#noscript-warning {
+    color: #FFFFFF;
+    background-color: #AE0000;
+    font-size: 13px;
+    font-weight: bold;
+    text-align: center;
+    line-height: 24px;
+    margin: 0 auto;
+    padding: 0;
+    text-align: center;
+    vertical-align: middle;
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    z-index: 101;
+}
+
+#header {
+    border-top: 2px solid #D4DEEF;
+}
+	
+#header-logo {
+	margin: 0;
+	padding: 5px; 
+    height: 70px;
+    color: #004071;
+    background-color: #E9EEF7;
+}
+	
+#header-logo #clarin-logo {
+	background: transparent url('../images/clarin-logo.png') no-repeat;
+	float: left;
+	width: 298px;
+    height: 70px;
+}
+
+#header-logo #ids-logo {
+    background: transparent url('../images/ids-logo.png') no-repeat;
+    float: right;
+    width: 55px;
+    height: 70px;
+}
+
+#header-logo h1 {
+    margin: 0 55px 0 298px;
+    padding: 0 32px 0 32px;
+    height: 70px;
+    line-height: 70px;
+    font-size: 34px;
+    font-weight: bold;
+    font-variant: small-caps;
+}
+
+#navigation {
+	margin: 0;
+	padding: 0;
+	height: 20px;
+	clear: both;
+    color: #000000;
+    background-color: #D4DEEF;
+    font-size: 12px;
+    line-height: 12px;
+    vertical-align: middle;
+}
+	
+#menu {
+    margin: 0 100px 0 0;
+}
+	
+#menu ul {
+	list-style-type: none;
+    margin: 0;
+    padding: 0;
+}
+
+#menu li {
+    float: left;
+    margin-left: 4px;
+}
+
+#menu a, #menu span.active {
+    display: block;
+    margin: 0;
+    padding: 4px 8px;
+    width: auto;
+    height: 12px;
+    text-align: center;
+    text-decoration: none;
+    font-weight: bold;
+}
+
+#menu ul li + #menu ul li {
+	border-left: 1px solid black;
+}
+	
+#menu a, #menu a:active, #menu a:visited {
+	color: #000000;
+	background-color: inherit;
+}
+
+#menu a:hover {
+	color: #AE0000;
+	background-color: #E9EEF7;
+}
+
+#menu span.active {
+    font-style: italic;
+}
+	
+#login {
+	float: right;
+    padding: 4px 4px 4px 4px;
+    width: auto;
+}
+
+#container {
+    min-height: 100%;
+    height: auto !important;
+    height: 100%;
+    margin: 0 auto -20px; /* the bottom margin is the negative value of the footer's height */
+    color: #000000;
+    background-color: #FFFFFF;
+    border-left: 2px solid #D4DEEF;
+    border-right: 2px solid #D4DEEF;
+}
+
+#content {
+    margin: 0;
+    padding: 0;
+}
+
+#container .push {
+	height: 20px;
+    width: auto;
+}
+
+#footer {
+	padding: 0 4px;
+	margin: 0 auto;
+	height: 18px;
+    line-height: 18px;
+    text-align: right;
+    color: #004071;
+    background-color: #D4DEEF;
+    border-left: 2px solid #D4DEEF;
+    border-right: 2px solid #D4DEEF;
+    border-bottom: 2px solid #D4DEEF;
+}
+
+/* general */
+input, select, textarea {
+    font-family: Verdana, sans-serif;
+    font-size: 100%;
+    border: 1px solid #000000;
+}
+
+/*
+ * wizard
+ */
+div.wizard .wicketExtensionsWizardHeaderTitle {
+	padding: 0px 10px;
+	font-size: 16px;
+	font-weight: bold;
+}
+
+div.wizard .wicketExtensionsWizardHeaderSummary {
+	padding: 5px 10px 0 10px;
+	font-style: italics;
+}
+
+div.wizard .wizardHeader {
+	border-bottom: 1px solid #000000;
+    margin: 0 0 10px 0;
+	padding: 0 0 10px 0;
+}
+
+form.wizard {
+  margin: 5px 4px;
+  padding: 0;
+  font-size: 100%;
+  width: 978px;
+  min-width: 978px;
+}
+
+form.wizard fieldset,
+div.wizard div.fieldset {
+  clear: both;
+  font-size: 100%;
+  border: none;
+  padding: 0 10px 0 10px;
+  margin: 0 0 0 0;
+}
+
+/*
+form.wizard fieldset legend {
+  font-size: 150%;
+  font-weight: normal;
+  color: #000000;
+  margin: 0 0 0 0;
+  padding: 0 5px;
+}
+
+div.wizard label {
+  font-size: 100%;
+}
+
+div.wizard label u {
+  font-style: normal;
+  text-decoration: underline;
+}
+
+div.wizard input, div.wizard select, div.wizard textarea {
+  font-family: Tahoma, Arial, sans-serif; 
+  font-size: 100%;
+  color: #000000;
+}
+
+div.wizard textarea {
+  overflow: auto;
+}
+*/
+
+form.wizard div,
+div.editDialog div {
+  clear: left;
+  display: block;
+/*  width: 354px;
+  zoom: 1;
+  margin: 5px 0 0 0;
+  padding: 1px 3px;
+  */
+}
+
+form.wizard div.notes {
+  float: right;
+  width: 198px;
+  height: auto;
+  margin: 0 0 10px 10px;
+  padding: 5px;
+  border: 1px solid #666666;
+  background-color: #ffffe1;
+  color: #666666;
+}
+
+form.wizard div.notes h4 {
+  /*
+  background-image: url(/images/icon_info.gif);
+  background-repeat: no-repeat;
+  background-position: top left;
+  */
+  text-align: center;
+  padding: 3px 0 3px 0; /*27px; */
+  margin: 0 0 3px 0;
+  border-width: 0 0 1px 0;
+  border-style: solid;
+  border-color: #666666;
+  color: #666666;
+  font-size: 100%;
+}
+
+form.wizard div.notes p {
+  margin: 0em 0em 0.5em 0em;
+  color: #666666;
+  font-size: 80%;
+}
+
+form.wizard div.notes p.last {
+  margin: 0em;
+}
+
+form.wizard div label {
+  display: block;
+  float: left;
+  width: 140px;
+  padding: 3px 5px;
+  margin: 0 0 5px 0;
+  text-align: right;
+  font-weight: normal;
+}
+
+div.required label,
+label.required {
+  font-weight: bold !important;
+}
+
+/*
+div.optional label:after {
+  content: ':';
+}
+
+div.required label:after {
+  content: ':';
+}
+*/
+
+form.wizard textarea {
+	font-family: Verdana, sans-serif;
+	font-size: 100%;
+	width: 570px;
+	overflow: auto;
+	margin-bottom: 5px;
+}
+
+
+/*
+form.wizard div label.labelCheckbox, form div label.labelRadio {
+  float: none;
+  display: block;
+  width: 200px;
+  zoom: 1;
+  padding: 0;
+  margin: 0 0 5px 142px;
+  text-align: left;
+}
+
+form.wizard div fieldset label.labelCheckbox, form div fieldset label.labelRadio {
+  margin: 0 0 5px 0;
+  width: 170px;
+}
+
+form.wizard div img {
+  float: left;
+  border: 1px solid #000000;
+  margin: 0 0 5px 0;
+}
+
+p.error {
+  background-color: #ff0000;
+  background-image: url(/images/icon_error.gif);
+  background-repeat: no-repeat;
+  background-position: 3px 3px;
+  color: #ffffff;
+  padding: 3px 3px 5px 27px;
+  border: 1px solid #000000;
+  margin: auto 100px;
+}
+
+form.wizard div.error {
+  background-color: #ffffe1;
+  background-image: url(/images/required_bg.gif);
+  background-repeat: no-repeat;
+  background-position: top left;
+  color: #666666;
+  border: 1px solid #ff0000;
+}
+
+form.wizard div.error p.error {
+  background-image: url(/images/icon_error.gif);
+  background-position: top left;
+  background-color: transparent;
+  border-style: none;
+  font-size: 88%;
+  font-weight: bold;
+  margin: 0 0 0 118px;
+  width: 200px;
+  color: #ff0000;
+}
+
+form.wizard div select, form div textarea {
+  width: 200px;
+  padding: 1px 3px;
+  margin: 0 0 0 0;
+}
+
+form.wizard div input.inputText, form div input.inputPassword {
+  width: 200px;
+  padding: 1px 3px;
+  margin: 0 0 0 0;
+}
+
+form.wizard div input.inputFile {
+  width: 211px;
+}
+
+form.wizard div select.selectOne, form div select.selectMultiple {
+  width: 211px;
+  padding: 1px 3px;
+}
+
+form.wizard div input.inputCheckbox, form div input.inputRadio, input.inputCheckbox, input.inputRadio {
+  display: inline;
+  height: auto;
+  width: auto;
+  background-color: transparent;
+  border-width: 0;
+  padding: 0;
+  margin: 0 0 0 140px;
+}
+
+form.wizard div.submit {
+  width: 214px;
+  padding: 0 0 0 146px;
+}
+
+form.wizard div.submit div {
+  display: inline;
+  float: left;
+  text-align: left;
+  width: auto;
+  padding: 0;
+  margin: 0;
+}
+
+form.wizard div input.inputSubmit,
+form div input.inputButton, input.inputSubmit, input.inputButton {
+  background-color: #cccccc;
+  color: #000000;
+  width: auto;
+  padding: 0 6px;
+  margin: 0;
+}
+
+form.wizard div.submit div input.inputSubmit,
+form div.submit div input.inputButton {
+  float: right;
+  margin: 0 0 0 5px;
+}
+
+form.wizard div small {
+  display: block;
+  margin: 0 0 5px 142px;
+  padding: 1px 3px;
+  font-size: 88%;
+  zoom: 1;
+}
+*/
+
+form.wizard .wizardButtons {
+    border-top: 1px solid #000000;
+    margin: 10px 0 0 0;
+    clear: both;
+}
+	
+form.wizard .buttonbar {
+	margin: 10px 220px 0 0;
+	padding: 0 10px 0 0;
+	text-align: right; 
+}
+
+/*
+form.wizard .buttonbar input.button,
+div.modalDialog div.modalDialogButtons input.button
+*/
+input.button {
+	padding: 1px 4px;
+	margin: 0 2px;
+	color: #000000;
+	background-color: #E9EEF7;
+	border: 2px solid #D4DEEF;
+	border-radius: 4px;
+    -moz-border-radius: 4px;
+}
+
+form.wizard .buttonbar input.button:hover,
+div.modalDialog div.modalDialogButtons input.button:hover {
+    color: #AE0000;
+    background-color: #E9EEF7;
+}
+
+form.wizard .buttonbar  input.button[disabled],
+form.wizard .buttonbar  input.button[disabled]:hover,
+div.modalDialog div.modalDialogButtons input.button[disabled],
+div.modalDialog div.modalDialogButtons input.button[disabled]:hover {
+	color: #AFAFAF;
+	border-color: #EFEFEF;
+}
+
+
+/*
+ * modal dialog
+ */
+div.modalDialog {
+	margin: 0;
+	padding: 0;
+}
+
+div.modalDialog div.modalDialogContent {
+	margin: 10px;
+}
+	
+div.modalDialog div.modalDialogButtons {
+	border-top: 1px solid #BBBBBB;
+	margin: 0 5px 5px 5px;
+	padding: 5px 0 0 0;
+	text-align: center;
+}
+
+div.confirmationDialog {
+    text-align: center;
+}
+
+div.editDialog fieldset {
+	border: none;
+	margin: 0;
+	padding: 0;
+}
+
+div.editDialog fieldset label {
+  display: block;
+  float: left;
+  /* width: 100px; */
+  padding: 3px 5px 3px 0;
+  margin: 0 0 5px 0;
+  text-align: right;
+}
+
+
+/*
+ * dialog customizations
+ */
+div.editCreatorDialog  fieldset label {
+	width: 90px;
+}
+
+div.editCreatorDialog fieldset input,
+div.editCreatorDialog fieldset textarea {
+    width: 255px;
+}
+
+div.editResourceDialog fieldset label {
+    width: 70px;
+}
+
+div.editResourceDialog fieldset input,
+div.editResourceDialog fieldset textarea {
+    width: 475px;
+}
+
+div.addResourcesDialog fieldset label {
+	width: 85px;
+}
+
+div.addResourcesDialog fieldset input,
+div.addResourcesDialog fieldset textarea {
+    width: 460px;
+}
+
+div.addKeywordDialog fieldset input {
+	width: 285px;
+}
+
+/*
+ * create virtual collection wizard
+ */
+div.wizard a {
+    font-weight: normal;
+}
+
+
+table {
+    font-size: 100%;
+	border-collapse: collapse;
+}
+
+table th,
+table td {
+	padding: 2px;
+	text-align: left;
+}
+
+table thead,
+table tfoot {
+	color: inherit;
+	background-color: #D4DEEF;
+    border-top: 1px solid #000000;
+	border-bottom: 1px solid #000000;
+}
+
+table td.action {
+	width: 98px;
+	text-align: right;
+}
+
+a {
+	color: #AE0000;
+	background-color: inherit;
+	text-decoration: none;
+}
+
+table tbody {
+	border-bottom: 1px solid #000000;
+}
+
+table tr.even {
+    color: inherit;
+    background-color: #FFFFFF;
+}
+
+table tr.odd {
+    color: inherit;
+    background-color: #E9EEF7;
+}
+
+table td.norecords-td {
+	text-align: center;
+	color: inherit;
+	background-color: #FFFFFF;
+}
+
+table thead tr.navigation {
+	border-bottom: 1px dotted #BBBBBB;
+}
+
+table tr.navigation div {
+	clear: none !important;
+}
+
+table.creatorsTable,
+table.resourcesTable {
+    width: 805px;
+}
+
+table.creatorsTable td {
+    width: 252px;
+    overflow: hidden;
+}
+
+table.resourcesTable td.type {
+	width: 60px;
+}
+
+div.keywordsList {
+	clear: none !important;
+	width: 602px;
+	margin-left: 160px;
+	padding: 3px 0;
+}
+
+div.keywordsList div {
+	clear: none;
+}
+
+div.keywordsList ul {
+    list-style-type: square;
+    margin: 0;
+    padding: 0 0 0 16px;
+}
+
+div.keywordsList ul li {
+	margin-top: 2px;
+}
+
+div.keywordsList ul li.first {
+    margin-top: 0;
+}
+
+table.collectionsTable {
+	width: auto;
+	table-layout: fixed;
+}
+
+table.collectionsTable th,
+table.collectionsTable td {
+    vertical-align: top;
+}
+
+table.collectionsTable.public th.name,
+table.collectionsTable.public td.name {
+	width: 796px;
+}
+
+table.collectionsTable.private th.name,
+table.collectionsTable.private td.name {
+    width: 732px;
+}
+
+table.collectionsTable td.name div.name {
+	padding-left: 16px;
+    background-image: url('../icons/bullet_arrow_down.png');
+    background-repeat: no-repeat;
+    background-position: center left;
+    cursor: pointer;
+}
+
+table.collectionsTable td.name div.name:hover {
+	color: #AE0000;
+	background-color: inherit;
+}
+
+table.collectionsTable td.name div.name.detailsShown {
+    padding-left: 16px;
+    background-image: url('../icons/bullet_arrow_up.png');
+    background-repeat: no-repeat;
+    background-position: center left;
+}
+
+table.collectionsTable td.name div.name.noDetails{
+    background-image: none;
+}
+
+table.collectionsTable td.name div.details {
+	margin-left: 16px;
+    margin-right: -248px;
+    width: 964px;
+}
+
+table.collectionsTable td.name div.desc {
+    text-align: justify;
+    padding: 4px 0 0 0;
+}
+
+table.collectionsTable td.name div.actions {
+	margin: 4px 0 2px 0;
+}
+
+table.collectionsTable th.state,
+table.collectionsTable td.state {
+    width: 60px;
+}
+
+table.collectionsTable th.type,
+table.collectionsTable td.type {
+    width: 80px;
+}
+
+table.collectionsTable th.created,
+table.collectionsTable td.created {
+    width: 80px;
+}
+
+table.collectionsTable td.action,
+table.collectionsTable th.action {
+	width: 12px;
+}
+
+table.collectionsTable.public td.action span,
+table.collectionsTable.private td.action div.popupTrigger span {
+    display: none;
+}
+
+table.collectionsTable.public td.action a {
+    display: block;
+    width: 12px;
+    height: 12px;
+    margin-right: 2px;
+    background: transparent url('../icons/magnifier.png') no-repeat center center;
+}
+
+table.collectionsTable.private td.action div.popupTrigger {
+    display: block;
+    width: 12px;
+    height: 12px;
+    margin-right: 2px;
+    background: transparent url('../icons/wrench.png') no-repeat center center;
+}
+
+tr.headers a.wicket_orderNone {
+	padding-right: 7px;
+	background: transparent url('../icons/order_none.png') no-repeat center right;
+}
+
+tr.headers a.wicket_orderUp {
+    padding-right: 7px;
+    background: transparent url('../icons/order_up.png') no-repeat center right;
+}
+
+tr.headers a.wicket_orderDown {
+    padding-right: 7px;
+    background: transparent url('../icons/order_down.png') no-repeat center right;
+}
+
+div.toggleBorder {
+	border-top: 1px solid #000000;
+}
+
+div.toggleBorder div.toggleBorderHeader {
+	margin: 0;
+	padding: 2px 2px 2px 16px;
+	border-bottom: 1px solid #000000;
+    color: inherit;
+    background-color: #D4DEEF;
+    background-image: url('../icons/bullet_arrow_up.png');
+    background-repeat: no-repeat;
+    background-position: center left;
+    cursor: pointer;
+}
+
+div.toggleBorder div.toggleBorderHeader span {
+	font-weight: bold;
+}
+
+div.toggleBorder div.toggleBorderHeader.collapsed {
+	background-image: url('../icons/bullet_arrow_down.png');
+	background-repeat: no-repeat;
+	background-position: center left;
+}
+
+div.toggleBorder div.toggleBorderContent {
+    margin: 0;
+    padding: 5px;
+}
+
+form.filterForm {
+	margin: 0;
+	padding: 0;
+}
+	
+form.filterForm fieldset {
+	padding: 0;
+	margin: 0;
+	border: none;
+}
+
+form.filterForm div.optional {
+	margin-bottom: 4px;
+}
+
+form.filterForm label {
+	width: 75px !important;
+}
+
+form.filterForm input.name,
+form.filterForm input.desc {
+	width: 790px;
+}
+
+div.popupMenu {
+    -moz-box-shadow: 3px 3px 3px #888;
+    -webkit-box-shadow: 3px 3px 3px #888;
+    box-shadow: 3px 3px 3px #888;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+}
+
+div.popupMenu li a {
+	color: #000000;
+}
+
+div.popupMenu li a:hover {
+    color: #AE0000;
+}
+
+div.popupTrigger span.title {
+    color: #AE0000;
+}
+
+
+div.collectionDetails div.property {
+  clear: both;
+  font-size: 100%;
+  border: none;
+  padding: 0 10px 0 10px;
+  margin: 0 0 4px 0;
+}
+
+div.collectionDetails div label {
+  display: block;
+  float: left;
+  width: 140px;
+  padding: 0px 2px;
+  margin: 0;
+  text-align: right;
+  font-weight: normal;
+}
+
+div.collectionDetails div label:after {
+	content: ':';
+}
+
+div.collectionDetails div div {
+    display: block;
+    clear: none;
+    padding: 0px 2px;
+    margin-left: 144px;
+}
+
+div.collectionDetails ul.keywords {
+    margin: 0 0 0 16px;
+    padding: 0;
+    list-style: square;
+}
+
+div.collectionDetails ul.creators {
+	margin: 0;
+	padding: 0;
+    list-style: none;
+}
+
+div.collectionDetails ul.creators li {
+	border-top: 1px dotted #000000;
+    margin-top: 4px;
+    padding-top: 4px;
+}
+
+div.collectionDetails ul.creators li.first {
+	border-top: none;
+	margin-top: 0;
+	padding-top: 0;
+}
+
+div.collectionDetails.resources {
+	padding: 0 !important;
+}
+
+div.collectionDetails.resources table {
+	width: 100%;
+}
+
+div.collectionDetails.buttons {
+	margin: 10px 0 10px 0;
+	text-align: center;
+}
+
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/index.jsp
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/index.jsp	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/index.jsp	(revision 1025)
@@ -0,0 +1,4 @@
+<%@ page language="java" %>
+<%
+    response.sendRedirect("app/");
+%>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/oai2.xsl
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/oai2.xsl	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/main/webapp/oai2.xsl	(revision 1025)
@@ -0,0 +1,650 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  XSL Transform to convert OAI 2.0 responses into XHTML
+
+  By Christopher Gutteridge, University of Southampton
+
+  v1.1
+
+-->
+<!-- 
+  
+Copyright (c) 2006 University of Southampton, UK. SO17 1BJ.
+
+EPrints 3 is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+EPrints 3 is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with EPrints 3; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+-->
+<!--
+  
+  All the elements really needed for EPrints are done but if
+  you want to use this XSL for other OAI archive you may want
+  to make some minor changes or additions.
+
+  Not Done
+    The 'about' section of 'record'
+    The 'compession' part of 'identify'
+    The optional attributes of 'resumptionToken'
+    The optional 'setDescription' container of 'set'
+
+  All the links just link to oai_dc versions of records.
+
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:oai="http://www.openarchives.org/OAI/2.0/">
+
+<xsl:output method="html"/>
+
+
+
+<xsl:template name="style">
+td.value {
+	vertical-align: top;
+	padding-left: 1em;
+	padding: 3px;
+}
+td.key {
+	background-color: #e0e0ff;
+	padding: 3px;
+	text-align: right;
+	border: 1px solid #c0c0c0;
+	white-space: nowrap;
+	font-weight: bold;
+	vertical-align: top;
+}
+.dcdata td.key {
+	background-color: #ffffe0;
+}
+body { 
+	margin: 1em 2em 1em 2em;
+}
+h1, h2, h3 {
+	font-family: sans-serif;
+	clear: left;
+}
+h1 {
+	padding-bottom: 4px;
+	margin-bottom: 0px;
+}
+h2 {
+	margin-bottom: 0.5em;
+}
+h3 {
+	margin-bottom: 0.3em;
+	font-size: medium;
+}
+.link {
+	border: 1px outset #88f;
+	background-color: #c0c0ff;
+	padding: 1px 4px 1px 4px;
+	font-size: 80%;
+	text-decoration: none;
+	font-weight: bold;
+	font-family: sans-serif;
+	color: black;
+}
+.link:hover {
+	color: red;
+}
+.link:active {
+	color: red;
+	border: 1px inset #88f;
+	background-color: #a0a0df;
+}
+.oaiRecord, .oaiRecordTitle {
+	background-color: #f0f0ff;
+	border-style: solid;
+	border-color: #d0d0d0;
+}
+h2.oaiRecordTitle {
+	background-color: #e0e0ff;
+	font-size: medium;
+	font-weight: bold;
+	padding: 10px;
+	border-width: 2px 2px 0px 2px;
+	margin: 0px;
+}
+.oaiRecord {
+	margin-bottom: 3em;
+	border-width: 2px;
+	padding: 10px;
+}
+
+.results {
+	margin-bottom: 1.5em;
+}
+ul.quicklinks {
+	margin-top: 2px;
+	padding: 4px;
+	text-align: left;
+	border-bottom: 2px solid #ccc;
+	border-top: 2px solid #ccc;
+	clear: left;
+}
+ul.quicklinks li {
+	font-size: 80%;
+	display: inline;
+	list-stlye: none;
+	font-family: sans-serif;
+}
+p.intro {
+	font-size: 80%;
+}
+<xsl:call-template name="xmlstyle"/>
+</xsl:template>
+
+<xsl:variable name="identifier" select="substring-before(concat(substring-after(/oai:OAI-PMH/oai:request,'identifier='),'&amp;'),'&amp;')"/>
+
+<xsl:template match="/">
+<html>
+  <head>
+    <title>OAI 2.0 Request Results</title>
+    <style><xsl:call-template name="style"/></style>
+  </head>
+  <body>
+    <h1>OAI 2.0 Request Results</h1>
+    <xsl:call-template name="quicklinks"/>
+    <p class="intro">You are viewing an HTML version of the XML OAI response. To see the underlying XML use your web browsers view source option. More information about this XSLT is at the <a href="#moreinfo">bottom of the page</a>.</p>
+    <xsl:apply-templates select="/oai:OAI-PMH"/>
+    <xsl:call-template name="quicklinks"/>
+    <h2><a name="moreinfo">About the XSLT</a></h2>
+    <p>An XSLT file has converted the <a href="http://www.openarchives.org">OAI-PMH 2.0</a> responses into XHTML which looks nice in a browser which supports XSLT such as Mozilla, Firebird and Internet Explorer. The XSLT file was created by <a href="http://www.ecs.soton.ac.uk/people/cjg">Christopher Gutteridge</a> at the University of Southampton as part of the <a href="http://www.eprints.org/software/">GNU EPrints system</a>, and is freely redistributable under the <a href="http://www.gnu.org">GPL</a>.</p><p>If you want to use the XSL file on your own OAI interface you may but due to the way XSLT works you must install the XSL file on the same server as the OAI script, you can't just link to this copy.</p><p>For more information or to download the XSL file please see the <a href="http://software.eprints.org/xslt.php">OAI to XHTML XSLT homepage</a>.</p>
+
+  </body>
+</html>
+</xsl:template>
+
+<xsl:template name="quicklinks">
+    <ul class="quicklinks">
+      <li><a href="?verb=Identify">Identify</a> | </li> 
+      <li><a href="?verb=ListRecords&amp;metadataPrefix=oai_dc">ListRecords</a> | </li>
+      <li><a href="?verb=ListSets">ListSets</a> | </li>
+      <li><a href="?verb=ListMetadataFormats">ListMetadataFormats</a> | </li>
+      <li><a href="?verb=ListIdentifiers&amp;metadataPrefix=oai_dc">ListIdentifiers</a></li>
+    </ul>
+</xsl:template>
+
+
+<xsl:template match="/oai:OAI-PMH">
+  <table class="values">
+    <tr><td class="key">Datestamp of response</td>
+    <td class="value"><xsl:value-of select="oai:responseDate"/></td></tr>
+    <tr><td class="key">Request URL</td>
+    <td class="value"><xsl:value-of select="oai:request"/></td></tr>
+  </table>
+<!--  verb: [<xsl:value-of select="oai:request/@verb" />]<br /> -->
+  <xsl:choose>
+    <xsl:when test="oai:error">
+      <h2>OAI Error(s)</h2>
+      <p>The request could not be completed due to the following error or errors.</p>
+      <div class="results">
+        <xsl:apply-templates select="oai:error"/>
+      </div>
+    </xsl:when>
+    <xsl:otherwise>
+      <p>Request was of type <xsl:value-of select="oai:request/@verb"/>.</p>
+      <div class="results">
+        <xsl:apply-templates select="oai:Identify"/>
+        <xsl:apply-templates select="oai:GetRecord"/>
+        <xsl:apply-templates select="oai:ListRecords"/>
+        <xsl:apply-templates select="oai:ListSets"/>
+        <xsl:apply-templates select="oai:ListMetadataFormats"/>
+        <xsl:apply-templates select="oai:ListIdentifiers"/>
+      </div>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+
+<!-- ERROR -->
+
+<xsl:template match="/oai:OAI-PMH/oai:error">
+  <table class="values">
+    <tr><td class="key">Error Code</td>
+    <td class="value"><xsl:value-of select="@code"/></td></tr>
+  </table>
+  <p class="error"><xsl:value-of select="."/></p>
+</xsl:template>
+
+<!-- IDENTIFY -->
+
+<xsl:template match="/oai:OAI-PMH/oai:Identify">
+  <table class="values">
+    <tr><td class="key">Repository Name</td>
+    <td class="value"><xsl:value-of select="oai:repositoryName"/></td></tr>
+    <tr><td class="key">Base URL</td>
+    <td class="value"><xsl:value-of select="oai:baseURL"/></td></tr>
+    <tr><td class="key">Protocol Version</td>
+    <td class="value"><xsl:value-of select="oai:protocolVersion"/></td></tr>
+    <tr><td class="key">Earliest Datestamp</td>
+    <td class="value"><xsl:value-of select="oai:earliestDatestamp"/></td></tr>
+    <tr><td class="key">Deleted Record Policy</td>
+    <td class="value"><xsl:value-of select="oai:deletedRecord"/></td></tr>
+    <tr><td class="key">Granularity</td>
+    <td class="value"><xsl:value-of select="oai:granularity"/></td></tr>
+    <xsl:apply-templates select="oai:adminEmail"/>
+  </table>
+  <xsl:apply-templates select="oai:description"/>
+<!--no warning about unsupported descriptions -->
+</xsl:template>
+
+<xsl:template match="/oai:OAI-PMH/oai:Identify/oai:adminEmail">
+    <tr><td class="key">Admin Email</td>
+    <td class="value"><xsl:value-of select="."/></td></tr>
+</xsl:template>
+
+<!--
+   Identify / Unsupported Description
+-->
+
+<xsl:template match="oai:description/*" priority="-100">
+  <h2>Unsupported Description Type</h2>
+  <p>The XSL currently does not support this type of description.</p>
+  <div class="xmlSource">
+    <xsl:apply-templates select="." mode="xmlMarkup"/>
+  </div>
+</xsl:template>
+
+
+<!--
+   Identify / OAI-Identifier
+-->
+
+<xsl:template match="id:oai-identifier" xmlns:id="http://www.openarchives.org/OAI/2.0/oai-identifier">
+  <h2>OAI-Identifier</h2>
+  <table class="values">
+    <tr><td class="key">Scheme</td>
+    <td class="value"><xsl:value-of select="id:scheme"/></td></tr>
+    <tr><td class="key">Repository Identifier</td>
+    <td class="value"><xsl:value-of select="id:repositoryIdentifier"/></td></tr>
+    <tr><td class="key">Delimiter</td>
+    <td class="value"><xsl:value-of select="id:delimiter"/></td></tr>
+    <tr><td class="key">Sample OAI Identifier</td>
+    <td class="value"><xsl:value-of select="id:sampleIdentifier"/></td></tr>
+  </table>
+</xsl:template>
+
+
+<!--
+   Identify / EPrints
+-->
+
+<xsl:template match="ep:eprints" xmlns:ep="http://www.openarchives.org/OAI/1.1/eprints">
+  <h2>EPrints Description</h2>
+  <xsl:if test="ep:content">
+    <h3>Content</h3>
+    <xsl:apply-templates select="ep:content"/>
+  </xsl:if>
+  <xsl:if test="ep:submissionPolicy">
+    <h3>Submission Policy</h3>
+    <xsl:apply-templates select="ep:submissionPolicy"/>
+  </xsl:if>
+  <h3>Metadata Policy</h3>
+  <xsl:apply-templates select="ep:metadataPolicy"/>
+  <h3>Data Policy</h3>
+  <xsl:apply-templates select="ep:dataPolicy"/>
+  <xsl:apply-templates select="ep:comment"/>
+</xsl:template>
+
+<xsl:template match="ep:content|ep:dataPolicy|ep:metadataPolicy|ep:submissionPolicy" xmlns:ep="http://www.openarchives.org/OAI/1.1/eprints">
+  <xsl:if test="ep:text">
+    <p><xsl:value-of select="ep:text"/></p>
+  </xsl:if>
+  <xsl:if test="ep:URL">
+    <div><a href="{ep:URL}"><xsl:value-of select="ep:URL"/></a></div>
+  </xsl:if>
+</xsl:template>
+
+<xsl:template match="ep:comment" xmlns:ep="http://www.openarchives.org/OAI/1.1/eprints">
+  <h3>Comment</h3>
+  <div><xsl:value-of select="."/></div>
+</xsl:template>
+
+
+<!--
+   Identify / Friends
+-->
+
+<xsl:template match="fr:friends" xmlns:fr="http://www.openarchives.org/OAI/2.0/friends/">
+  <h2>Friends</h2>
+  <ul>
+    <xsl:apply-templates select="fr:baseURL"/>
+  </ul>
+</xsl:template>
+
+<xsl:template match="fr:baseURL" xmlns:fr="http://www.openarchives.org/OAI/2.0/friends/">
+  <li><xsl:value-of select="."/> 
+<xsl:text> </xsl:text>
+<a class="link" href="{.}?verb=Identify">Identify</a></li>
+</xsl:template>
+
+
+<!--
+   Identify / Branding
+-->
+
+<xsl:template match="br:branding" xmlns:br="http://www.openarchives.org/OAI/2.0/branding/">
+  <h2>Branding</h2>
+  <xsl:apply-templates select="br:collectionIcon"/>
+  <xsl:apply-templates select="br:metadataRendering"/>
+</xsl:template>
+
+<xsl:template match="br:collectionIcon" xmlns:br="http://www.openarchives.org/OAI/2.0/branding/">
+  <h3>Icon</h3>
+  <xsl:choose>
+    <xsl:when test="link!=''">
+      <a href="{br:link}"><img src="{br:url}" alt="{br:title}" width="{br:width}" height="{br:height}" border="0"/></a>
+    </xsl:when>
+    <xsl:otherwise>
+      <img src="{br:url}" alt="{br:title}" width="{br:width}" height="{br:height}" border="0"/>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<xsl:template match="br:metadataRendering" xmlns:br="http://www.openarchives.org/OAI/2.0/branding/">
+  <h3>Metadata Rendering Rule</h3>
+  <table class="values">
+    <tr><td class="key">URL</td>
+    <td class="value"><xsl:value-of select="."/></td></tr>
+    <tr><td class="key">Namespace</td>
+    <td class="value"><xsl:value-of select="@metadataNamespace"/></td></tr>
+    <tr><td class="key">Mime Type</td>
+    <td class="value"><xsl:value-of select="@mimetype"/></td></tr>
+  </table>
+</xsl:template>
+
+
+
+<!--
+   Identify / Gateway
+-->
+
+<xsl:template match="gw:gateway" xmlns:gw="http://www.openarchives.org/OAI/2.0/gateway/x">
+  <h2>Gateway Information</h2>
+  <table class="values">
+    <tr><td class="key">Source</td>
+    <td class="value"><xsl:value-of select="gw:source"/></td></tr>
+    <tr><td class="key">Description</td>
+    <td class="value"><xsl:value-of select="gw:gatewayDescription"/></td></tr>
+    <xsl:apply-templates select="gw:gatewayAdmin"/>
+    <xsl:if test="gw:gatewayURL">
+      <tr><td class="key">URL</td>
+      <td class="value"><xsl:value-of select="gw:gatewayURL"/></td></tr>
+    </xsl:if>
+    <xsl:if test="gw:gatewayNotes">
+      <tr><td class="key">Notes</td>
+      <td class="value"><xsl:value-of select="gw:gatewayNotes"/></td></tr>
+    </xsl:if>
+  </table>
+</xsl:template>
+
+<xsl:template match="gw:gatewayAdmin" xmlns:gw="http://www.openarchives.org/OAI/2.0/gateway/">
+  <tr><td class="key">Admin</td>
+  <td class="value"><xsl:value-of select="."/></td></tr>
+</xsl:template>
+
+
+<!-- GetRecord -->
+
+<xsl:template match="oai:GetRecord">
+  <xsl:apply-templates select="oai:record"/>
+</xsl:template>
+
+<!-- ListRecords -->
+
+<xsl:template match="oai:ListRecords">
+  <xsl:apply-templates select="oai:record"/>
+  <xsl:apply-templates select="oai:resumptionToken"/>
+</xsl:template>
+
+<!-- ListIdentifiers -->
+
+<xsl:template match="oai:ListIdentifiers">
+  <xsl:apply-templates select="oai:header"/>
+  <xsl:apply-templates select="oai:resumptionToken"/>
+</xsl:template>
+
+<!-- ListSets -->
+
+<xsl:template match="oai:ListSets">
+  <xsl:apply-templates select="oai:set"/>
+  <xsl:apply-templates select="oai:resumptionToken"/>
+</xsl:template>
+
+<xsl:template match="oai:set">
+  <h2>Set</h2>
+  <table class="values">
+    <tr><td class="key">setName</td>
+    <td class="value"><xsl:value-of select="oai:setName"/></td></tr>
+    <xsl:apply-templates select="oai:setSpec"/>
+  </table>
+</xsl:template>
+
+<!-- ListMetadataFormats -->
+
+<xsl:template match="oai:ListMetadataFormats">
+  <xsl:choose>
+    <xsl:when test="$identifier">
+      <p>This is a list of metadata formats available for the record "<xsl:value-of select="$identifier"/>". Use these links to view the metadata: <xsl:apply-templates select="oai:metadataFormat/oai:metadataPrefix"/></p>
+    </xsl:when>
+    <xsl:otherwise>
+      <p>This is a list of metadata formats available from this archive.</p>
+    </xsl:otherwise>
+  </xsl:choose>
+  <xsl:apply-templates select="oai:metadataFormat"/>
+</xsl:template>
+
+<xsl:template match="oai:metadataFormat">
+  <h2>Metadata Format</h2>
+  <table class="values">
+    <tr><td class="key">metadataPrefix</td>
+    <td class="value"><xsl:value-of select="oai:metadataPrefix"/></td></tr>
+    <tr><td class="key">metadataNamespace</td>
+    <td class="value"><xsl:value-of select="oai:metadataNamespace"/></td></tr>
+    <tr><td class="key">schema</td>
+    <td class="value"><a href="{oai:schema}"><xsl:value-of select="oai:schema"/></a></td></tr>
+  </table>
+</xsl:template>
+
+<xsl:template match="oai:metadataPrefix">
+      <xsl:text> </xsl:text><a class="link" href="?verb=GetRecord&amp;metadataPrefix={.}&amp;identifier={$identifier}"><xsl:value-of select="."/></a>
+</xsl:template>
+
+<!-- record object -->
+
+<xsl:template match="oai:record">
+  <h2 class="oaiRecordTitle">OAI Record: <xsl:value-of select="oai:header/oai:identifier"/></h2>
+  <div class="oaiRecord">
+    <xsl:apply-templates select="oai:header"/>
+    <xsl:apply-templates select="oai:metadata"/>
+    <xsl:apply-templates select="oai:about"/>
+  </div>
+</xsl:template>
+
+<xsl:template match="oai:header">
+  <h3>OAI Record Header</h3>
+  <table class="values">
+    <tr><td class="key">OAI Identifier</td>
+    <td class="value">
+      <xsl:value-of select="oai:identifier"/>
+      <xsl:text> </xsl:text><a class="link" href="?verb=GetRecord&amp;metadataPrefix=oai_dc&amp;identifier={oai:identifier}">oai_dc</a>
+      <xsl:text> </xsl:text><a class="link" href="?verb=ListMetadataFormats&amp;identifier={oai:identifier}">formats</a>
+    </td></tr>
+    <tr><td class="key">Datestamp</td>
+    <td class="value"><xsl:value-of select="oai:datestamp"/></td></tr>
+  <xsl:apply-templates select="oai:setSpec"/>
+  </table>
+  <xsl:if test="@status='deleted'">
+    <p>This record has been deleted.</p>
+  </xsl:if>
+</xsl:template>
+
+
+<xsl:template match="oai:about">
+  <p>"about" part of record container not supported by the XSL</p>
+</xsl:template>
+
+<xsl:template match="oai:metadata">
+  Â 
+  <div class="metadata">
+    <xsl:apply-templates select="*"/>
+  </div>
+</xsl:template>
+
+
+
+
+<!-- oai setSpec object -->
+
+<xsl:template match="oai:setSpec">
+  <tr><td class="key">setSpec</td>
+  <td class="value"><xsl:value-of select="."/>
+    <xsl:text> </xsl:text><a class="link" href="?verb=ListIdentifiers&amp;metadataPrefix=oai_dc&amp;set={.}">Identifiers</a>
+    <xsl:text> </xsl:text><a class="link" href="?verb=ListRecords&amp;metadataPrefix=oai_dc&amp;set={.}">Records</a>
+  </td></tr>
+</xsl:template>
+
+
+
+<!-- oai resumptionToken -->
+
+<xsl:template match="oai:resumptionToken">
+   <p>There are more results.</p>
+   <table class="values">
+     <tr><td class="key">resumptionToken:</td>
+     <td class="value"><xsl:value-of select="."/>
+<xsl:text> </xsl:text>
+<a class="link" href="?verb={/oai:OAI-PMH/oai:request/@verb}&amp;resumptionToken={.}">Resume</a></td></tr>
+   </table>
+</xsl:template>
+
+<!-- unknown metadata format -->
+
+<xsl:template match="oai:metadata/*" priority="-100">
+  <h3>Unknown Metadata Format</h3>
+  <div class="xmlSource">
+    <xsl:apply-templates select="." mode="xmlMarkup"/>
+  </div>
+</xsl:template>
+
+<!-- oai_dc record -->
+
+<xsl:template match="oai_dc:dc" xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/">
+  <div class="dcdata">
+    <h3>Dublin Core Metadata (oai_dc)</h3>
+    <table class="dcdata">
+      <xsl:apply-templates select="*"/>
+    </table>
+  </div>
+</xsl:template>
+
+<xsl:template match="dc:title" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Title</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:creator" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Author or Creator</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:subject" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Subject and Keywords</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:description" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Description</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:publisher" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Publisher</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:contributor" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Other Contributor</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:date" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Date</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:type" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Resource Type</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:format" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Format</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:identifier" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Resource Identifier</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:source" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Source</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:language" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Language</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:relation" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Relation</td><td class="value">
+  <xsl:choose>
+    <xsl:when test="starts-with(.,&quot;http&quot; )">
+      <xsl:choose>
+        <xsl:when test="string-length(.) &gt; 50">
+          <a class="link" href="{.}">URL</a>
+          <i> URL not shown as it is very long.</i>
+        </xsl:when>
+        <xsl:otherwise>
+          <a href="{.}"><xsl:value-of select="."/></a>
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:value-of select="."/>
+    </xsl:otherwise>
+  </xsl:choose>
+</td></tr></xsl:template>
+
+<xsl:template match="dc:coverage" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Coverage</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<xsl:template match="dc:rights" xmlns:dc="http://purl.org/dc/elements/1.1/">
+<tr><td class="key">Rights Management</td><td class="value"><xsl:value-of select="."/></td></tr></xsl:template>
+
+<!-- XML Pretty Maker -->
+
+<xsl:template match="node()" mode="xmlMarkup">
+  <div class="xmlBlock">
+    &lt;<span class="xmlTagName"><xsl:value-of select="name(.)"/></span><xsl:apply-templates select="@*" mode="xmlMarkup"/>&gt;<xsl:apply-templates select="node()" mode="xmlMarkup"/>&lt;/<span class="xmlTagName"><xsl:value-of select="name(.)"/></span>&gt;
+  </div>
+</xsl:template>
+
+<xsl:template match="text()" mode="xmlMarkup"><span class="xmlText"><xsl:value-of select="."/></span></xsl:template>
+
+<xsl:template match="@*" mode="xmlMarkup">
+  <xsl:text> </xsl:text><span class="xmlAttrName"><xsl:value-of select="name()"/></span>="<span class="xmlAttrValue"><xsl:value-of select="."/></span>"
+</xsl:template>
+
+<xsl:template name="xmlstyle">
+.xmlSource {
+	font-size: 70%;
+	border: solid #c0c0a0 1px;
+	background-color: #ffffe0;
+	padding: 2em 2em 2em 0em;
+}
+.xmlBlock {
+	padding-left: 2em;
+}
+.xmlTagName {
+	color: #800000;
+	font-weight: bold;
+}
+.xmlAttrName {
+	font-weight: bold;
+}
+.xmlAttrValue {
+	color: #0000c0;
+}
+</xsl:template>
+
+</xsl:stylesheet>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionMarshallerTest.java
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionMarshallerTest.java	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/java/eu/clarin/cmdi/virtualcollectionregistry/VirtualCollectionMarshallerTest.java	(revision 1025)
@@ -0,0 +1,206 @@
+package eu.clarin.cmdi.virtualcollectionregistry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import org.junit.Test;
+
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionMarshaller;
+import eu.clarin.cmdi.virtualcollectionregistry.VirtualCollectionMarshaller.Format;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Creator;
+import eu.clarin.cmdi.virtualcollectionregistry.model.GeneratedBy;
+import eu.clarin.cmdi.virtualcollectionregistry.model.Resource;
+import eu.clarin.cmdi.virtualcollectionregistry.model.VirtualCollection;
+
+public class VirtualCollectionMarshallerTest {
+
+    @Test
+    public void testMinmalExtensional() throws Exception {
+        VirtualCollectionMarshaller m =
+           new VirtualCollectionMarshaller();
+        VirtualCollection vc = new VirtualCollection();
+        vc.setType(VirtualCollection.Type.EXTENSIONAL);
+        vc.setName("Test 12");
+        vc.getResources().add(new Resource(Resource.Type.RESOURCE, "a/ref/"));
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream(65536);
+        m.marshal(out, Format.XML, vc);
+        VirtualCollection vc2 =
+            m.unmarshal(new ByteArrayInputStream(out.toByteArray()),
+                        Format.XML, "utf-8");
+
+        assertEquals(vc, vc2);
+    }
+
+    @Test
+    public void testUnmarshallExtensionalCollection() throws Exception {
+        VirtualCollectionMarshaller m =
+            new VirtualCollectionMarshaller();
+         InputStream in = this.getClass().getClassLoader()
+                .getResourceAsStream("VirtualCollection-extensional.xml");
+        VirtualCollection vc = m.unmarshal(in, Format.XML, "utf-8");
+
+        final DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        assertNotNull(vc);
+        assertEquals("Test Collection 1", vc.getName());
+        assertEquals(VirtualCollection.Type.EXTENSIONAL, vc.getType());
+        assertEquals("Test Description", vc.getDescription());
+        assertEquals(sdf.parse("2009-12-31"), vc.getCreationDate());
+        assertEquals(VirtualCollection.Purpose.SAMPLE, vc.getPurpose());
+        assertEquals(VirtualCollection.Reproducibility.UNTENDED,
+                vc.getReproducibility());
+        assertEquals("Unmaintaned test collection.",
+                vc.getReproducibilityNotice());
+
+        assertNotNull(vc.getKeywords());
+        assertEquals(2, vc.getKeywords().size());
+        assertNotNull(vc.getKeywords());
+        assertEquals("test", vc.getKeywords().get(0));
+        assertEquals("unmaintained", vc.getKeywords().get(1));
+
+        assertNotNull(vc.getCreators());
+        assertEquals(2, vc.getCreators().size());
+        Creator c = vc.getCreators().get(0);
+        assertNotNull(c);
+        assertEquals("Joe User", c.getPerson());
+        assertEquals("Example Address Joe", c.getAddress());
+        assertEquals("joe.user@example.com", c.getEMail());
+        assertEquals("Example Organization", c.getOrganisation());
+        assertEquals("+1-800-555-0101", c.getTelephone());
+        assertEquals("http://www.example.org/~joe/", c.getWebsite());
+        assertEquals("Test Role Joe", c.getRole());
+        c = vc.getCreators().get(1);
+        assertNotNull(c);
+        assertEquals("Jane User", c.getPerson());
+        assertEquals("Example Address Jane", c.getAddress());
+        assertEquals("jane.user@example.com", c.getEMail());
+        assertEquals("Example Organization", c.getOrganisation());
+        assertEquals("+1-800-555-0102", c.getTelephone());
+        assertEquals("http://www.example.org/~jane/", c.getWebsite());
+        assertEquals("Test Role Jane", c.getRole());
+
+        assertNotNull(vc.getResources());
+        assertEquals(3, vc.getResources().size());
+
+        Resource r = vc.getResources().get(0);
+        assertNotNull(r);
+        assertEquals(Resource.Type.METADATA, r.getType());
+        assertEquals("http://hdl.handle.net/1839/00-0000-0000-0005-671C-C",
+                r.getRef());
+        r = vc.getResources().get(1);
+        assertNotNull(r);
+        assertEquals(Resource.Type.METADATA, r.getType());
+        assertEquals("http://hdl.handle.net/1839/00-0000-0000-0006-671C-C",
+                r.getRef());
+        r = vc.getResources().get(2);
+        assertNotNull(r);
+        assertEquals(Resource.Type.METADATA, r.getType());
+        assertEquals("http://hdl.handle.net/1839/00-0000-0000-0007-671C-C",
+                r.getRef());
+
+        assertNull(vc.getGeneratedBy());
+
+        // round-tripping
+        ByteArrayOutputStream out = new ByteArrayOutputStream(65536);
+        m.marshal(out, Format.XML, vc);
+        VirtualCollection vc2 =
+            m.unmarshal(new ByteArrayInputStream(out.toByteArray()),
+                    Format.XML, "utf-8");
+        assertEquals(vc.getOwner(), vc2.getOwner());
+        assertEquals(vc.getPersistentIdentifier(),
+                vc2.getPersistentIdentifier());
+        assertEquals(vc.getState(), vc2.getState());
+        assertEquals(vc.getType(), vc2.getType());
+        assertEquals(vc.getName(), vc2.getName());
+        assertEquals(vc.getDescription(), vc2.getDescription());
+        assertEquals(vc.getCreationDate(), vc2.getCreationDate());
+        assertEquals(vc.getCreators(), vc2.getCreators());
+        assertEquals(vc.getPurpose(), vc2.getPurpose());
+        assertEquals(vc.getReproducibility(), vc2.getReproducibility());
+        assertEquals(vc.getReproducibilityNotice(),
+                vc2.getReproducibilityNotice());
+        assertEquals(vc.getKeywords(), vc2.getKeywords());
+        assertEquals(vc.getResources(), vc2.getResources());
+        assertEquals(vc.getGeneratedBy(), vc2.getGeneratedBy());
+        assertEquals(vc, vc2);
+    }
+
+    @Test
+    public void testUnmarshallIntensionalCollection() throws Exception {
+        VirtualCollectionMarshaller m =
+            new VirtualCollectionMarshaller();
+        InputStream in = this.getClass().getClassLoader()
+                .getResourceAsStream("VirtualCollection-intensional.xml");
+        VirtualCollection vc = m.unmarshal(in, Format.XML, "utf-8");
+
+        final DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        assertNotNull(vc);
+        assertEquals("Test Collection 2", vc.getName());
+        assertEquals(VirtualCollection.Type.INTENSIONAL, vc.getType());
+        assertEquals("Test Description", vc.getDescription());
+        assertEquals(sdf.parse("2009-12-31"), vc.getCreationDate());
+        assertEquals(VirtualCollection.Purpose.SAMPLE, vc.getPurpose());
+        assertEquals(VirtualCollection.Reproducibility.UNTENDED,
+                vc.getReproducibility());
+        assertEquals("Unmaintaned test collection.",
+                vc.getReproducibilityNotice());
+
+        assertNotNull(vc.getKeywords());
+        assertEquals(2, vc.getKeywords().size());
+        assertNotNull(vc.getKeywords());
+        assertEquals("test", vc.getKeywords().get(0));
+        assertEquals("unmaintained", vc.getKeywords().get(1));
+
+        assertNotNull(vc.getCreators());
+        assertEquals(1, vc.getCreators().size());
+        Creator c = vc.getCreators().get(0);
+        assertNotNull(c);
+        assertEquals("Joe User", c.getPerson());
+        assertEquals("joe.user@example.com", c.getEMail());
+        assertEquals("Example Organization", c.getOrganisation());
+
+
+        assertEquals(0, vc.getResources().size());
+
+        GeneratedBy generatedBy = vc.getGeneratedBy();
+        assertNotNull(generatedBy);
+        assertEquals("GeneratedBy description", generatedBy.getDescription());
+        assertEquals("urn:x-vcr:test-uri", generatedBy.getURI());
+        GeneratedBy.Query query = generatedBy.getQuery();
+        assertNotNull(query);
+        assertEquals("sql", query.getProfile());
+        assertEquals("<SQL>Test-Query</SQL>", query.getValue());
+
+        // round-tripping
+        ByteArrayOutputStream out = new ByteArrayOutputStream(65536);
+        m.marshal(out, Format.XML, vc);
+        VirtualCollection vc2 =
+            m.unmarshal(new ByteArrayInputStream(out.toByteArray()),
+                    Format.XML, "utf-8");
+        assertEquals(vc.getOwner(), vc2.getOwner());
+        assertEquals(vc.getPersistentIdentifier(),
+                vc2.getPersistentIdentifier());
+        assertEquals(vc.getState(), vc2.getState());
+        assertEquals(vc.getType(), vc2.getType());
+        assertEquals(vc.getName(), vc2.getName());
+        assertEquals(vc.getDescription(), vc2.getDescription());
+        assertEquals(vc.getCreationDate(), vc2.getCreationDate());
+        assertEquals(vc.getCreators(), vc2.getCreators());
+        assertEquals(vc.getPurpose(), vc2.getPurpose());
+        assertEquals(vc.getReproducibility(), vc2.getReproducibility());
+        assertEquals(vc.getReproducibilityNotice(),
+                vc2.getReproducibilityNotice());
+        assertEquals(vc.getKeywords(), vc2.getKeywords());
+        assertEquals(vc.getResources(), vc2.getResources());
+        assertEquals(vc.getGeneratedBy(), vc2.getGeneratedBy());
+        assertEquals(vc, vc2);
+    }
+
+} // class VirtualCollectionMarshallerTest
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/VirtualCollection-extensional.xml
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/VirtualCollection-extensional.xml	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/VirtualCollection-extensional.xml	(revision 1025)
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<VirtualCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="VirtualCollection.xsd">
+    <Type>extensional</Type>
+    <Name>Test Collection 1</Name>
+    <Description>Test Description</Description>
+    <CreationDate>2009-12-31</CreationDate>
+    <Creators>
+        <Creator>
+            <Person>Joe User</Person>
+            <Address>Example Address Joe</Address>
+            <Email>joe.user@example.com</Email>
+            <Organisation>Example Organization</Organisation>
+            <Telephone>+1-800-555-0101</Telephone>
+            <Website>http://www.example.org/~joe/</Website>
+            <Role>Test Role Joe</Role>
+        </Creator>
+        <Creator>
+            <Person>Jane User</Person>
+            <Address>Example Address Jane</Address>
+            <Email>jane.user@example.com</Email>
+            <Organisation>Example Organization</Organisation>
+            <Telephone>+1-800-555-0102</Telephone>
+            <Website>http://www.example.org/~jane/</Website>
+            <Role>Test Role Jane</Role>
+        </Creator>
+    </Creators>
+    <Purpose>sample</Purpose>
+    <Reproducibility>untended</Reproducibility>
+    <ReproducibilityNotice>Unmaintaned test collection.</ReproducibilityNotice>
+    <Keywords>
+        <Keyword>test</Keyword>
+        <Keyword>unmaintained</Keyword>
+    </Keywords>
+    <Resources>
+        <Resource>
+            <ResourceType>Metadata</ResourceType>
+            <ResourceRef>http://hdl.handle.net/1839/00-0000-0000-0005-671C-C</ResourceRef>
+        </Resource>
+        <Resource>
+            <ResourceType>Metadata</ResourceType>
+            <ResourceRef>http://hdl.handle.net/1839/00-0000-0000-0006-671C-C</ResourceRef>
+        </Resource>
+        <Resource>
+            <ResourceType>Metadata</ResourceType>
+            <ResourceRef>http://hdl.handle.net/1839/00-0000-0000-0007-671C-C</ResourceRef>
+        </Resource>
+    </Resources>
+</VirtualCollection>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/VirtualCollection-intensional.xml
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/VirtualCollection-intensional.xml	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/VirtualCollection-intensional.xml	(revision 1025)
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<VirtualCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="VirtualCollection.xsd">
+    <Type>intensional</Type>
+    <Name>Test Collection 2</Name>
+    <Description>Test Description</Description>
+    <CreationDate>2009-12-31</CreationDate>
+    <Creators>
+        <Creator>
+            <Person>Joe User</Person>
+            <Email>joe.user@example.com</Email>
+            <Organisation>Example Organization</Organisation>
+        </Creator>
+    </Creators>
+    <Purpose>sample</Purpose>
+    <Reproducibility>untended</Reproducibility>
+    <ReproducibilityNotice>Unmaintaned test collection.</ReproducibilityNotice>
+    <Keywords>
+        <Keyword>test</Keyword>
+        <Keyword>unmaintained</Keyword>
+    </Keywords>
+    <GeneratedBy>
+        <Description>GeneratedBy description</Description>
+        <URI>urn:x-vcr:test-uri</URI>
+        <Query profile="sql"><![CDATA[<SQL>Test-Query</SQL>]]></Query>
+    </GeneratedBy>
+</VirtualCollection>
Index: /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/testdb.properties
===================================================================
--- /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/testdb.properties	(revision 1025)
+++ /VirtualCollectionRegistry/tags/VirtualCollectionRegistry-0.2.1/src/test/resources/testdb.properties	(revision 1025)
@@ -0,0 +1,13 @@
+hibernate.dialect=org.hibernate.dialect.HSQLDialect
+hibernate.connection.driver_class=org.hsqldb.jdbcDriver
+hibernate.connection.username=sa
+hibernate.connection.password=
+hibernate.connection.url=jdbc:hsqldb:target/test-database/database
+#hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
+#hibernate.connection.driver_class=com.mysql.jdbc.Driver
+#hibernate.connection.username=vcr_devel
+#hibernate.connection.password=***
+#hibernate.connection.url=jdbc:mysql://clarin.ids-mannheim.de:3306/vcr_devel
+hibernate.hbm2ddl.auto=create-drop
+hibernate.show_sql=true
+hibernate.format_sql=true
