commit d051d1153ca3f8b41a194cd76c77c386d4970d77 Author: Derek Hulley Date: Thu Dec 8 07:13:07 2005 +0000 Moving to root below branch label git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 diff --git a/.classpath b/.classpath new file mode 100644 index 0000000000..d86c837f55 --- /dev/null +++ b/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000000..645114745a --- /dev/null +++ b/.project @@ -0,0 +1,20 @@ + + + Web-Client + + + 3rd Party + Repository + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + \ No newline at end of file diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties new file mode 100644 index 0000000000..17f07b18a4 --- /dev/null +++ b/config/alfresco/messages/webclient.properties @@ -0,0 +1,906 @@ +# I18N message properties + +# Date Pattern +date_pattern=MMMM, d yyyy +date_time_pattern=MMMM, d yyyy HH:mm +time_pattern=HH:mm + +# General UI +product_name=Alfresco +view_description=This view allows you to browse the items in your space. +search_description=This view allows you to see the results from your search. +checkinfile_description=Check in your working copy for other team members to work with. +checkoutfilelink_description=Edit the checked out file, undo the check out or carry on working. +checkoutfile_description=Enter information about the check out. +documentdetails_description=View the details about the content. +previewdocument_description=Preview the content or space within a Template. +spacedetails_description=View the details about the space. +undocheckoutfile_description=Cancel the check out of a document and discard any changes. +updatefile_description=Update a document on the repository with content from your computer. +editfile_description=Edit the content of the file. +editfileinline_description=Edit the content of the document, then click Save. +createfile_description=Enter new content. +manageusers_description=Manage the users of the repository. +manage_invited_users_description=Manage the permissions you have granted to users who access your space. +modify_user_roles_description=Modify the permissions granted to a user for accessing your space. +advancedsearch_description=Perform a more detailed search of the repository. +editdocument_description=Modify the document properties then click OK. +editcategory_description=Set the category for the document then click OK. +editworkflow_description=Modify the simple workflow properties then click OK. +editspace_description=Modify the space properties then click OK. +newspace_description=Enter information about the new space then click Create Space. +space_rules_description=This view shows you all the rules to be applied to content in this space. +warning_inline=This is only recommended for HTML or plain text documents. +categories_description=This view allows you to browse and modify the categories hiearchy. +new_category_description=Enter information about the new Category then click Create Category. +status_message_default=No messages. + +# UI Component messages +yes=Yes +no=No +kilobyte=KB +megabyte=MB +gigabyte=GB +locked_you=Item locked by you +locked_user=Item locked by user +wizard_errors=Please correct the errors below. +last_page=Last Page +next_page=Next Page +prev_page=Previous Page +first_page=First Page +page_info=Page {0} of {1} +go_up=Go Up +ok=OK +go=Go +to=To +from=From +options=Options +other_options=Other Options +local=Local +inherited=Inherited +search=Search +advanced_search=Advanced Search +value_not_set=not set +clear=Clear Results +results_contains=Results for ''{0}''. +results_contains_filter=Results for ''{0}'' in ''{1}''. +details_view=Details View +view_icon=Icon View +view_browse=Browse View +more_options=More... +more_actions=More Actions +more_options_space=More Actions for this Space +more_options_file=More Actions for this Document +select_space_prompt=Click here to select a Space +select_existing_space_prompt=Click here to select an existing Space +select_home_space_prompt=Click here to select the Home Space location +select_category_prompt=Click here to select a Category +select_destination_prompt=Click here to select the destination +add_new=Add New +change=Change +set=Set +no_categories_applied=This document does not yet have any categories applied. +has_following_categories=This document has the following categories applied... +moved=moved +copied=copied +document_action=The document will be {0} to ''{1}'' if the ''{2}'' action is taken. +clipboard=Clipboard +recent_spaces=Recent Spaces +shortcuts=Shortcuts +company_home=Company Home +my_home=My Home +advanced_search=Advanced Search +new_search=New Search +search_results=Search Results +search_detail=Search for \"{0}\" results shown below +close_search=Close Search +browse_spaces=Browse Spaces +browse_content=Content items +items=item(s) +location=Item Location +toggle_shelf=Hide or Show the Shelf +shelf=Shelf +actions=Actions +view=View +view_details=View Details +view_details_file=View Details for file +change_details=Change Details +view_details=View Details +update=Update +cut=Cut +copy=Copy +paste=Paste +remove=Remove +change_roles=Change Roles +change_user_roles=Change User Roles +paste_item=Paste Item +paste_all=Paste All +remove_item=Remove Item +remove_all=Remove All +close=Close +invite=Invite +invite_user=Invite User +filter_contents=Filter Contents +users=Users +groups=Groups +resetall=Reset All +content_rules=Content Rules +view_in_browser=View In Browser +view_in_webdav=View in WebDAV +view_in_cifs=View in CIFS +download_content=Download Content +details_page_bookmark=External Access URL +links=Links +create_shortcut=Create Shortcut +navigation=Navigation +next_item=Next Item +previous_item=Previous Item +cancel=Cancel +upload=Upload +homespace=Home Space +network_folder=Open Network Folder +other_action=Run Action +information=Information +move=Move +copy=Copy +type=Type +aspect=Aspect +workflow=Workflow +rules=Rules +system_error=System Error +login=Login +templates=Templates +template=Template +local=Local +select_button=Select... +select_items=Select items +select_an_item=Select an item +selected_items=Selected Items +add_to_list_button=Add to List +none=None +no_selected_items=No selected items. +search_select_item=Search for and select an item. +search_select_items=Search for and select items. +search_minimum=Please enter a minimum of {0} characters to perform a search. +filter=Filter +choose_icon=Choose icon +security=Security +all_formats=All Formats + +# Properties +username=User Name +joindate=Join Date +roles=Roles +help=Help +name=Name +password=Password +confirm=Confirm +path=Path +description=Description +created=Created +modified=Modified +created_date=Created Date +modified_date=Modified Date +size=Size +title=Title +author=Author +date=Date +mimetype=Format + +# Repo permission display labels +# Note - these come from the server, the english translation is generally the same +Administrator=Administrator +Guest=Guest +Coordinator=Coordinator +Contributor=Contributor +Editor=Editor +All=All + +# Actions +delete=Delete +edit=Edit +checkin=Check In +checkout=Check Out +checkout_document=Check out this document +undocheckout=Undo Check Out +delete_space=Delete Space +delete_file=Delete File +delete_rule=Delete Rule +delete_user=Delete User +remove_user=Remove User +new_space=Create Space +add_content=Add Content +create_content=Create Content +add_multiple_files=Add Multiple Files +import_directory=Import Directory +advanced_space_wizard=Advanced Space Wizard +create_rule=Create Rule +manage_rules=Manage Content Rules +manage_users=Manage System Users +manage_groups=Manage User Groups +manage_invited_users=Manage Space Users +modify_user_roles=Modify User Roles for +modify=Modify +view=View +logout=Logout +add=Add +system_info=System Information +node_browser=Node Browser +reset_config=Reset Config Service +save=Save +user_details=User Details +language=Language +export=Export +import=Import +take_ownership=Take Ownership +create_forums=Create Forum Space +create_forum=Create Forum +create_topic=Create Topic +create_post=Create Post +create_reply=Post Reply +delete_forums=Delete Forum Space +delete_forum=Delete Forum +delete_topic=Delete Topic +delete_post=Delete Post +post_to_topic=Post to Topic +reply=Reply +edit_post=Edit Post +reply_to=Reply to +post_reply=Post Reply + +# Login page message +login_details=Enter Login details +login_err_password_length=Password must be between {0} and {1} characters in length. +login_err_password_chars=Password can only contain characters or digits. +login_err_username_length=Username must be between {0} and {1} characters in length. +login_err_username_chars=Username can only contain characters or digits. +loggedout_details=You have been logged out of Alfresco. +relogin=Re-login to Alfresco + +# Browse list messages +no_space_items=No items to display. Click the ''{0}'' action to create a space. +no_content_items=No items to display. To add an existing document click ''{0}'' action. To create an HTML or Plain Text file click ''{1}'' action. + +# Advanced Search messages +look_in=Look in location +look_for=Look for +all_spaces=All Spaces +specify_space=Specify Space +include_child_spaces=Include child spaces +show_results_for=Show me results for +all_items=All Items +file_names_contents=File names and contents +file_names=File names only +space_names=Space names only +show_results_categories=Show me results in the categories +include_sub_categories=Include sub-categories +also_search_results=More search options +additional_options=Additional options + +# Forum messages +forums=Forum Space +forum=Forum +browse_forums=Browse Forum Spaces and Forums +browse_topics=Browse Topics +browse_posts=Browse Posts +forums_info=This view allows you to browse forum spaces and forums. +forum_info=This view allows you to browse topics in this forum. +topic_info=This view allows you to browse posts in this topic. +no_forums=No forum spaces or forums to display. Click the ''Create Forum Space'' action to create a forum space or ''Create Forum'' action to create a forum. +no_topics=No topics to display. Click the ''Create Topic'' action to create a topic. +no_posts=No posts to display. Click the ''Post to Topic'' action to create a post. +topic=Topic +topics=Topics +post=Post +posted=Posted +create_forums_description=Enter information about the new forum space then click Create Forum Space. +create_forum_description=Enter information about the new forum then click Create Forum. +create_topic_description=Enter information about the new topic then click Create Topic. +create_post_description=Enter the content of the message then click Post. +create_reply_description=Enter message text to reply then click Reply. +forums_props=Forum Space Properties +forum_props=Forum Properties +topic_props=Topic Properties +create_forums_finish=To create the forum space click Create Forum Space. +create_forum_finish=To create the forum click Create Forum. +create_topic_finish=To create the topic click Create Topic. +create_post_finish=To create the post click Post. +create_reply_finish=To create the reply click Reply. +forums_details_description=View details about the forum space. +forum_details_description=View details about the forum. +topic_details_description=View details about the topic. +bubble_view=Bubble View +replies=Replies +on=On +reply_message=Reply Message + +# Common Wizard messages +steps=Steps +summary=Summary +summary_desc=The information you entered is shown below. +default_instruction=To continue click Next. +next_button=Next +back_button=Back +finish_button=Finish +cancel_button=Cancel +clear_button=Clear +title=Title +description=Description +you_may_want=You may want to + +# Category Management messages +title_categories_list=Categories +add_category=Add Category +edit_category=Edit Category +delete_category=Delete Category +category_icons=Categories +category_details=Details +category_management=Category Management +title_create_category=Create New Category +new_category=New Category +category_props=Category Properties +items=Items +title_delete_category=Delete Category +delete_category=Delete Category +delete_category_warning=This category has {0} existing document(s) linked to it. +delete_category_info=To remove this category and all it's sub-categories, click Delete. +delete_category_confirm=Are you sure you want to delete category \"{0}\" and all sub-categories? +title_edit_category=Edit Category +edit_category_description=Edit the information for this category. + +# Groups Management messages +title_groups_list=Groups Management +groups_management=Groups Management +groups_description=Manage the members of a group, create new groups or remove existing groups. +new_group=Create Group +new_sub_group=Create Sub-Group +edit_group=Edit Group +delete_group=Delete Group +add_user=Add User +group_icons=Groups +group_details=Details +root_groups=Root Groups +group_filter_children=Children +group_filter_all=All +title_create_group=Create Group +new_group_description=Enter information about the new Group then click Create Group. +group_props=Group Properties +identifier=Identifier +create_group_warning=The Identifier for a Group cannot be changed once it has been set. +title_delete_group=Delete Group +delete_group_warning=This group has {0} sub-group(s) or user(s) attached to it. +delete_group_info=To delete this Group from the system and remove all members from it, click Delete. +delete_group_confirm=Once the group is removed from the system it will no longer be accessable. Are you sure you want to delete Group \"{0}\" and remove all users from it? +title_add_user_group=Add User to Group +add_user_group_description=Add an existing User to a Group +select_users=Select Users to add to this Group +selected_users=Selected Users + +# Invite Users Wizard messages +invite_title=Invite Users Wizard +invite_desc=This wizard helps you to give other users access to your space. +invite_step1_title=Step One - Invite Users +invite_step1_desc=Select the users and roles they will play in this space. +invite_step2_title=Step Two - Notify Users +invite_step2_desc=Notify the selected users. +i_want_to=I want to... +invite_step_1=Invite Users +invite_step_2=Notify Users +invite_all=Invite All users as guests +invite_users=Specify Users/Groups and their roles +specify_usersgroups=Specify Users/Groups +select_usersgroups=Select user/group and their role(s) +select_role=Select role +selected_usersgroups=Selected users/groups and their role(s) +selected_roles=Selected roles +click_add=Click Add +role=Role +send_email=Do you want to send an email to notify the invited users? +subject=Subject +body=Body +automatic_text=Automatic text +invite_space=You have been invited to ''{0}'' by {1}. +invite_role=You will have the role of: {0} +invite_finish_instruction=To close this wizard and apply your changes click Finish. To review or change your selections click Back. +remove_invited_user_info=Remove an invited user from this space. +add_role=Add Role +space_owner=User ''{0}'' is the current owner of this space. +users_and_groups=Users and Groups +authority=Username + +# System Users messages +create_user=Create User +change_password=Change Password +title_change_password=Change User Password +change_password_description=Use this view to change an existing user password. +change_password_instructions=Enter the new password for this user. + +# Check-in messages +check_in=Check In +checkin_options=Check In options +checkin_changes_info=Check in changes and keep file checked out +workingcopy_location=Working copy location +which_copy_checkin=Which copy do you want to check in? +which_copy_current=Use copy in current space +which_copy_other=Use copy uploaded from my computer +locate_doc_upload=Locate document to upload +file_location=Location +click_upload=Click upload +minor_change=Minor Change +major_change=Major Change +notes=Notes + +# Check-out messages +check_out=Check Out +copy_work_with=A copy of the file ''{0}'' will be made for you to work with. +copy_checkin_changes=When you have completed your changes you need to check in the file to allow others to view the changes. +copy_store_prompt=Where do you want to keep the copy of this file? +store_space_current=In the current space +store_space_selected=In the space selected +check_out_of=Check Out of +copy_file_checkedout=A copy of the file ''{0}'' is now checked out to you for editing. +edit_workingcopy_title=Edit the working copy now +edit_workingcopy_info=To edit the working copy of the file, click the link below and if asked select Save. +continue_working_title=Continue working +continue_working_info1=If you do not want to edit the file now click OK to close the page. +continue_working_info2=To edit the file in the future click the Edit action +download_complete=When the download is complete, click OK. +undo_checkout_for=Undo Check Out for +undo_checkout=Undo Check Out +undo_checkout_info=If you undo the check out of a document, the associated working copy will be deleted and all changes to it since the Check Out will be lost. + +# Document and Space details messages +details_of=Details of +preview_of=Preview of +modify_props_of=Modify Properties of +preview=Preview in Template +dashboard_view=Dashboard View +dashboard=Dashboard +view_links=Links +not_inline_editable=This document is not inline editable. +allow_inline_editing=Allow Inline Editing +not_in_workflow=This document is not part of any workflow. +not_in_category=This document is not categorized. +not_versioned=This document has no version history. +allow_categorization=Allow Categorization +allow_versioning=Allow Versioning +version_history=Version History +version=Version +document_properties=Document Properties +other_properties=Other Properties +no_other_properties=This document does not have any other properties to show. +modify_categories_of=Modify categories of +space_props=Space Properties +choose_space_icon=Choose space icon +create_space_finish=To create your space click Create Space. +select_category=Select a category +selected_categories=Selected categories +no_selected_categories=No categories selected. +success_ownership=Successfully took ownership of the document. +inherit_permissions=Inherit Parent Space Permissions +success_inherit_permissions=Successfully changed Inherit Parent Permissions to 'Yes' +success_not_inherit_permissions=Successfully changed Inherit Parent Permissions to 'No' +apply_dashboard=Apply Dashboard +apply_dashboard_info=Select a template to be applied to the Space as a Dashboard view. + +# Export messages +export_info=Exports metadata and content from this or all Spaces. +export_from=Export From +package_name=Package Name +all_spaces=Repository Root +current_space=Current Space +include_children=Include Children +include_self=Include this Space +run_export_in_background=Run export in background +export_error_info=If this option is selected the export will be performed in the background so the results may not appear immediately. You may also want to monitor the server console and log files for errors. + +# Import messages +import_info=Imports an Alfresco content package file into the repository. +import_package_description=Alfresco content package +run_import_in_background=Run import in background +import_error_info=If this option is selected the import will be performed in the background so the results may not appear immediately. You may also want to monitor the server console and log files for errors. + +# Edit Content messages +edit_file_title=Edit file +edit_file_prompt=To edit the file ''{0}'', click the link below and if asked select Save. +edit_download_complete=When the download is complete, click Close. +checkout_file_title=Check out file +checkout_you_may_want=You may want to check out this file to lock it and prevent other users from editing it. +checkout_hint1=Hint: When you check out a file a copy is made for you to work with. +checkout_hint2=When you have finished editing the copy you need to check it in to release the lock and allow others to work with the modified document. +checkout_want_to=to prevent the possibility of other users overwriting your changes. +checkout_warn=Note: You will lose any changes already made to this document. +local_copy_location=Local copy location +locate_content_upload=Locate and upload your document to the repository. + +# Edit Workflow messages +modify_workflow_props=Modify Properties of Simple Workflow +approve_flow=Approve Flow +reject_flow=Reject Flow +name_approve_step=Name for approve step +name_reject_step=Name for reject step +select_reject_step=Do you want to provide a reject step? +choose_copy_move_location=Choose whether you want to move or copy the content and also the location. + +# System Information and admin page messages +system_info=System Information +current_user=Current User +http_app_state=HTTP Application State +http_session_state=HTTP Session State +http_request_state=HTTP Request State +http_request_params=HTTP Request Parameters +http_request_headers=HTTP Request Headers +system_props=System Properties +hide_details=Hide Details +show_details=Show Details + +# Content Wizard messages +add_content_title=Add Content Wizard +add_content_desc=This wizard helps you to add a document to a space. +add_conent_step1_title=Step One - Upload Document +add_conent_step1_desc=Locate and upload your document to the repository. +add_conent_step2_title=Step Two - Properties +add_conent_step2_desc=Enter information about this content. +upload_document=Upload Document +properties=Properties +general=General +file_name=File Name +content_type=Content Type +content_format=Content Format +author=Author +inline_editable=Inline Editable +locate_document=Locate document to upload +location=Location +click_upload=Click upload +file_upload_success=The file ''{0}'' was uploaded successfully. If the file you uploaded does not exist, then an empty file will be created with the name you specified. +content_finish_instruction=To add the content to this space click Finish. To review or change your selections click Back. +create_content_title=Create Content Wizard +create_content_desc=This wizard helps you to create a new document in a space. +create_content_step1_title=Step One - Select Type +create_content_step1_desc=Select the type of content you wish to create. +create_content_step2_title=Step Two - Enter Content +create_content_step2_desc=Enter your document content into the repository. +create_content_step3_title=Step Three - Properties +create_content_step3_desc=Enter information about this content. +enter_content=Enter Content +select_type=Select Type +content=Content +text_content=Plain Text Content +html_content=HTML Content + +# Rule and Action Wizard messages +create_action_title=Custom Action Wizard +create_action_desc=This wizard helps you create a custom action for +create_action_step1_title=Step One - Select Action +create_action_step2_title=Step Two - Action Settings +create_action_finish_instruction=To execute the action click Finish. To review or change your selections click Back. +new_rule_title=New Rule Wizard +new_rule_title_edit=Edit Rule Wizard +new_rule_desc=This wizard helps you create a new rule. +new_rule_desc_edit=This wizard helps you modify a rule. +new_rule_step1_title=Step One - Enter Details +new_rule_step2_title=Step Two - Select Conditions +new_rule_step3_title=Step Three - Select Actions +new_rule_finish_instruction=To create the rule click Finish. To review or change your selections click Back. +new_rule_finish_instruction_edit=To update the rule click Finish. To review or change your selections click Back. +select_action=Select Action +select_an_action=Select an action... +action=Action +actions=Actions +action_settings=Action Settings +set_action_values=Set action values +select_feature=Select required feature +version_notes=Version Notes +checkout_location=Check out location +destination=Destination +subject=Subject +message=Message +category=Category +categories=Categories +change_category=Change Category +approve_flow=Approve Flow +approve_step_name=Name for approve step +move_or_copy=Choose whether you want to move or copy the content and also the location. +reject_flow=Reject Flow +want_reject_step=Do you want to provide a reject step? +reject_step_name=Name for reject step +required_format=Required format +details=Details +select_condition=Select Condition +select_a_condition=Select a condition... +condition=Condition +conditions=Conditions +condition_settings=Condition Settings +set_condition_values=Set condition values +select_checkout_prompt=Click here to select the check out location +condition_contains_desc=Enter the text pattern required, including any wildcards. The file name includes the file type extension when matching. +file_name_pattern=File name pattern +condition_contains_hints=Hints +condition_contains_hints_desc=Use zz* to match any name that begins with zz; use *.txt to match any text file; use *zz* to match any file name that contains zz anywhere including the beginning or end. +apply_to_sub_spaces=Apply rule to sub spaces +run_in_background=Run rule in background +not=Not +click_set_and_add=Click to set values and add to list +click_add=Click to add to list +set_and_add_button=Set Values and Add +selected_conditions=Selected Rule Conditions +selected_actions=Selected Rule Actions +condition_no_condition=Match any item +condition_has_aspect=Item has aspect +condition_has_aspect_not=Item does not have aspect +condition_is_subtype=Item is a subtype of +condition_is_subtype_not=Item is not a subtype of +condition_compare_mime_type=Item has a mimetype of +condition_compare_mime_type_not=Item does not have a mimetype of +condition_in_category=Item is in category +condition_in_category_not=Item is not in category +condition_compare_property_value=Name property matches +condition_compare_property_value_not=Name property does not match +action_add_features=Add aspect +action_specialise_type=Item is specialised to type +action_simple_workflow={0} item to ''{1}'' if ''{2}'' action is taken. +action_link_category=Link to category +action_transform=Copies content to ''{0}'' and transforms to ''{1}'' +action_transform_image=Copies image to ''{0}'' and transforms to ''{1}'' using option ''{2}'' +action_copy=Copy to +action_extract_metadata=Extract metadata from content +action_move=Move to +action_mail=Send email to +action_check_in=Check in content as ''{0}'' with comment ''{1}'' +action_check_out=Check out content to +action_set_property_value=Sets property +action_import=Imports to +not_condition_result=Check the item does not match the criteria above +space=Space +import_to=Import To +encoding=Encoding +encoding_utf8=UTF-8 +rule_type=Rule Type +rule_background_info=If this option is selected the rule will execute in the background so the results may not appear immediately. + +# New Space Wizard messages +new_space_title=New Space Wizard +new_space_desc=This wizard helps you to create a new space. +new_space_step1_title=Step One - Starting Space +new_space_step1_desc=Choose how you want to create your space. +new_space_step2_title=Step Two - Space Options +new_space_step2_desc=Select space options. +new_space_step3_title=Step Three - Space Details +new_space_step3_desc=Enter information about the space. +new_space_finish_instruction=To close this wizard and create your space click Finish. To review or change your selections click Back. +scratch=Scratch +an_existing_space=An existing space +a_template=A template +creating_from=Creating From +save_as_template=Save As Template +template_name=Template Name +select_a_template=Select a template... +starting_space=Starting Space +space_options=Space Options +space_details=Space Details +how_to_create_space=How do you want to create your space? +from_scratch=From scratch +based_on_existing_space=Based on an existing space +using_a_template=Using a template +choose_space_icon=Choose space icon +existing_space=Existing Space +copy_existing_space=Copy existing space +structure=Structure +structure_contents=Structure and contents +space_copy_note=Note: Any content rules for spaces will also be copied. +space_type=Space Type +space_type_create=Select the type of space you want to create. +container=Folder Space +container_desc=A place for keeping and organizing documents and other spaces. +forums_desc=A place to discuss content with other users. +space_type_note=Note: If you can only see one type of space then other space types may not be enabled. See your System Administrator for further help. +template_space=Template Space +select_template=Select the template you want to use. + +# New User Wizard messages +new_user_title=New User Wizard +new_user_title_edit=Edit User Wizard +new_user_desc=This wizard helps you to add a user to the repository. +new_user_desc_edit=This wizard helps you modify a user in the repository. +new_user_step1_title=Step One - Person Properties +new_user_step1_desc=Enter information about this person. +new_user_step2_title=Step Two - User Properties +new_user_step2_desc=Enter information about this user. +new_user_finish_instruction=To add the user to this space click Finish. To review or change your selections click Back. +person_properties=Person Properties +user_properties=User Properties +first_name=First Name +last_name=Last Name +email=Email +company_id=Company ID +home_space_location=Home Space Location +home_space_name=Home Space Name + +# Admin Console messages +title_admin_console=Administration Console +admin_console=Administration Console +admin_description=Use this view to perform system administration functions. + +# UI Page Titles +title_about=About Alfresco +title_login=Alfresco Web Client - Login +title_relogin=Alfresco Web Client - Logged Out +title_error=Alfresco Web Client - System Error +title_browse=Alfresco Web Client +title_create_space=Create Space +title_inviteusers_invite=Invite Users - Invite +title_inviteusers_notify=Invite Users - Notify +title_change_user_roles=Change User Roles +title_remove_invited_user=Remove Invited User +title_advanced_search=Advanced Search +title_checkin_file=Check In File +title_checkout_file=Check Out File +title_checkout_file_link=Check Out File Download +title_delete_file=Delete File +title_delete_rule=Delete Rule +title_delete_user=Delete User +title_delete_space=Delete Space +title_file_details=Document Details +title_file_preview=Preview In Template +title_edit_categories=Edit Categories +title_edit_doc_props=Edit Document Properties +title_edit_file=Edit File +title_edit_html_inline=Edit HTML File Inline +title_edit_text_inline=Edit Text File Inline +title_edit_simple_workflow=Edit Simple Workflow +title_edit_space=Edit Space Details +title_rules=Space Rules +title_space_details=Space Details +title_system_info=System Information +title_undo_checkout=Undo Check Out +title_update_file=Update File Content +title_users=User Management +title_invited_users=Manage Invited Users +title_add_content_props=Add Content - Properties +title_add_content_summary=Add Content - Summary +title_add_content_upload=Add Content - Upload +title_create_content=Create New Content +title_create_content_props=Create New Content - Properties +title_create_content_summary=Create New Content - Summary +title_create_action_action=Create Action - Select Action +title_create_action_add_feature=Create Action - Add Feature +title_create_action_checkin=Create Action - Check In +title_create_action_checkout=Create Action - Check Out +title_create_action_copy=Create Action - Copy +title_create_action_import=Create Action - Import +title_create_action_email=Create Action - Email +title_create_action_link_category=Create Action - Link Category +title_create_action_move=Create Action - Move +title_create_action_simple_workflow=Create Action - Simple Workflow +title_create_action_transform=Create Action - Transform +title_create_action_transform_image=Create Action - Transform Image +title_create_specialise_action=Create Action - Specialise Type +title_create_action_summary=Create Action - Summary +title_new_rule_action=New Rule - Select Action +title_new_rule_add_feature=New Rule - Add Feature +title_specialise_type_action=New Rule - Specialise Type +title_new_rule_checkin=New Rule - Check In +title_new_rule_checkout=New Rule - Check Out +title_new_rule_copy=New Rule - Copy +title_new_rule_email=New Rule - Email +title_new_rule_link_category=New Rule - Link Category +title_new_rule_move=New Rule - Move +title_new_rule_simple_workflow=New Rule - Simple Workflow +title_new_rule_transform=New Rule - Transform +title_new_rule_transform_image=New Rule - Transform Image +title_new_rule_import=New Rule - Import +title_new_rule_condition=New Rule - Conditions +title_new_rule_cond_contains=New Rule - Contains Text +title_new_rule_cond_mimetype=New Rule - Has Mimetype +title_new_rule_cond_category=New Rule - In Category +title_new_rule_cond_subtype=New Rule - Is Subtype +title_new_rule_cond_aspect=New Rule - Has Subtype +title_new_rule_details=New Rule - Details +title_new_rule_summary=New Rule - Summary +title_new_space_create-from=New Space - Create From +title_new_space_details=New Space - Details +title_new_space_existing=New Space - From Existing +title_new_space_scratch=New Space - From Scratch +title_new_space_template=New Space - From Template +title_new_space_summary=New Space - Summary +title_new_user_person_props=User - Person Properties +title_new_user_user_props=User - User Properties +title_new_user_summary=User - Summary +title_export=Export +title_import=Import +title_admin_store_browser=Alfresco Store Browser +title_admin_node_browser=Alfresco Node Browser +title_admin_search_results=Node Browser Search Results +title_forums=Forum Space +title_forum=Forum +title_topic=Topic +title_delete_forums_space=Delete Forum Space +title_delete_forum_space=Delete Forum +title_delete_topic_space=Delete Topic +title_delete_post=Delete Post +title_create_forums=Create Forum Space +title_create_forum=Create Forum +title_forums_details=Forum Space Details +title_forum_details=Forum Details +title_create_topic=Create Topic +title_topic_details=Topic Details +title_create_post=Post Message +title_create_reply=Create Reply + +# UI Error messages +error_generic=A system error happened during the operation: {0} +error_noderef=Unable to find the repository item referenced by Id: {0} - the record has probably been deleted from the database. +error_deleted_folder=The folder item referenced by Id: {0} - has been deleted from the database. The system has changed your folder location as the folder you were in no longer exists. +error_homespace=The Home Space node referenced by Id: {0} cannot be found. It may have been deleted from the database. Please contact your system administrator. +error_search=Search failed due to system error: {0} +error_exists=A Space or File with that name already exists: {0} +error_delete_space=Unable to delete Space due to system error: +error_delete_file=Unable to delete File due to system error: +error_checkout=Unable to check out Content Node due to system error: +error_update=Unable to update Content Node due to system error: +error_cancel_checkout=Unable to cancel check out of Content Node due to system error: +error_checkin=Unable to check in Content Node due to system error: +error_paste=Unable to paste item due to system error: +error_login_user=Unable to login - unknown username/password. +error_login_missing=Must specify username and password. +error_delete_rule=Unable to delete Rule due to system error: +error_action=Failed to run Action due to error: {0} +error_rule=Failed to create Rule due to error: {0} +error_space=Failed to create new space due to error: {0} +error_person=Failed to create Person due to error: {0} +error_delete_user=Failed to delete User due to error: {0} +error_remove_user=Failed to remove User due to error: {0} +error_password_match=Please ensure that both password fields contain the same value. +error_property=Property ''{0}'' is not available for this node +error_child_association=Child association ''{0}'' is not available for this node +error_not_child_association=The association named ''{0}'' is not a child association +error_association=Association ''{0}'' is not available for this node +error_not_association=The association named ''{0}'' is not an association +error_create_space_dialog=Please correct the errors below then click Create Space. +error_create_forums_dialog=Please correct the errors below then click Create Forum Space. +error_create_forum_dialog=Please correct the errors below then click Create Forum. +error_create_topic_dialog=Please correct the errors below then click Create Topic. +error_create_post_dialog=Please correct the errors below then click Post. +error_create_reply_dialog=Please correct the errors below then click Reply. +error_create_category_dialog=Please correct the errors below then click New Category. +error_create_group_dialog=Please correct the errors below then click Create Group. +error_wizard=Please correct the errors below then click Finish. +error_update_category=Failed to update category due to system error: {0} +error_update_simpleworkflow=Failed to update simple workflow due to system error: {0} +error_workflow_approve=Failed to approve the document due to system error: {0} +error_workflow_reject=Failed to reject the document due to system error: {0} +error_aspect_classify=Failed to apply the ''classifiable'' aspect to the document due to system error: {0} +error_aspect_versioning=Failed to apply the ''versionable'' aspect to the document due to system error: {0} +error_aspect_inlineeditable=Failed to apply the ''inlineeditable'' aspect to the document due to system error: {0} +error_content_missing=The node''s content is missing: \n node: {0} \n reader: {1} \nPlease contact your system administrator. +error_export=Failed to execute export: {0} +error_import=Failed to execute import: {0} +error_import_no_file=Can not find an ACP file to import! +error_import_empty_file=You can not import an empty ACP file! +error_import_all=Please correct the import errors below then click OK. +error_export_all=Please correct the export errors below then click OK. + +# Confirmations +return_to_application=Return to application +delete_space_info=To remove this space and all of its contents, click Delete. +delete_space_confirm=Are you sure you want to delete \"{0}\" and all its contents? +delete_forums_info=To remove this forum space and its contents, click Delete. +delete_forum_info=To remove this forum and its topics, click Delete. +delete_forum_confirm=Are you sure you want to delete \"{0}\" and all its topics? +delete_topic_info=To remove this topic and its posts, click Delete. +delete_topic_confirm=Are you sure you want to delete \"{0}\" and all its posts? +delete_post_info=To remove this post, click Delete. +delete_post_confirm=Are you sure you want to delete \"{0}\"? +delete_file_info=To remove this file and any previous versions, click Delete. +delete_file_confirm=Are you sure you want to delete \"{0}\" and all previous versions? +delete_rule_info=To remove this rule, click Delete. +delete_user_info=To delete this user, click Delete. +delete_rule_confirm=Are you sure you want to delete \"{0}\"? +delete_user_confirm=Are you sure you want to delete user \"{0}\"? The User will no longer be able to access the system. +remove_invited_user_confirm=Are you sure you want to remove user \"{0}\"? The User will no longer be able to access the documents and folders in this space. +delete_companyroot_confirm=WARNING: This folder is a special folder accessed by all Users! Please be sure that you wish to delete this folder. It may cause System Errors if you remove it. + +# Status Messages +status_space_created=Successfully created space ''{0}''. +status_space_deleted=Successfully deleted space ''{0}''. +status_space_updated=Successfully updated space ''{0}''. diff --git a/config/alfresco/templates/company_logos.ftl b/config/alfresco/templates/company_logos.ftl new file mode 100644 index 0000000000..51befa00b1 --- /dev/null +++ b/config/alfresco/templates/company_logos.ftl @@ -0,0 +1,20 @@ +<#-- Table of the images found in a folder under Company Home called "Company Logos" --> +<#-- Shows each image found as inline content --> + + <#list companyhome.children as child> + <#if child.isContainer && child.name = "Company Logos"> + <#list child.children as image> + <#switch image.mimetype> + <#case "image/jpeg"> + <#case "image/gif"> + <#case "image/png"> + + + + <#break> + <#default> + + + + +
diff --git a/config/alfresco/templates/doc_info.ftl b/config/alfresco/templates/doc_info.ftl new file mode 100644 index 0000000000..859ba73dd7 --- /dev/null +++ b/config/alfresco/templates/doc_info.ftl @@ -0,0 +1,17 @@ +<#-- Shows some general info about the current document, including NodeRef and aspects applied --> +

=====Template Start=====

+ +

Current Document Info:

+Name: ${document.name}
+Ref: ${document.nodeRef}
+Type: ${document.type}
+Content URL: /alfresco${document.url}
+Locked: <#if document.isLocked>Yes<#else>No
+Aspects: + +<#list document.aspects as aspect> + + +
${aspect}
+ +

=====Template End=====

diff --git a/config/alfresco/templates/example.ftl b/config/alfresco/templates/example.ftl new file mode 100644 index 0000000000..28628aa543 --- /dev/null +++ b/config/alfresco/templates/example.ftl @@ -0,0 +1,47 @@ +

=====Example Template Start=====

+ +Company Home Space: ${companyhome.properties.name} +
+My Home Space: ${userhome.properties.name} +
+Company Home children count: ${companyhome.children?size} +
+Company Home first child node name: ${companyhome.children[0].properties.name} +
+Current Document Name: ${document.name} +
+Current Space Name: ${space.name} + +

List of child spaces in my Home Space:

+ +<#list userhome.children as child> + <#if child.isContainer> + + + + + + + +
${child.properties.name} (${child.children?size})Path: ${child.displayPath}
+ +

List of docs in my Home Space (text only content shown inline, JPG images shown as thumbnails):

+ +<#list userhome.children as child> + <#if child.isDocument> + + <#if child.mimetype = "text/plain"> + + <#elseif child.mimetype = "image/jpeg"> + + + + +
${child.properties.name}
${child.content}
+ +

Assoc example:

+<#if userhome.children[0].assocs["cm:contains"]?exists> + ${userhome.children[0].assocs["cm:contains"][0].name} + + +

=====Example Template End=====

diff --git a/config/alfresco/templates/localizable.ftl b/config/alfresco/templates/localizable.ftl new file mode 100644 index 0000000000..12cf233a83 --- /dev/null +++ b/config/alfresco/templates/localizable.ftl @@ -0,0 +1,10 @@ +<#-- Shows if a document is localizable and the locale if set --> +Localisable: +<#if hasAspect(document, "cm:localizable") = 1> + Yes
+ <#if document.properties.locale?exists> + Locale: ${document.properties.locale.properties.name} + +<#else> + No
+ diff --git a/config/alfresco/templates/my_docs.ftl b/config/alfresco/templates/my_docs.ftl new file mode 100644 index 0000000000..1a31125b52 --- /dev/null +++ b/config/alfresco/templates/my_docs.ftl @@ -0,0 +1,20 @@ +<#-- Table of the documents in my Home Space --> +<#-- Shows the Icon and link to the content for the doc, also the size in KB and lock status --> + + + + + + + + <#list userhome.children as child> + <#if child.isDocument> + + + + + + + + +
NameSizeLocked
${child.properties.name}${(child.size / 1000)?string("0.##")} KB <#if child.isLocked>Yes
diff --git a/config/alfresco/templates/my_pressreleases.ftl b/config/alfresco/templates/my_pressreleases.ftl new file mode 100644 index 0000000000..f4c4af2157 --- /dev/null +++ b/config/alfresco/templates/my_pressreleases.ftl @@ -0,0 +1,28 @@ +<#-- Displays a table of all the documents from a "Press Releases" folder under Company Home --> +<#-- Obviously this folder needs to exist and the docs in it should have the title and description fields set --> + + <#list companyhome.children as child> + <#if child.isContainer && child.name = "Press Releases"> + <#list child.children as doc> + <#if doc.isDocument> + + + + + + + + + + + + + + +
${doc.properties.title}
${doc.properties.description}
+ <#if (doc.content?length > 500)> + ${doc.content[0..500]}... + <#else> + ${doc.content} + +
diff --git a/config/alfresco/templates/my_spaces.ftl b/config/alfresco/templates/my_spaces.ftl new file mode 100644 index 0000000000..f83005cab4 --- /dev/null +++ b/config/alfresco/templates/my_spaces.ftl @@ -0,0 +1,15 @@ +<#-- Table of the Spaces in my Home Folder --> +<#-- Shows the large 32x32 pixel icon, and generates an external access servlet URL to the space --> + + <#list userhome.children as child> + <#if child.isContainer> + + + <#assign ref=child.nodeRef> + <#assign workspace=ref[0..ref?index_of("://")-1]> + <#assign storenode=ref[ref?index_of("://")+3..]> + + + + +
${child.properties.name} (${child.children?size})
diff --git a/config/alfresco/templates/my_summary.ftl b/config/alfresco/templates/my_summary.ftl new file mode 100644 index 0000000000..c2803041f9 --- /dev/null +++ b/config/alfresco/templates/my_summary.ftl @@ -0,0 +1,8 @@ +<#-- Table of some summary details about the current user --> + + + + + + +
Name: ${person.properties.firstName} ${person.properties.lastName}
User: ${person.properties.userName}
Home Space location: ${userhome.displayPath}/${userhome.name}
Items in Home Space: ${userhome.children?size}
Items in Company Space: ${companyhome.children?size}
diff --git a/config/alfresco/templates/namepath_xpath.ftl b/config/alfresco/templates/namepath_xpath.ftl new file mode 100644 index 0000000000..0aace9cd38 --- /dev/null +++ b/config/alfresco/templates/namepath_xpath.ftl @@ -0,0 +1,19 @@ +<#-- Shows use of the childByNamePath and childrenByXPath API --> + +

Template Documents in 'Company Home/Data Dictionary/Content Templates':

+ +<#list companyhome.childByNamePath["Data Dictionary/Content Templates"].children as child> + <#if child.isDocument> + + + +
${child.properties.name}
+ +

Folders in 'Company Home/Data Dictionary/Space Templates/Software Engineering Project':

+ +<#list companyhome.childrenByXPath["*[@cm:name='Data Dictionary']/*[@cm:name='Space Templates']/*[@cm:name='Software Engineering Project']/*"] as child> + <#if child.isContainer> + + + +
${child.properties.name}
diff --git a/config/alfresco/templates/recent_docs.ftl b/config/alfresco/templates/recent_docs.ftl new file mode 100644 index 0000000000..698bd2af20 --- /dev/null +++ b/config/alfresco/templates/recent_docs.ftl @@ -0,0 +1,20 @@ +<#-- Table of docs in a specific folder, that have been created or modified in the last week --> +

Documents created or modified in the last week

+ + + + + + + + <#list space.childrenByXPath[".//*[subtypeOf('cm:content')]"] as child> + <#if (dateCompare(child.properties["cm:modified"], date, 1000*60*60*24*7) == 1) || (dateCompare(child.properties["cm:created"], date, 1000*60*60*24*7) == 1)> + + + + + + + + +
NameCreated DateModified Date
${child.properties.name}${child.properties["cm:created"]?datetime}${child.properties["cm:modified"]?datetime}
diff --git a/config/alfresco/templates/system_users.ftl b/config/alfresco/templates/system_users.ftl new file mode 100644 index 0000000000..da73637c95 --- /dev/null +++ b/config/alfresco/templates/system_users.ftl @@ -0,0 +1,17 @@ +

System users, company Ids and home space information

+ + + + + + + + <#list space.childrenByXPath["/sys:system/sys:people/*[subtypeOf('cm:person')]"] as child> + + + + + + + +
Company IDUsernameHome Space PathCreation Date
${child.properties["cm:organizationId"]}${child.properties["cm:userName"]}${child.properties["cm:homeFolder"].displayPath}${child.properties["cm:homeFolder"].properties["cm:created"]?date}
diff --git a/config/alfresco/templates/translatable.ftl b/config/alfresco/templates/translatable.ftl new file mode 100644 index 0000000000..f47dc69d98 --- /dev/null +++ b/config/alfresco/templates/translatable.ftl @@ -0,0 +1,12 @@ +<#-- Shows the translations applied to a doc through the translatable aspect --> +Translatable: +<#if hasAspect(document, "cm:translatable") = 1> + Yes
+ + <#list document.assocs["cm:translations"] as t> + + +
${t.content}
+<#else> + No
+ diff --git a/config/alfresco/templates/userhome_docs.ftl b/config/alfresco/templates/userhome_docs.ftl new file mode 100644 index 0000000000..180fb92aa7 --- /dev/null +++ b/config/alfresco/templates/userhome_docs.ftl @@ -0,0 +1,15 @@ +<#-- List of docs in the Home Space for current user --> +<#-- If the doc mimetype is plain/text then the content is shown inline --> +<#-- If the doc mimetype is JPEG then the image is shown inline as a small thumbnail image --> + +<#list userhome.children as child> + <#if child.isDocument> + + <#if child.mimetype = "text/plain"> + + <#elseif child.mimetype = "image/jpeg"> + + + + +
${child.properties.name}
${child.content}
diff --git a/config/alfresco/web-client-application-context.xml b/config/alfresco/web-client-application-context.xml new file mode 100644 index 0000000000..c047252535 --- /dev/null +++ b/config/alfresco/web-client-application-context.xml @@ -0,0 +1,28 @@ + + + + + + + + + alfresco/web-client-config.xml + alfresco/web-client-config-edit-properties.xml + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/web-client-config-edit-properties.xml b/config/alfresco/web-client-config-edit-properties.xml new file mode 100644 index 0000000000..86e4810caf --- /dev/null +++ b/config/alfresco/web-client-config-edit-properties.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/web-client-config-zoo.xml b/config/alfresco/web-client-config-zoo.xml new file mode 100644 index 0000000000..0b55b77eec --- /dev/null +++ b/config/alfresco/web-client-config-zoo.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/web-client-config.xml b/config/alfresco/web-client-config.xml new file mode 100644 index 0000000000..f9fa193ad9 --- /dev/null +++ b/config/alfresco/web-client-config.xml @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + /jsp/error.jsp + + /jsp/error.jsp + + /jsp/error_missing.jsp + + + /jsp/login.jsp + + + admin + + + + + English + + + + + + + + + + + + + + + + + + + + + + org.alfresco.web.ui.common.renderer.data.RichListRenderer$DetailsViewRenderer + org.alfresco.web.ui.common.renderer.data.RichListRenderer$IconViewRenderer + org.alfresco.web.ui.common.renderer.data.RichListRenderer$ListViewRenderer + org.alfresco.web.bean.ForumsBean$TopicBubbleViewRenderer + + + + + + + icons + + 10 +
10
+ 9 +
+
+ + + list + + 20 +
20
+ 20 +
+
+ + + details + +
20
+
+
+ + + bubble + created + true + + 5 +
20
+
+
+
+ + + 6 + + + 3 + + + + + + Guest + + + http://www.alfresco.org/mediawiki/index.php/Client_Help + + + + http +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/project-build.xml b/project-build.xml new file mode 100644 index 0000000000..e03427694f --- /dev/null +++ b/project-build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + isTomcat = ${isTomcat} + isJBoss = ${isJBoss} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project-override.properties b/project-override.properties new file mode 100644 index 0000000000..2c25b41451 --- /dev/null +++ b/project-override.properties @@ -0,0 +1,4 @@ +file.name.war=alfresco.war + +javadoc.title.window=Alfresco Web Client API +javadoc.title.document=Alfresco Web Client API Specification diff --git a/project.properties b/project.properties new file mode 100644 index 0000000000..7e67da7136 --- /dev/null +++ b/project.properties @@ -0,0 +1,4 @@ +webinf.delete.tomcat=jboss*.xml,portlet*.xml,alfresco-pages.xml +webinf.lib.delete.jboss=log4j-1.2.8.jar,portlet-api-lib.jar + +files.faces.config=/WEB-INF/faces-config-navigation.xml,/WEB-INF/faces-config-common.xml,/WEB-INF/faces-config-repo.xml,/WEB-INF/faces-config-zoo.xml diff --git a/source/java/jsftest/DummyBean.java b/source/java/jsftest/DummyBean.java new file mode 100644 index 0000000000..27f983121c --- /dev/null +++ b/source/java/jsftest/DummyBean.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest; + +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Object that can be used as a backing bean for components in the zoo + * + * @author gavinc + */ +public class DummyBean +{ + private static Log logger = LogFactory.getLog(DummyBean.class); + + private String name; + private Properties properties; + + public DummyBean() + { + this.properties = new Properties(); + this.properties.put("one", "This is 1"); + this.properties.put("two", "This is 2"); + this.properties.put("three", "This is 3"); + this.properties.put("four", "This is 4"); + } + + public Properties getProperties() + { + return this.properties; + } + + /** + * @return Returns the name. + */ + public String getName() + { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder builder = new StringBuilder(super.toString()); + builder.append(" (name=").append(this.name); + builder.append(" properties=").append(this.properties).append(")"); + return builder.toString(); + } + + /** + * Method to call on form submit buttons + */ + public void submit() + { + if (logger.isDebugEnabled()) + logger.debug("Submit called on DummyBean, state = " + toString()); + } +} diff --git a/source/java/jsftest/TestList.java b/source/java/jsftest/TestList.java new file mode 100644 index 0000000000..f7fba1669b --- /dev/null +++ b/source/java/jsftest/TestList.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Map; + +import javax.faces.event.ActionEvent; + +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIBreadcrumb; +import org.apache.log4j.Logger; + +/** + * @author kevinr + */ +public class TestList +{ + /** + * Constructor + */ + public TestList() + { + // Create test data rows + + Calendar date = new GregorianCalendar(1999, 1, 5); + rows.add(new TestRow("monkey", 5, true, 0.1f, date.getTime())); + date = new GregorianCalendar(2000, 12, 5); + rows.add(new TestRow("biscuit", 15, true, 0.2f, date.getTime())); + date = new GregorianCalendar(1999, 11, 15); + rows.add(new TestRow("HORSEY", 23, false, 0.333f, date.getTime())); + date = new GregorianCalendar(2003, 11, 11); + rows.add(new TestRow("thing go here", 5123, true, 0.999f, date.getTime())); + date = new GregorianCalendar(1999, 2, 3); + rows.add(new TestRow("I like docs", -5, false, 0.333f, date.getTime())); + date = new GregorianCalendar(2005, 1, 1); + rows.add(new TestRow("Document", 1235, false, 12.0f, date.getTime())); + date = new GregorianCalendar(1998, 8, 8); + rows.add(new TestRow("1234567890", 52, false, 5.0f, date.getTime())); + date = new GregorianCalendar(1997, 9, 30); + rows.add(new TestRow("space", 77, true, 17.5f, date.getTime())); + date = new GregorianCalendar(2001, 7, 15); + rows.add(new TestRow("House", 12, true, 0.4f, date.getTime())); + date = new GregorianCalendar(2002, 5, 28); + rows.add(new TestRow("Baboon", 14, true, -0.888f, date.getTime())); + date = new GregorianCalendar(2003, 11, 11); + rows.add(new TestRow("Woof", 0, true, 0.0f, date.getTime())); + } + + public List getRows() + { + return this.rows; + } + + public void clickBreadcrumb(ActionEvent event) + { + if (event.getComponent() instanceof UIBreadcrumb) + { + s_logger.debug("clickBreadcrumb action listener called, path now: " + ((UIBreadcrumb)event.getComponent()).getValue()); + } + } + + public void clickActionLink(ActionEvent event) + { + s_logger.debug("clickActionLink"); + } + + public void clickNameLink(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String value = params.get("name"); + if (value != null) + { + s_logger.debug("clicked item in list: " + value); + } + } + + + private final static Logger s_logger = Logger.getLogger(TestList.class); + + private List rows = new ArrayList();; +} diff --git a/source/java/jsftest/TestRow.java b/source/java/jsftest/TestRow.java new file mode 100644 index 0000000000..612a977913 --- /dev/null +++ b/source/java/jsftest/TestRow.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest; + +import java.util.Date; + +/** + * @author kevinr + */ +public class TestRow +{ + /** + * Test a row bean with various data types + */ + public TestRow(String name, int count, boolean valid, float relevance, Date created) + { + this.name = name; + this.count = count; + this.valid = valid; + this.relevance = relevance; + this.created = created; + } + + public String getName() + { + return name; + } + + public int getCount() + { + return count; + } + + public boolean getValid() + { + return valid; + } + + public float getRelevance() + { + return relevance; + } + + public Date getCreated() + { + return created; + } + + public void setCreated(Date date) + { + this.created = date; + } + + public TestRow getObject() + { + return this; + } + + + private String name; + private int count; + private boolean valid; + private float relevance; + private Date created; +} diff --git a/source/java/jsftest/User.java b/source/java/jsftest/User.java new file mode 100644 index 0000000000..9f0daa8c49 --- /dev/null +++ b/source/java/jsftest/User.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.faces.model.SelectItem; + + +/** + * Class representing a single User bean instance. + * + * @author kevinr + */ +public class User implements Cloneable +{ + public User() + { + setRoles(new ArrayList(4)); + } + + public User(String username, String password, String name, String[] roles, Date joined) + { + setUsername(username); + setPassword(password); + setName(name); + setDateJoined(joined); + List rolesList = new ArrayList(roles.length); + for (int i=0; i(u.getRoles())); + } + + /** + * @see java.lang.Object#clone() + */ + protected Object clone() throws CloneNotSupportedException + { + // invoke copy constructor + return new User(this); + } + + /** + * Get the username + * + * @return the username + */ + public String getUsername() + { + return m_username; + } + + /** + * Set the username + * + * @param username the username + */ + public void setUsername(String username) + { + m_username = username; + } + + /** + * Get the name + * + * @return the name + */ + public String getName() + { + return m_name; + } + + /** + * Set the name + * + * @param name the name + */ + public void setName(String name) + { + m_name = name; + } + + /** + * Get the roles + * + * @return the roles + */ + public List getRoles() + { + return m_roles; + } + + /** + * Set the roles + * + * @param roles the roles + */ + public void setRoles(List roles) + { + m_roles = roles; + } + + /** + * Get the password + * + * @return the password + */ + public String getPassword() + { + return m_password; + } + + /** + * Set the password + * + * @param password the password + */ + public void setPassword(String password) + { + m_password = password; + } + + /** + * Get the All Roles List + * + * @return the allRolesList + */ + public List getAllRolesList() + { + return m_allRolesList; + } + + /** + * Set the allRolesList + * + * @param allRolesList the allRolesList + */ + public void setAllRolesList(List allRolesList) + { + m_allRolesList = allRolesList; + } + + /** + * Get the dateJoined + * + * @return the dateJoined + */ + public Date getDateJoined() + { + return m_dateJoined; + } + + /** + * Set the dateJoined + * + * @param dateJoined the dateJoined + */ + public void setDateJoined(Date dateJoined) + { + m_dateJoined = dateJoined; + } + + + /** the allRolesList enum list */ + private static List m_allRolesList = new ArrayList(8); + static + { + m_allRolesList.add(new SelectItem("admin", "Administrator")); + m_allRolesList.add(new SelectItem("superuser", "Super User")); + m_allRolesList.add(new SelectItem("dev", "Developer")); + m_allRolesList.add(new SelectItem("qa", "QA")); + m_allRolesList.add(new SelectItem("standard", "Basic User")); + } + + + /** the password */ + private String m_password; + + /** the username */ + private String m_username; + + /** the name */ + private String m_name; + + /** the roles */ + private List m_roles; + + /** the date joined */ + private Date m_dateJoined; +} \ No newline at end of file diff --git a/source/java/jsftest/UserListBean.java b/source/java/jsftest/UserListBean.java new file mode 100644 index 0000000000..d3921e3ff3 --- /dev/null +++ b/source/java/jsftest/UserListBean.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; + +import javax.faces.application.FacesMessage; +import javax.faces.component.UIComponent; +import javax.faces.component.UIParameter; +import javax.faces.component.html.HtmlOutputText; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.event.ValueChangeEvent; +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.faces.validator.ValidatorException; + +import org.apache.log4j.Logger; + +/** + * JSF Managed Bean. Provides the backing for the userlist.jsp view. The view uses + * the datatable control to bind to the List of User bean objects. Implements the + * action events called by the view when the user clicks the Edit link or Add button. + * + * @author kevinr + */ +public class UserListBean +{ + // =========================================================================== + // Construction + + public UserListBean() + { + Calendar date = new GregorianCalendar(2002, 5, 10); + m_users.add(new User("admin", "admin", "Administrator", new String[] {"admin","superuser"}, date.getTime())); + date = new GregorianCalendar(2001, 7, 10); + m_users.add(new User("kevinr", "kevinr", "Kevin Roast", new String[] {"admin","superuser","dev"}, date.getTime())); + date = new GregorianCalendar(2003, 8, 15); + m_users.add(new User("gavinc", "gavinc", "Gavin Cornwell", new String[] {"superuser","dev"}, date.getTime())); + date = new GregorianCalendar(2003, 1, 1); + m_users.add(new User("stever", "stever", "Steve Rigby", new String[] {"superuser","qa"}, date.getTime())); + } + + + // =========================================================================== + // Bean methods + + public List getUsers() + { + return m_users; + } + + public void setUsers(List users) + { + m_users = users; + } + + /** + * Get the users list as a wrapped DataModel object + * + * @return DataModel for use by the data-table components + */ + public DataModel getUsersModel() + { + if (m_usersModel == null) + { + m_usersModel = new ListDataModel(); + m_usersModel.setWrappedData(m_users); + } + + return m_usersModel; + } + + public User getUser() + { + return m_currentUser; + } + + public void setUser(User user) + { + m_currentUser = user; + } + + /** + * Get the isNewUser + * + * @return the isNewUser + */ + public boolean getIsNewUser() + { + return m_isNewUser; + } + + /** + * Set the isNewUser + * + * @param isNewUser the isNewUser + */ + public void setIsNewUser(boolean isNewUser) + { + m_isNewUser = isNewUser; + } + + /** + * Get the rolesOutputText + * + * @return the rolesOutputText + */ + public HtmlOutputText getRolesOutputText() + { + return m_rolesOutputText; + } + + /** + * Set the rolesOutputText + * + * @param rolesOutputText the rolesOutputText component + */ + public void setRolesOutputText(HtmlOutputText rolesOutputText) + { + m_rolesOutputText = rolesOutputText; + } + + + + // =========================================================================== + // Action event methods + + /** + * Edit user action event listener + * + * Specified directly on the appropriate tag such as commandLink or commandButton + * e.g. actionListener="#{UserListBean.editUser}" + * + * This listener cannot directly affect the navigation of the page - the command + * tag has an "action" attribute of which the default handler will use the outcome + * from the faces-config.xml by default or call a specifid controller method + * returning the String outcome as usual. + */ + public void editUser(ActionEvent event) + { + s_logger.debug("*****USERLIST: " + ((UIParameter)event.getComponent().findComponent("userId")).getValue().toString()); + + // Get the username from the "param" tag component we added as a nested tag + // to the command tag that fired this event. + // So we can have a key to work out which item was clicked in the data table + String usernameId = ((UIParameter)event.getComponent().findComponent("userId")).getValue().toString(); + + // It is also possible to get the relevent row from the DataModel we created + // wrapping our users list. But this is a weak solution as models which then + // potentially sort or page data may not provide the correct row index. + // e.g. + // m_usersModel.getWrappedData().get(m_usersModel.getRowIndex()); + + for (Iterator i=m_users.iterator(); i.hasNext(); /**/) + { + User user = (User)i.next(); + if (user.getUsername().equals(usernameId)) + { + // set the user as current so we know which one to edit etc. + try + { + setUser((User)user.clone()); + setIsNewUser(false); + } + catch (CloneNotSupportedException e) + { + // will not happen - clone is supported for our own types + } + } + } + } + + /** + * OK button action handler + * + * @return outcome view name + */ + public void editUserOK(ActionEvent event) + { + s_logger.debug("*****USERLIST: persisting user: " + getUser().getUsername()); + for (int i=0; i 12) + { + String err = "Username must be between 5 and 12 characters in length."; + throw new ValidatorException(new FacesMessage(err)); + } + else if (username.indexOf(' ') != -1 || username.indexOf('\t') != -1) + { + String err = "Username cannot contain space or whitespace characters."; + throw new ValidatorException(new FacesMessage(err)); + } + } + + + // =========================================================================== + // Private data + + private List m_users = new ArrayList(); + private DataModel m_usersModel = null; + private User m_currentUser = null; + private boolean m_isNewUser = false; + + /** the HTMLOutputText component */ + private HtmlOutputText m_rolesOutputText = null; + + protected final static Logger s_logger = Logger.getLogger(UserListBean.class); +} \ No newline at end of file diff --git a/source/java/jsftest/ZooService.java b/source/java/jsftest/ZooService.java new file mode 100644 index 0000000000..e50d6f3780 --- /dev/null +++ b/source/java/jsftest/ZooService.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest; + +/** + * Managed bean that provides action handlers to navigate around the component zoo + * + * @author gavinc + */ +public class ZooService +{ + public String showPropertyZoo() + { + return "showPropertyZoo"; + } + + public String showPropertyZoo2() + { + return "showPropertyZoo2"; + } +} diff --git a/source/java/jsftest/repository/DataDictionary.java b/source/java/jsftest/repository/DataDictionary.java new file mode 100644 index 0000000000..4a01f57095 --- /dev/null +++ b/source/java/jsftest/repository/DataDictionary.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest.repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Class to represent a basic data dictionary service + * + * @author gavinc + */ +public class DataDictionary +{ + private Map types; + + public DataDictionary() + { + this.types = new HashMap(); + + // setup the dictionary + Property name = new Property("name", "string", "Name", false); + Property desc = new Property("description", "string", "Description" , false); + Property created = new Property("created", "datetime", "Created Date", true); + Property modified = new Property("modified", "datetime", "Modified Date", false); + Property keywords = new Property("keywords", "string[]", "Keywords", false); + + MetaData base = new MetaData("base"); + base.addProperty(name); + base.addProperty(desc); + base.addProperty(created); + base.addProperty(modified); + base.addProperty(keywords); + + Property sopid = new Property("sopId", "string", "SOP ID", true); + Property effective = new Property("effective", "datetime", "Effective Date", false); + Property approved = new Property("approved", "boolean", "Approved", false); + Property latestVersion = new Property("latestversion", "string", "Latest Version", true); + + MetaData sop = new MetaData("SOP"); + sop.setProperties(base.getProperties()); + sop.addProperty(sopid); + sop.addProperty(effective); + sop.addProperty(approved); + // add an aspect and the associated property + sop.addAspect("versionable"); + sop.addProperty(latestVersion); + + this.types.put(base.getTypeName(), base); + this.types.put(sop.getTypeName(), sop); + } + + public MetaData getMetaData(String type) + { + return (MetaData)this.types.get(type); + } + + /** + * @return Returns the types. + */ + public Map getTypes() + { + return this.types; + } + + + // ********************* + // *** Inner classes *** + // ********************* + + /** + * Represents the meta data of an object + * @author gavinc + */ + public class MetaData + { + private Map propertiesMap; + private List properties; + private String typeName; + private List aspects; + + public MetaData(String typeName) + { + this.properties = new ArrayList(); + this.propertiesMap = new HashMap(); + this.aspects = new ArrayList(); + this.typeName = typeName; + } + + /** + * Adds a property to the meta data object + * + * @author gavinc + */ + public void addProperty(Property property) + { + this.properties.add(property); + this.propertiesMap.put(property.getName(), property); + } + + /** + * @return Returns the properties. + */ + public List getProperties() + { + return this.properties; + } + + /** + * @param properties The properties to set. + */ + public void setProperties(List properties) + { + this.properties.clear(); + this.propertiesMap.clear(); + + Iterator iter = properties.iterator(); + while (iter.hasNext()) + { + Property prop = (Property)iter.next(); + this.properties.add(prop); + this.propertiesMap.put(prop.getName(), prop); + } + } + + public Map getPropertiesMap() + { + return this.propertiesMap; + } + + public List getAspects() + { + return this.aspects; + } + + public void addAspect(String aspect) + { + this.aspects.add(aspect); + } + + /** + * @return Returns the typeName. + */ + public String getTypeName() + { + return this.typeName; + } + } + + /** + * Represents a property on an object + * @author gavinc + */ + public class Property + { + private String name; + private String type; + private String displayName; + private boolean readOnly; + + /** + * @param name + * @param type + * @param readOnly + */ + public Property(String name, String type, String displayName, boolean readOnly) + { + this.name = name; + this.type = type; + this.displayName = displayName; + this.readOnly = readOnly; + } + + /** + * @return Returns the name. + */ + public String getName() + { + return this.name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @return Returns the type. + */ + public String getType() + { + return this.type; + } + + /** + * @param type The type to set. + */ + public void setType(String type) + { + this.type = type; + } + + /** + * @return Returns the displayName. + */ + public String getDisplayName() + { + return this.displayName; + } + + /** + * @param displayName The displayName to set. + */ + public void setDisplayName(String displayName) + { + this.displayName = displayName; + } + + /** + * @return Returns the readOnly. + */ + public boolean isReadOnly() + { + return this.readOnly; + } + + /** + * @param readOnly The readOnly to set. + */ + public void setReadOnly(boolean readOnly) + { + this.readOnly = readOnly; + } + } + +} \ No newline at end of file diff --git a/source/java/jsftest/repository/NodeRef.java b/source/java/jsftest/repository/NodeRef.java new file mode 100644 index 0000000000..388ee4258b --- /dev/null +++ b/source/java/jsftest/repository/NodeRef.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest.repository; + +import java.io.Serializable; + +/** + * Mock NodeRef object that comes from the Mock NodeService API. + * + * @author gavinc + */ +public class NodeRef implements Serializable +{ + private static final long serialVersionUID = 3833183614468175153L; + + private String id; + + public NodeRef(String id) + { + this.id = id; + } + + /** + * @return Returns the id. + */ + public String getId() + { + return id; + } +} diff --git a/source/java/jsftest/repository/NodeService.java b/source/java/jsftest/repository/NodeService.java new file mode 100644 index 0000000000..b558580336 --- /dev/null +++ b/source/java/jsftest/repository/NodeService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package jsftest.repository; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Mock NodeService API + * + * @author gavinc + */ +public class NodeService +{ + private static Log logger = LogFactory.getLog(NodeService.class); + + public static NodeRef getNodeRef(String path) + { + return new NodeRef(path); + } + + public static String getType(NodeRef nodeRef) + { + String id = nodeRef.getId(); + String type = null; + + if (id.equalsIgnoreCase("/gav.doc") || + id.equalsIgnoreCase("/kev.txt")) + { + type = "base"; + } + else if (id.equalsIgnoreCase("/sop.txt")) + { + type = "SOP"; + } + + return type; + } + + public static Map getProperties(NodeRef nodeRef) + { + String id = nodeRef.getId(); + Map properties = null; + + if (id.equalsIgnoreCase("/gav.doc")) + { + properties = createProperties("Gav", "Gavs Object", + new String[] {"gav", "gadget", "gibbon"}, null); + } + else if (id.equalsIgnoreCase("/kev.txt")) + { + properties = createProperties("Kev", "Kevs Object", + new String[] {"kev", "monkey"}, null); + } + else if (id.equalsIgnoreCase("/sop.txt")) + { + properties = createProperties("SOP", "A manufacturing SOP", + new String[] {"sop", "manufacturing"}, "sop1"); + } + + return properties; + } + + private static Map createProperties(String name, String desc, + String[] keywords, String sop) + { + HashMap props = new HashMap(); + + Date date = new Date(); + props.put("name", name); + props.put("description", desc); + props.put("keywords", keywords); + props.put("created", date); + props.put("modified", date); + + if (sop != null) + { + props.put("sopId", sop); + props.put("effective", date); + props.put("approved", new Boolean(true)); + props.put("latestversion", "1.6"); + } + + return props; + } +} diff --git a/source/java/org/alfresco/web/app/AlfrescoNavigationHandler.java b/source/java/org/alfresco/web/app/AlfrescoNavigationHandler.java new file mode 100644 index 0000000000..26d1b6e81e --- /dev/null +++ b/source/java/org/alfresco/web/app/AlfrescoNavigationHandler.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app; + +import javax.faces.application.NavigationHandler; +import javax.faces.application.ViewHandler; +import javax.faces.component.UIViewRoot; +import javax.faces.context.FacesContext; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigService; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.config.NavigationConfigElement; +import org.alfresco.web.config.NavigationElementReader; +import org.alfresco.web.config.NavigationResult; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * @author gavinc + */ +public class AlfrescoNavigationHandler extends NavigationHandler +{ + private final static Log logger = LogFactory.getLog(AlfrescoNavigationHandler.class); + + // The original navigation handler + private NavigationHandler origHandler; + + /** + * Default constructor + * + * @param origHandler The original navigation handler + */ + public AlfrescoNavigationHandler(NavigationHandler origHandler) + { + super(); + this.origHandler = origHandler; + } + + /** + * @see javax.faces.application.NavigationHandler#handleNavigation(javax.faces.context.FacesContext, java.lang.String, java.lang.String) + */ + @Override + public void handleNavigation(FacesContext context, String fromAction, String outcome) + { + if (logger.isDebugEnabled()) + logger.debug("handleNavigation (fromAction=" + fromAction + ", outcome=" + outcome + ")"); + + boolean useOriginalNavHandler = true; + String finalOutcome = outcome; + String viewId = context.getViewRoot().getViewId(); + + if (logger.isDebugEnabled()) + logger.debug("Current view id: " + viewId); + + NavigationBean navBean = (NavigationBean)context.getExternalContext(). + getSessionMap().get("NavigationBean"); + + // only continue if we have some dispatching context + if (navBean != null && navBean.getDispatchContextNode() != null) + { + Node node = navBean.getDispatchContextNode(); + + if (logger.isDebugEnabled()) + logger.debug("Found node in dispatch context: " + node); + + // see if there is any navigation config for the node type + ConfigService configSvc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + context).getBean(Application.BEAN_CONFIG_SERVICE); + + Config nodeConfig = configSvc.getConfig(node); + NavigationConfigElement navigationCfg = (NavigationConfigElement)nodeConfig. + getConfigElement(NavigationElementReader.ELEMENT_NAVIGATION); + + if (navigationCfg != null) + { + // see if there is config for the current view state + NavigationResult navResult = navigationCfg.getOverride(viewId, outcome); + + if (navResult != null) + { + if (logger.isDebugEnabled()) + logger.debug("Found navigation config: " + navResult); + + if (navResult.isOutcome()) + { + finalOutcome = navResult.getResult(); + } + else + { + String newViewId = navResult.getResult(); + + if (newViewId.equals(viewId) == false) + { + useOriginalNavHandler = false; + + if (logger.isDebugEnabled()) + logger.debug("Dispatching to new view id: " + newViewId); + + ViewHandler viewHandler = context.getApplication().getViewHandler(); + UIViewRoot viewRoot = viewHandler.createView(context, newViewId); + viewRoot.setViewId(newViewId); + context.setViewRoot(viewRoot); + context.renderResponse(); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("New view id is the same as the current one so setting outcome to null"); + + finalOutcome = null; + } + } + } + else if (logger.isDebugEnabled()) + { + logger.debug("No override configuration found for current view or outcome"); + } + } + else if (logger.isDebugEnabled()) + { + logger.debug("No navigation configuration found for node"); + } + + // reset the dispatch context + navBean.resetDispatchContext(); + } + else if (logger.isDebugEnabled()) + { + logger.debug("No dispatch context found"); + } + + // do the appropriate navigation handling + if (useOriginalNavHandler) + { + if (logger.isDebugEnabled()) + logger.debug("Passing outcome '" + finalOutcome + "' to original navigation handler"); + + this.origHandler.handleNavigation(context, fromAction, finalOutcome); + } + } +} diff --git a/source/java/org/alfresco/web/app/Application.java b/source/java/org/alfresco/web/app/Application.java new file mode 100644 index 0000000000..ff05b39b55 --- /dev/null +++ b/source/java/org/alfresco/web/app/Application.java @@ -0,0 +1,662 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import javax.faces.context.FacesContext; +import javax.portlet.PortletContext; +import javax.portlet.PortletSession; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.alfresco.config.ConfigService; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.bean.ErrorBean; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.config.ServerConfigElement; +import org.apache.commons.logging.Log; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Utilities class + * + * @author gavinc + */ +public class Application +{ + private static final String LOCALE = "locale"; + + public static final String BEAN_CONFIG_SERVICE = "configService"; + public static final String BEAN_DATA_DICTIONARY = "dataDictionary"; + public static final String BEAN_IMPORTER_BOOTSTRAP = "importerBootstrap"; + + public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; + + private static boolean inPortalServer = true; + private static StoreRef repoStoreRef; + private static String rootPath; + private static String companyRootId; + private static String companyRootDescription; + private static String glossaryFolderName; + private static String spaceTemplatesFolderName; + private static String contentTemplatesFolderName; + + /** + * Private constructor to prevent instantiation of this class + */ + private Application() + { + } + + /** + * Sets whether this application is running inside a portal server + * + * @param inPortal true to indicate the application is running as a portlet + */ + public static void setInPortalServer(boolean inPortal) + { + inPortalServer = inPortal; + } + + /** + * Determines whether the server is running in a portal + * + * @return true if we are running inside a portal server + */ + public static boolean inPortalServer() + { + return inPortalServer; + } + + /** + * Handles errors thrown from servlets + * + * @param servletContext The servlet context + * @param request The HTTP request + * @param response The HTTP response + * @param error The exception + * @param logger The logger + */ + public static void handleServletError(ServletContext servletContext, HttpServletRequest request, + HttpServletResponse response, Throwable error, Log logger, String returnPage) + throws IOException, ServletException + { + // get the error bean from the session and set the error that occurred. + HttpSession session = request.getSession(); + ErrorBean errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME); + if (errorBean == null) + { + errorBean = new ErrorBean(); + session.setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean); + } + errorBean.setLastError(error); + errorBean.setReturnPage(returnPage); + + // try and find the configured error page + boolean errorShown = false; + String errorPage = getErrorPage(servletContext); + + if (errorPage != null) + { + if (logger.isDebugEnabled()) + logger.debug("An error has occurred, redirecting to error page: " + errorPage); + + if (response.isCommitted() == false) + { + errorShown = true; + response.sendRedirect(request.getContextPath() + errorPage); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Response is already committed, re-throwing error"); + } + } + else + { + if (logger.isDebugEnabled()) + logger.debug("No error page defined, re-throwing error"); + } + + // if we could not show the error page for whatever reason, re-throw the error + if (!errorShown) + { + if (error instanceof IOException) + { + throw (IOException)error; + } + else if (error instanceof ServletException) + { + throw (ServletException)error; + } + else + { + throw new ServletException(error); + } + } + } + + /** + * Retrieves the configured error page for the application + * + * @param servletContext The servlet context + * @return The configured error page or null if the configuration is missing + */ + public static String getErrorPage(ServletContext servletContext) + { + return getErrorPage(WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)); + } + + /** + * Retrieves the configured error page for the application + * + * @param portletContext The portlet context + * @return + */ + public static String getErrorPage(PortletContext portletContext) + { + return getErrorPage((WebApplicationContext)portletContext.getAttribute( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); + } + + /** + * Retrieves the configured login page for the application + * + * @param servletContext The servlet context + * @return The configured login page or null if the configuration is missing + */ + public static String getLoginPage(ServletContext servletContext) + { + return getLoginPage(WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)); + } + + /** + * Retrieves the configured login page for the application + * + * @param portletContext The portlet context + * @return + */ + public static String getLoginPage(PortletContext portletContext) + { + return getLoginPage((WebApplicationContext)portletContext.getAttribute( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); + } + + /** + * @return Returns the User object representing the currently logged in user + */ + public static User getCurrentUser(HttpSession session) + { + return (User)session.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); + } + + /** + * @return Returns the User object representing the currently logged in user + */ + public static User getCurrentUser(FacesContext context) + { + return (User)context.getExternalContext().getSessionMap().get(AuthenticationHelper.AUTHENTICATION_USER); + } + + /** + * @return Returns the repository store URL (retrieved from config service) + */ + public static StoreRef getRepositoryStoreRef(ServletContext context) + { + return getRepositoryStoreRef(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the repository store URL (retrieved from config service) + */ + public static StoreRef getRepositoryStoreRef(FacesContext context) + { + return getRepositoryStoreRef(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns id of the company root + */ + public static String getCompanyRootId() + { + return companyRootId; + } + + /** + * Sets the company root id. This is setup by the ContextListener. + * + * @param id The company root id + */ + public static void setCompanyRootId(String id) + { + companyRootId = id; + } + + /** + * @return Returns the root path for the application (retrieved from config service) + */ + public static String getRootPath(ServletContext context) + { + return getRootPath(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the root path for the application (retrieved from config service) + */ + public static String getRootPath(FacesContext context) + { + return getRootPath(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the glossary folder name (retrieved from config service) + */ + public static String getGlossaryFolderName(ServletContext context) + { + return getGlossaryFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the glossary folder name (retrieved from config service) + */ + public static String getGlossaryFolderName(FacesContext context) + { + return getGlossaryFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Space templates folder name (retrieved from config service) + */ + public static String getSpaceTemplatesFolderName(ServletContext context) + { + return getSpaceTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Space templates folder name (retrieved from config service) + */ + public static String getSpaceTemplatesFolderName(FacesContext context) + { + return getSpaceTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Content templates folder name (retrieved from config service) + */ + public static String getContentTemplatesFolderName(ServletContext context) + { + return getContentTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Content templates folder name (retrieved from config service) + */ + public static String getContentTemplatesFolderName(FacesContext context) + { + return getContentTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * Set the language locale for the current user context + * + * @param context FacesContext for current user + * @param code The ISO locale code to set + */ + public static void setLanguage(FacesContext context, String code) + { + Locale locale = parseLocale(code); + + // set locale for JSF framework usage + context.getViewRoot().setLocale(locale); + + // set locale for our framework usage + context.getExternalContext().getSessionMap().put(LOCALE, locale); + + // clear the current message bundle - so it's reloaded with new locale + context.getExternalContext().getSessionMap().remove(MESSAGE_BUNDLE); + } + + /** + * Set the language locale for the current user session + * + * @param session HttpSession for current user + * @param code The ISO locale code to set + */ + public static void setLanguage(HttpSession session, String code) + { + Locale locale = parseLocale(code); + + session.putValue(LOCALE, locale); + session.removeAttribute(MESSAGE_BUNDLE); + } + + /** + * @param code Locale code (java format with underscores) to parse + * @return Locale object or default if unable to parse + */ + private static Locale parseLocale(String code) + { + Locale locale = Locale.getDefault(); + + StringTokenizer t = new StringTokenizer(code, "_"); + int tokens = t.countTokens(); + if (tokens == 1) + { + locale = new Locale(code); + } + else if (tokens == 2) + { + locale = new Locale(t.nextToken(), t.nextToken()); + } + else if (tokens == 3) + { + locale = new Locale(t.nextToken(), t.nextToken(), t.nextToken()); + } + + return locale; + } + + /** + * Return the language Locale for the current user context + * + * @param context FacesContext for the current user + * + * @return Current language Locale set or null if none set + */ + public static Locale getLanguage(FacesContext context) + { + return (Locale)context.getExternalContext().getSessionMap().get(LOCALE); + } + + /** + * Return the language Locale for the current user Session. + * + * @param session HttpSession for the current user + * + * @return Current language Locale set or null if none set + */ + public static Locale getLanguage(HttpSession session) + { + return (Locale)session.getAttribute(LOCALE); + } + + /** + * Return the language Locale for the current user PortletSession. + * + * @param session PortletSession for the current user + * + * @return Current language Locale set or null if none set + */ + public static Locale getLanguage(PortletSession session) + { + return (Locale)session.getAttribute(LOCALE); + } + + /** + * Get the specified I18N message string from the default message bundle for this user + * + * @param context FacesContext + * @param msg Message ID + * + * @return String from message bundle or $$msg$$ if not found + */ + public static String getMessage(FacesContext context, String msg) + { + return getBundle(context).getString(msg); + } + + /** + * Get the specified I18N message string from the default message bundle for this user + * + * @param session HttpSession + * @param msg Message ID + * + * @return String from message bundle or $$msg$$ if not found + */ + public static String getMessage(HttpSession session, String msg) + { + return getBundle(session).getString(msg); + } + + /** + * Get the specified the default message bundle for this user + * + * @param session HttpSession + * + * @return ResourceBundle for this user + */ + public static ResourceBundle getBundle(HttpSession session) + { + ResourceBundle bundle = (ResourceBundle)session.getAttribute(MESSAGE_BUNDLE); + if (bundle == null) + { + // get Locale from language selected by each user on login + Locale locale = (Locale)session.getAttribute(LOCALE); + if (locale == null) + { + locale = Locale.getDefault(); + } + bundle = ResourceBundle.getBundle(MESSAGE_BUNDLE, locale); + if (bundle == null) + { + throw new AlfrescoRuntimeException("Unable to load Alfresco messages bundle: " + MESSAGE_BUNDLE); + } + + // apply our wrapper to catch MissingResourceException + bundle = new ResourceBundleWrapper(bundle); + + session.setAttribute(MESSAGE_BUNDLE, bundle); + } + + return bundle; + } + + /** + * Get the specified the default message bundle for this user + * + * @param context FacesContext + * + * @return ResourceBundle for this user + */ + public static ResourceBundle getBundle(FacesContext context) + { + // get the resource bundle for the current locale + // we store the bundle in the users session + // this makes it easy to add a locale per user support later + Map session = context.getExternalContext().getSessionMap(); + ResourceBundle bundle = (ResourceBundle)session.get(MESSAGE_BUNDLE); + if (bundle == null) + { + // get Locale from language selected by each user on login + Locale locale = (Locale)session.get(LOCALE); + if (locale == null) + { + locale = Locale.getDefault(); + } + bundle = ResourceBundle.getBundle(MESSAGE_BUNDLE, locale); + if (bundle == null) + { + throw new AlfrescoRuntimeException("Unable to load Alfresco messages bundle: " + MESSAGE_BUNDLE); + } + + // apply our wrapper to catch MissingResourceException + bundle = new ResourceBundleWrapper(bundle); + + session.put(MESSAGE_BUNDLE, bundle); + } + + return bundle; + } + + /** + * Helper to get the ConfigService instance + * + * @param context FacesContext + * + * @return ConfigService + */ + public static ConfigService getConfigService(FacesContext context) + { + return (ConfigService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean( + Application.BEAN_CONFIG_SERVICE); + } + + /** + * Returns the repository store URL (retrieved from config service) + * + * @param context The spring context + * @return The repository store URL to use + */ + private static StoreRef getRepositoryStoreRef(WebApplicationContext context) + { + if (repoStoreRef == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + repoStoreRef = bootstrap.getStoreRef(); + } + + return repoStoreRef; + } + + /** + * Returns the root path for the application (retrieved from config service) + * + * @param context The spring context + * @return The application root path + */ + private static String getRootPath(WebApplicationContext context) + { + if (rootPath == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + rootPath = configuration.getProperty("spaces.company_home.childname"); + } + + return rootPath; + } + + /** + * Returns the glossary folder name (retrieved from config service) + * + * @param context The spring context + * @return The glossary folder name + */ + private static String getGlossaryFolderName(WebApplicationContext context) + { + if (glossaryFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + glossaryFolderName = configuration.getProperty("spaces.dictionary.childname"); + } + + return glossaryFolderName; + } + + /** + * Returns the Space Templates folder name (retrieved from config service) + * + * @param context The spring context + * @return The templates folder name + */ + private static String getSpaceTemplatesFolderName(WebApplicationContext context) + { + if (spaceTemplatesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + spaceTemplatesFolderName = configuration.getProperty("spaces.templates.childname"); + } + + return spaceTemplatesFolderName; + } + + /** + * Returns the Content Templates folder name (retrieved from config service) + * + * @param context The spring context + * @return The templates folder name + */ + private static String getContentTemplatesFolderName(WebApplicationContext context) + { + if (contentTemplatesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + contentTemplatesFolderName = configuration.getProperty("spaces.templates.content.childname"); + } + + return contentTemplatesFolderName; + } + + /** + * Retrieves the configured error page for the application + * + * @param context The Spring contexr + * @return The configured error page or null if the configuration is missing + */ + private static String getErrorPage(WebApplicationContext context) + { + String errorPage = null; + + ConfigService svc = (ConfigService)context.getBean(BEAN_CONFIG_SERVICE); + ServerConfigElement serverConfig = (ServerConfigElement)svc.getGlobalConfig().getConfigElement("server"); + + if (serverConfig != null) + { + errorPage = serverConfig.getErrorPage(); + } + + return errorPage; + } + + /** + * Retrieves the configured login page for the application + * + * @param context The Spring contexr + * @return The configured login page or null if the configuration is missing + */ + private static String getLoginPage(WebApplicationContext context) + { + String loginPage = null; + + ConfigService svc = (ConfigService)context.getBean(BEAN_CONFIG_SERVICE); + ServerConfigElement serverConfig = (ServerConfigElement)svc.getGlobalConfig().getConfigElement("server"); + + if (serverConfig != null) + { + loginPage = serverConfig.getLoginPage(); + } + + return loginPage; + } +} diff --git a/source/java/org/alfresco/web/app/ContextListener.java b/source/java/org/alfresco/web/app/ContextListener.java new file mode 100644 index 0000000000..7d73f05010 --- /dev/null +++ b/source/java/org/alfresco/web/app/ContextListener.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigService; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.app.portlet.AlfrescoFacesPortlet; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * ServletContextListener implementation that initialises the application. + * + * NOTE: This class must appear after the Spring context loader listener + * + * @author gavinc + */ +public class ContextListener implements ServletContextListener, HttpSessionListener +{ + private static Log logger = LogFactory.getLog(ContextListener.class); + + private static final String ADMIN = "admin"; + private static final String ADMIN_FIRSTNAME = "Repository"; + private static final String ADMIN_LASTNAME = "Administrator"; + + private ServletContext servletContext; + + /** + * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) + */ + public void contextInitialized(ServletContextEvent event) + { + // make sure that the spaces store in the repository exists + this.servletContext = event.getServletContext(); + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + ServiceRegistry registry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + TransactionService transactionService = registry.getTransactionService(); + NodeService nodeService = registry.getNodeService(); + SearchService searchService = registry.getSearchService(); + NamespaceService namespaceService = registry.getNamespaceService(); + AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx + .getBean("authenticationComponent"); + + // repo bootstrap code for our client + UserTransaction tx = null; + NodeRef companySpaceNodeRef = null; + try + { + tx = transactionService.getUserTransaction(); + tx.begin(); + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + + // get and setup the initial store ref from config + StoreRef storeRef = Repository.getStoreRef(servletContext); + + // check the repository exists, create if it doesn't + if (nodeService.exists(storeRef) == false) + { + throw new AlfrescoRuntimeException("Store not created prior to application startup: " + storeRef); + } + + // get hold of the root node + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + + // see if the company home space is present + String rootPath = Application.getRootPath(servletContext); + if (rootPath == null) + { + throw new AlfrescoRuntimeException("Root path has not been configured"); + } + + List nodes = searchService.selectNodes(rootNodeRef, rootPath, null, namespaceService, false); + if (nodes.size() == 0) + { + throw new AlfrescoRuntimeException("Root path not created prior to application startup: " + rootPath); + } + + // Extract company space id and store it in the Application object + companySpaceNodeRef = nodes.get(0); + Application.setCompanyRootId(companySpaceNodeRef.getId()); + + // check the admin user exists, create if it doesn't + MutableAuthenticationDao dao = (MutableAuthenticationDao) ctx.getBean("alfDaoImpl"); + + // this is required to setup the ACEGI context before we can check + // for the user + if (!dao.userExists(ADMIN)) + { + ConfigService configService = (ConfigService) ctx.getBean(Application.BEAN_CONFIG_SERVICE); + // default to password of "admin" if we don't find config for it + String password = ADMIN; + ConfigElement adminConfig = configService.getGlobalConfig().getConfigElement("admin"); + if (adminConfig != null) + { + List children = adminConfig.getChildren(); + if (children.size() != 0) + { + // try to find the config element for the initial + // password + ConfigElement passElement = children.get(0); + if (passElement.getName().equals("initial-password")) + { + password = passElement.getValue(); + } + } + } + + // create the Authentication instance for the "admin" user + AuthenticationService authService = (AuthenticationService) ctx.getBean("authenticationService"); + authService.createAuthentication(ADMIN, password.toCharArray()); + } + + PersonService personService = (PersonService) ctx.getBean("personService"); + if (!personService.personExists(ADMIN)) + { + // create the node to represent the Person instance for the + // admin user + Map props = new HashMap(7, 1.0f); + props.put(ContentModel.PROP_USERNAME, ADMIN); + props.put(ContentModel.PROP_FIRSTNAME, ADMIN_FIRSTNAME); + props.put(ContentModel.PROP_LASTNAME, ADMIN_LASTNAME); + props.put(ContentModel.PROP_HOMEFOLDER, companySpaceNodeRef); + props.put(ContentModel.PROP_EMAIL, ""); + props.put(ContentModel.PROP_ORGID, ""); + + personService.createPerson(props); + } + // set the store's GUEST access if we are allowed to modify permissions + if (!transactionService.isReadOnly()) + { + PermissionService permissionService = (PermissionService) ctx.getBean("permissionService"); + permissionService.setPermission(rootNodeRef, permissionService.getAllAuthorities(), PermissionService.GUEST, true); + } + + // commit the transaction + tx.commit(); + } + catch (Throwable e) + { + // rollback the transaction + try + { + if (tx != null) + { + tx.rollback(); + } + } + catch (Exception ex) {} + + logger.error("Failed to initialise ", e); + throw new AlfrescoRuntimeException("Failed to initialise ", e); + } + finally + { + try + { + authenticationComponent.clearCurrentSecurityContext(); + } + catch (Exception ex) {} + } + } + + /** + * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) + */ + public void contextDestroyed(ServletContextEvent event) + { + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + Scheduler quartz = (Scheduler) ctx.getBean("schedulerFactory"); + try + { + quartz.shutdown(true); + } + catch (SchedulerException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Session created listener + */ + public void sessionCreated(HttpSessionEvent event) + { + if (logger.isDebugEnabled()) logger.debug("HTTP session created: " + event.getSession().getId()); + } + + /** + * Session destroyed listener + */ + public void sessionDestroyed(HttpSessionEvent event) + { + if (logger.isDebugEnabled()) logger.debug("HTTP session destroyed: " + event.getSession().getId()); + + User user; + if (Application.inPortalServer() == false) + { + user = (User)event.getSession().getAttribute(AuthenticationHelper.AUTHENTICATION_USER); + } + else + { + user = (User)event.getSession().getAttribute(AlfrescoFacesPortlet.MANAGED_BEAN_PREFIX + AuthenticationHelper.AUTHENTICATION_USER); + } + + if (user != null) + { + // invalidate ticket + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + AuthenticationService authService = (AuthenticationService) ctx.getBean("authenticationService"); + authService.invalidateTicket(user.getTicket()); + event.getSession().removeAttribute(AuthenticationHelper.AUTHENTICATION_USER); + } + } +} diff --git a/source/java/org/alfresco/web/app/DebugPhaseListener.java b/source/java/org/alfresco/web/app/DebugPhaseListener.java new file mode 100644 index 0000000000..bf76fc5600 --- /dev/null +++ b/source/java/org/alfresco/web/app/DebugPhaseListener.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app; + +import javax.faces.event.PhaseEvent; +import javax.faces.event.PhaseId; +import javax.faces.event.PhaseListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Debug phase listener that simply logs when each phase is entered and exited. + * + * @author gavinc + */ +public class DebugPhaseListener implements PhaseListener +{ + private static final Log logger = LogFactory.getLog(DebugPhaseListener.class); + + /** + * @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent) + */ + public void afterPhase(PhaseEvent event) + { + if (logger.isDebugEnabled()) + logger.debug("********** Exiting phase: " + event.getPhaseId().toString()); + } + + /** + * @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent) + */ + public void beforePhase(PhaseEvent event) + { + if (logger.isDebugEnabled()) + logger.debug("********** Entering phase: " + event.getPhaseId().toString()); + } + + /** + * @see javax.faces.event.PhaseListener#getPhaseId() + */ + public PhaseId getPhaseId() + { + return PhaseId.ANY_PHASE; + } + +} diff --git a/source/java/org/alfresco/web/app/ResourceBundleWrapper.java b/source/java/org/alfresco/web/app/ResourceBundleWrapper.java new file mode 100644 index 0000000000..7bef0b82c9 --- /dev/null +++ b/source/java/org/alfresco/web/app/ResourceBundleWrapper.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app; + +import java.util.Enumeration; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; + +/** + * Wrapper around Alfresco Resource Bundle objects. Used to catch and handle missing + * resource exception to help identify missing I18N strings in client apps. + * + * @author Kevin Roast + */ +public final class ResourceBundleWrapper extends ResourceBundle +{ + private static Logger logger = Logger.getLogger(ResourceBundleWrapper.class); + private ResourceBundle delegate; + + /** + * Constructor + * + * @param bundle The ResourceBundle to route calls too + */ + public ResourceBundleWrapper(ResourceBundle bundle) + { + this.delegate = bundle; + } + + /** + * @see java.util.ResourceBundle#getKeys() + */ + public Enumeration getKeys() + { + return this.delegate.getKeys(); + } + + /** + * @see java.util.ResourceBundle#handleGetObject(java.lang.String) + */ + protected Object handleGetObject(String key) + { + try + { + return this.delegate.getObject(key); + } + catch (MissingResourceException err) + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Failed to find I18N message string key: " + key); + + return "$$" + key + "$$"; + } + } +} diff --git a/source/java/org/alfresco/web/app/context/IContextListener.java b/source/java/org/alfresco/web/app/context/IContextListener.java new file mode 100644 index 0000000000..effb091079 --- /dev/null +++ b/source/java/org/alfresco/web/app/context/IContextListener.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.context; + +/** + * Interface used to allow Beans to register themselves as interested in UI context events. + *

+ * Beans supporting this interface should be register against the UIContextService. Then Beans + * which wish to indicate that the UI should refresh itself i.e. dump all cached data and settings, + * call the UIContextService.notifyBeans() to inform all registered instances of the change. + * + * @author Kevin Roast + */ +public interface IContextListener +{ + /** + * Method called by UIContextService.notifyBeans() to inform all registered beans that + * all UI Beans should refresh dump all cached data and settings. + */ + public void contextUpdated(); +} diff --git a/source/java/org/alfresco/web/app/context/UIContextService.java b/source/java/org/alfresco/web/app/context/UIContextService.java new file mode 100644 index 0000000000..4335462df6 --- /dev/null +++ b/source/java/org/alfresco/web/app/context/UIContextService.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.context; + +import java.util.HashMap; +import java.util.Map; + +import javax.faces.context.FacesContext; + +/** + * Beans supporting the IContextListener interface are registered against this class. Then Beans + * which wish to indicate that the UI should refresh itself i.e. dump all cached data and settings, + * call the UIContextService.notifyBeans() to inform all registered instances of the change. + * + * @author Kevin Roast + */ +public final class UIContextService +{ + /** + * Private constructor + */ + private UIContextService() + { + } + + /** + * Returns a Session local instance of the UIContextService + * + * @return UIContextService for this Thread + */ + public static UIContextService getInstance(FacesContext fc) + { + Map session = fc.getExternalContext().getSessionMap(); + UIContextService service = (UIContextService)session.get(CONTEXT_KEY); + if (service == null) + { + service = new UIContextService(); + session.put(CONTEXT_KEY, service); + } + + return service; + } + + /** + * Register a bean to be informed of context events + * + * @param bean Conforming to the IContextListener interface + */ + public void registerBean(IContextListener bean) + { + if (bean == null) + { + throw new IllegalArgumentException("Bean reference specified cannot be null!"); + } + + this.registeredBeans.put(bean.getClass(), bean); + } + + /** + * Remove a bean reference from those notified of changes + * + * @param bean Conforming to the IContextListener interface + */ + public void unregisterBean(IContextListener bean) + { + if (bean == null) + { + throw new IllegalArgumentException("Bean reference specified cannot be null!"); + } + + this.registeredBeans.remove(bean); + } + + /** + * Call to notify all register beans that the UI context has changed and they should + * refresh themselves as appropriate. + */ + public void notifyBeans() + { + for (IContextListener listener: this.registeredBeans.values()) + { + listener.contextUpdated(); + } + } + + + /** key for the UI context service in the session */ + private final static String CONTEXT_KEY = "__uiContextService"; + + /** Map of bean registered against the context service */ + private Map registeredBeans = new HashMap(7, 1.0f); +} diff --git a/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java b/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java new file mode 100644 index 0000000000..ee8ed65ea3 --- /dev/null +++ b/source/java/org/alfresco/web/app/portlet/AlfrescoFacesPortlet.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.portlet; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +import javax.faces.application.ViewHandler; +import javax.faces.component.UIViewRoot; +import javax.faces.context.FacesContext; +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.PortletException; +import javax.portlet.PortletRequestDispatcher; +import javax.portlet.PortletSession; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.util.TempFileProvider; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.bean.ErrorBean; +import org.alfresco.web.bean.FileUploadBean; +import org.alfresco.web.bean.repository.User; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.portlet.PortletFileUpload; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.myfaces.portlet.MyFacesGenericPortlet; +import org.apache.myfaces.portlet.PortletUtil; +import org.springframework.web.context.WebApplicationContext; + +/** + * Class to extend the MyFacesGenericPortlet to provide behaviour specific to Alfresco web client. + * Handles upload of multi-part forms through a JSR-168 Portlet, generic error handling and session + * login authentication. + * + * @author Gavin Cornwell, Kevin Roast + */ +public class AlfrescoFacesPortlet extends MyFacesGenericPortlet +{ + public static final String INSTANCE_NAME = "AlfrescoClientInstance"; + public static final String WINDOW_NAME = "AlfrescoClientWindow"; + public static final String MANAGED_BEAN_PREFIX = "javax.portlet.p." + INSTANCE_NAME + + "." + WINDOW_NAME + "?"; + + private static final String ERROR_PAGE_PARAM = "error-page"; + private static final String ERROR_OCCURRED = "error-occurred"; + + private static Log logger = LogFactory.getLog(AlfrescoFacesPortlet.class); + + private String loginPage = null; + private String errorPage = null; + + + /** + * Called by the portlet container to allow the portlet to process an action request. + */ + public void processAction(ActionRequest request, ActionResponse response) + throws PortletException, IOException + { + boolean isMultipart = PortletFileUpload.isMultipartContent(request); + + try + { + // NOTE: Due to filters not being called within portlets we can not make use + // of the MyFaces file upload support, therefore we are using a pure + // portlet request/action to handle file uploads until there is a + // solution. + + if (isMultipart) + { + if (logger.isDebugEnabled()) + logger.debug("Handling multipart request..."); + + PortletSession session = request.getPortletSession(); + + // get the file from the request and put it in the session + DiskFileItemFactory factory = new DiskFileItemFactory(); + PortletFileUpload upload = new PortletFileUpload(factory); + List fileItems = upload.parseRequest(request); + Iterator iter = fileItems.iterator(); + FileUploadBean bean = new FileUploadBean(); + while(iter.hasNext()) + { + FileItem item = iter.next(); + String filename = item.getName(); + if(item.isFormField() == false) + { + if (logger.isDebugEnabled()) + logger.debug("Processing uploaded file: " + filename); + + // workaround a bug in IE where the full path is returned + // IE is only available for Windows so only check for the Windows path separator + int idx = filename.lastIndexOf('\\'); + + if (idx == -1) + { + // if there is no windows path separator check for *nix + idx = filename.lastIndexOf('/'); + } + + if (idx != -1) + { + filename = filename.substring(idx + File.separator.length()); + } + + File tempFile = TempFileProvider.createTempFile("alfresco", ".upload"); + item.write(tempFile); + bean.setFile(tempFile); + bean.setFileName(filename); + bean.setFilePath(tempFile.getAbsolutePath()); + session.setAttribute(FileUploadBean.FILE_UPLOAD_BEAN_NAME, bean, + PortletSession.PORTLET_SCOPE); + } + } + + // it doesn't matter what the value is we just need the VIEW_ID parameter + // to tell the faces portlet bridge to treat the request as a JSF request, + // this will send us back to the same page we came from, which is fine for + // most scenarios. + response.setRenderParameter(VIEW_ID, "a-jsf-page"); + } + else + { + // do the normal JSF processing + super.processAction(request, response); + } + } + catch (Throwable e) + { + if (getErrorPage() != null) + { + handleError(request, response, e); + } + else + { + logger.warn("No error page configured, re-throwing exception"); + + if (e instanceof PortletException) + { + throw (PortletException)e; + } + else if (e instanceof IOException) + { + throw (IOException)e; + } + else + { + throw new PortletException(e); + } + } + } + } + + /** + * @see org.apache.myfaces.portlet.MyFacesGenericPortlet#facesRender(javax.portlet.RenderRequest, javax.portlet.RenderResponse) + */ + protected void facesRender(RenderRequest request, RenderResponse response) + throws PortletException, IOException + { + Application.setInPortalServer(true); + + if (request.getParameter(ERROR_OCCURRED) != null) + { + String errorPage = Application.getErrorPage(getPortletContext()); + + if (logger.isDebugEnabled()) + logger.debug("An error has occurred, redirecting to error page: " + errorPage); + + response.setContentType("text/html"); + PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher(errorPage); + dispatcher.include(request, response); + } + else + { + // if we have no User object in the session then a timeout must have occured + // use the viewId to check that we are not already on the login page + String viewId = request.getParameter(VIEW_ID); + User user = (User)request.getPortletSession().getAttribute(AuthenticationHelper.AUTHENTICATION_USER); + if (user == null && (viewId == null || viewId.equals(getLoginPage()) == false)) + { + if (logger.isDebugEnabled()) + logger.debug("No valid login, requesting login page. ViewId: " + viewId); + + // login page redirect + response.setContentType("text/html"); + request.getPortletSession().setAttribute(PortletUtil.PORTLET_REQUEST_FLAG, "true"); + nonFacesRequest(request, response); + } + else + { + try + { + if (user != null) + { + // setup the authentication context + WebApplicationContext ctx = (WebApplicationContext)getPortletContext().getAttribute( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); + AuthenticationService auth = (AuthenticationService)ctx.getBean("authenticationService"); + auth.validate(user.getTicket()); + } + + // Set the current locale + I18NUtil.setLocale(Application.getLanguage(request.getPortletSession())); + + // do the normal JSF processing + super.facesRender(request, response); + } + catch (Throwable e) + { + if (getErrorPage() != null) + { + handleError(request, response, e); + } + else + { + logger.warn("No error page configured, re-throwing exception"); + + if (e instanceof PortletException) + { + throw (PortletException)e; + } + else if (e instanceof IOException) + { + throw (IOException)e; + } + else + { + throw new PortletException(e); + } + } + } + } + } + } + + /** + * Handles errors that occur during a process action request + */ + private void handleError(ActionRequest request, ActionResponse response, Throwable error) + throws PortletException, IOException + { + // get the error bean from the session and set the error that occurred. + PortletSession session = request.getPortletSession(); + ErrorBean errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME, + PortletSession.PORTLET_SCOPE); + if (errorBean == null) + { + errorBean = new ErrorBean(); + session.setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean, PortletSession.PORTLET_SCOPE); + } + errorBean.setLastError(error); + + response.setRenderParameter(ERROR_OCCURRED, "true"); + } + + /** + * Handles errors that occur during a render request + */ + private void handleError(RenderRequest request, RenderResponse response, Throwable error) + throws PortletException, IOException + { + // get the error bean from the session and set the error that occurred. + PortletSession session = request.getPortletSession(); + ErrorBean errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME, + PortletSession.PORTLET_SCOPE); + if (errorBean == null) + { + errorBean = new ErrorBean(); + session.setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean, PortletSession.PORTLET_SCOPE); + } + errorBean.setLastError(error); + + // if the faces context is available set the current view to the browse page + // so that the error page goes back to the application (rather than going back + // to the same page which just throws the error again meaning we can never leave + // the error page) + FacesContext context = FacesContext.getCurrentInstance(); + if (context != null) + { + ViewHandler viewHandler = context.getApplication().getViewHandler(); + // TODO: configure the portlet error return page + UIViewRoot view = viewHandler.createView(context, "/jsp/browse/browse.jsp"); + context.setViewRoot(view); + } + + // get the error page and include that instead + String errorPage = getErrorPage(); + + if (logger.isDebugEnabled()) + logger.debug("An error has occurred, redirecting to error page: " + errorPage); + + response.setContentType("text/html"); + PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher(errorPage); + dispatcher.include(request, response); + } + + /** + * @return Retrieves the configured login page + */ + private String getLoginPage() + { + if (this.loginPage == null) + { + this.loginPage = Application.getLoginPage(getPortletContext()); + } + + return this.loginPage; + } + + /** + * @return Retrieves the configured error page + */ + private String getErrorPage() + { + if (this.errorPage == null) + { + this.errorPage = Application.getErrorPage(getPortletContext()); + } + + return this.errorPage; + } +} diff --git a/source/java/org/alfresco/web/app/servlet/AlfrescoFacesServlet.java b/source/java/org/alfresco/web/app/servlet/AlfrescoFacesServlet.java new file mode 100644 index 0000000000..a0cfba6b3c --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/AlfrescoFacesServlet.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; + +import javax.faces.webapp.FacesServlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.web.app.Application; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Wrapper around standard faces servlet to provide error handling + * + * @author gavinc + */ +public class AlfrescoFacesServlet extends FacesServlet +{ + private static Log logger = LogFactory.getLog(AlfrescoFacesServlet.class); + + /** + * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void service(ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + try + { + super.service(request, response); + } + catch (Throwable error) + { + String returnPage = ((HttpServletRequest)request).getRequestURI(); + + Application.handleServletError(getServletConfig().getServletContext(), (HttpServletRequest)request, + (HttpServletResponse)response, error, logger, returnPage); + } + } +} diff --git a/source/java/org/alfresco/web/app/servlet/AuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/AuthenticationFilter.java new file mode 100644 index 0000000000..280d681cce --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/AuthenticationFilter.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.LoginBean; + +/** + * @author Kevin Roast + * + * Servlet filter responsible for redirecting to the login page for the Web Client if the user + * does not have a valid ticket. + *

+ * The current ticker is validated for each page request and the login page is shown if the + * ticker has expired. + *

+ * Note that this filter is only active when the system is running in a servlet container - + * the AlfrescoFacesPortlet will be used for a JSR-168 Portal environment. + */ +public class AuthenticationFilter implements Filter +{ + /** + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + public void init(FilterConfig config) throws ServletException + { + this.context = config.getServletContext(); + } + + /** + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest httpReq = (HttpServletRequest)req; + + // allow the login page to proceed + if (httpReq.getRequestURI().endsWith(getLoginPage()) == false) + { + if (AuthenticationHelper.authenticate(this.context, httpReq, (HttpServletResponse)res)) + { + // continue filter chaining + chain.doFilter(req, res); + } + else + { + // failed to authenticate - save redirect URL for after login process + httpReq.getSession().setAttribute(LoginBean.LOGIN_REDIRECT_KEY, httpReq.getRequestURI()); + } + } + else + { + // continue filter chaining + chain.doFilter(req, res); + } + } + + /** + * @see javax.servlet.Filter#destroy() + */ + public void destroy() + { + // nothing to do + } + + /** + * @return The login page url + */ + private String getLoginPage() + { + if (this.loginPage == null) + { + this.loginPage = Application.getLoginPage(this.context); + } + + return this.loginPage; + } + + + private String loginPage = null; + + private ServletContext context; +} diff --git a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java new file mode 100644 index 0000000000..85b19289d1 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.portlet.AlfrescoFacesPortlet; +import org.alfresco.web.bean.repository.User; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * @author Kevin Roast + */ +public final class AuthenticationHelper +{ + public final static String AUTHENTICATION_USER = "_alfAuthTicket"; + + public static boolean authenticate(ServletContext context, HttpServletRequest httpRequest, HttpServletResponse httpResponse) + throws IOException + { + // examine the appropriate session for our User object + User user; + if (Application.inPortalServer() == false) + { + user = (User)httpRequest.getSession().getAttribute(AUTHENTICATION_USER); + } + else + { + user = (User)httpRequest.getSession().getAttribute(AlfrescoFacesPortlet.MANAGED_BEAN_PREFIX + AUTHENTICATION_USER); + } + + if (user == null) + { + // no user/ticket - redirect to login page + httpResponse.sendRedirect(httpRequest.getContextPath() + "/faces" + Application.getLoginPage(context)); + + return false; + } + else + { + // setup the authentication context + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context); + AuthenticationService auth = (AuthenticationService)ctx.getBean("authenticationService"); + auth.validate(user.getTicket()); + + // Set the current locale + I18NUtil.setLocale(Application.getLanguage(httpRequest.getSession())); + + return true; + } + } + + public static boolean authenticate(ServletContext context, HttpServletRequest httpRequest, HttpServletResponse httpResponse, String ticket) + throws IOException + { + // setup the authentication context + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context); + AuthenticationService auth = (AuthenticationService)ctx.getBean("authenticationService"); + try + { + auth.validate(ticket); + } + catch (AuthenticationException authErr) + { + return false; + } + + // Set the current locale + I18NUtil.setLocale(Application.getLanguage(httpRequest.getSession())); + + return true; + } +} diff --git a/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java b/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java new file mode 100644 index 0000000000..a9aeceb7f0 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.SocketException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.text.MessageFormat; +import java.util.StringTokenizer; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.LoginBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Servlet responsible for streaming node content from the repo directly to the response stream. + * The appropriate mimetype is calculated based on filename extension. + *

+ * The URL to the servlet should be generated thus: + *

/alfresco/download/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf
+ * or + *
/alfresco/download/direct/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf
+ *

+ * The store protocol, followed by the store ID, followed by the content Node Id + * the last element is used for mimetype calculation and browser default filename. + *

+ * The 'attach' or 'direct' element is used to indicate whether to display the stream directly + * in the browser or download it as a file attachment. + *

+ * By default, the download assumes that the content is on the + * {@link org.alfresco.model.ContentModel#PROP_CONTENT content property}.
+ * To retrieve the content of a specific model property, use a 'property' arg, providing the workspace, + * node ID AND the qualified name of the property. + *

+ * The URL may be followed by a valid ticket argument for authentication: ?ticket=1234567890 + * + * @author Kevin Roast + */ +public class DownloadContentServlet extends HttpServlet +{ + private static final long serialVersionUID = -4558907921887235966L; + + private static Log logger = LogFactory.getLog(DownloadContentServlet.class); + + private static final String DOWNLOAD_URL = "/download/attach/{0}/{1}/{2}/{3}"; + private static final String BROWSER_URL = "/download/direct/{0}/{1}/{2}/{3}"; + + private static final String MIMETYPE_OCTET_STREAM = "application/octet-stream"; + + private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing"; + + private static final String ARG_PROPERTY = "property"; + private static final String ARG_ATTACH = "attach"; + private static final String ARG_TICKET = "ticket"; + + /** + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + ServletOutputStream out = res.getOutputStream(); + + try + { + // The URL contains multiple parts + // /alfresco/download/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf + // the protocol, followed by the store, followed by the Id + // the last part is only used for mimetype and browser use + // may be followed by valid ticket for pre-authenticated usage: ?ticket=1234567890 + String uri = req.getRequestURI(); + + if (logger.isDebugEnabled()) + logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : "")); + + // see if a ticket has been supplied + String ticket = req.getParameter(ARG_TICKET); + if (ticket == null || ticket.length() == 0) + { + if (AuthenticationHelper.authenticate(getServletContext(), req, res) == false) + { + // authentication failed - no point returning the content as we haven't logged in yet + // so end servlet execution and save the URL so the login page knows what to do later + req.getSession().setAttribute(LoginBean.LOGIN_REDIRECT_KEY, uri); + return; + } + } + else + { + AuthenticationHelper.authenticate(getServletContext(), req, res, ticket); + } + + // TODO: add compression here? + // see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example + // only really needed if we don't use the built in compression of the servlet container + StringTokenizer t = new StringTokenizer(uri, "/"); + if (t.countTokens() < 7) + { + throw new IllegalArgumentException("Download URL did not contain all required args: " + uri); + } + + t.nextToken(); // skip web app name + t.nextToken(); // skip servlet name + + String attachToken = t.nextToken(); + boolean attachment = attachToken.equals(ARG_ATTACH); + + StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken()); + String id = t.nextToken(); + String filename = t.nextToken(); + + // get property qualified name + QName propertyQName = null; + String property = req.getParameter(ARG_PROPERTY); + if (property == null || property.length() == 0) + { + propertyQName = ContentModel.PROP_CONTENT; + } + else + { + propertyQName = QName.createQName(property); + } + + NodeRef nodeRef = new NodeRef(storeRef, id); + if (logger.isDebugEnabled()) + { + logger.debug("Found NodeRef: " + nodeRef.toString()); + logger.debug("Will use filename: " + filename); + logger.debug("For property: " + propertyQName); + logger.debug("With attachment mode: " + attachment); + } + + if (attachment == true) + { + // set header based on filename - will force a Save As from the browse if it doesn't recognise it + // this is better than the default response of the browse trying to display the contents! + // TODO: make this configurable - and check it does not prevent streaming of large files + res.setHeader("Content-Disposition", "attachment;filename=\"" + URLDecoder.decode(filename, "UTF-8") + '"'); + } + + // get the services we need to retrieve the content + WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + ServiceRegistry serviceRegistry = (ServiceRegistry)context.getBean(ServiceRegistry.SERVICE_REGISTRY); + ContentService contentService = serviceRegistry.getContentService(); + + // get the content reader + ContentReader reader = contentService.getReader(nodeRef, propertyQName); + // ensure that it is safe to use + reader = FileContentReader.getSafeContentReader( + reader, + Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING), + nodeRef, reader); + + String mimetype = reader.getMimetype(); + // fall back if unable to resolve mimetype property + if (mimetype == null || mimetype.length() == 0) + { + MimetypeService mimetypeMap = serviceRegistry.getMimetypeService(); + mimetype = MIMETYPE_OCTET_STREAM; + int extIndex = filename.lastIndexOf('.'); + if (extIndex != -1) + { + String ext = filename.substring(extIndex + 1); + String mt = mimetypeMap.getMimetypesByExtension().get(ext); + if (mt != null) + { + mimetype = mt; + } + } + } + res.setContentType(mimetype); + + // get the content and stream directly to the response output stream + // assuming the repo is capable of streaming in chunks, this should allow large files + // to be streamed directly to the browser response stream. + try + { + reader.getContent( res.getOutputStream() ); + } + catch (SocketException e) + { + if (e.getMessage().contains("ClientAbortException")) + { + // the client cut the connection - our mission was accomplished apart from a little error message + logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader); + } + else + { + throw e; + } + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err); + } + finally + { + out.close(); + } + } + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * The content is supplied as an HTTP1.1 attachment to the response. This generally means + * a browser should prompt the user to save the content to specified location. + * + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name to return in the URL (cannot be null) + * + * @return URL to download the content from the specified node + */ + public final static String generateDownloadURL(NodeRef ref, String name) + { + String url = null; + + try + { + url = MessageFormat.format(DOWNLOAD_URL, new Object[] { + ref.getStoreRef().getProtocol(), + ref.getStoreRef().getIdentifier(), + ref.getId(), + URLEncoder.encode(name, "US-ASCII") } ); + } + catch (UnsupportedEncodingException uee) + { + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee); + } + + return url; + } + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * The content is supplied directly in the reponse. This generally means a browser will + * attempt to open the content directly if possible, else it will prompt to save the file. + * + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name to return in the URL (cannot be null) + * + * @return URL to download the content from the specified node + */ + public final static String generateBrowserURL(NodeRef ref, String name) + { + String url = null; + + try + { + url = MessageFormat.format(BROWSER_URL, new Object[] { + ref.getStoreRef().getProtocol(), + ref.getStoreRef().getIdentifier(), + ref.getId(), + URLEncoder.encode(name, "US-ASCII") } ); + } + catch (UnsupportedEncodingException uee) + { + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee); + } + + return url; + } +} diff --git a/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java b/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java new file mode 100644 index 0000000000..866344edc4 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/ExternalAccessServlet.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.StringTokenizer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.web.bean.LoginBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Servlet allowing external URL access to various global JSF views in the Web Client. + *

+ * The servlet accepts a well formed URL that can easily be generated from a Content or Space NodeRef. + * The URL also specifies the JSF "outcome" to be executed which provides the correct JSF View to be + * displayed. The JSF "outcome" must equate to a global navigation rule or it will not be displayed. + * Servlet URL is of the form: + *

+ * http://<server>/alfresco/navigate/<outcome>[/<workspace>/<store>/<nodeId>] or
+ * http://<server>/alfresco/navigate/<outcome>[/webdav/<path/to/node>] + *

+ * + * @author Kevin Roast + */ +public class ExternalAccessServlet extends HttpServlet +{ + private static final long serialVersionUID = -4118907921337237802L; + + private static Log logger = LogFactory.getLog(ExternalAccessServlet.class); + + + /** + * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void service(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + boolean alreadyAuthenticated = AuthenticationHelper.authenticate(getServletContext(), req, res); + + // The URL contains multiple parts + // /alfresco/navigate/ + String uri = req.getRequestURI(); + + if (logger.isDebugEnabled()) + logger.debug("Processing URL: " + uri); + + StringTokenizer t = new StringTokenizer(uri, "/"); + int count = t.countTokens(); + if (count < 3) + { + throw new IllegalArgumentException("Externally addressable URL did not contain all required args: " + uri); + } + t.nextToken(); // skip web app name + t.nextToken(); // skip servlet name + + String outcome = t.nextToken(); + + // get rest of the tokens + String[] tokens = new String[count - 3]; + for (int i=0; i/alfresco/navigate/[///] + private static final String EXTERNAL_URL = "/navigate/{0}"; + private static final String EXTERNAL_URL_ARGS = "/navigate/{0}/{1}"; +} diff --git a/source/java/org/alfresco/web/app/servlet/ModeDetectionFilter.java b/source/java/org/alfresco/web/app/servlet/ModeDetectionFilter.java new file mode 100644 index 0000000000..efafd0eb1d --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/ModeDetectionFilter.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +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 org.alfresco.web.app.Application; + + +/** + * Filter that determines whether the application is running inside a portal + * server or servlet engine. The fact that this filter gets called means + * the application is running inside a servlet engine. + * + * @author gavinc + */ +public class ModeDetectionFilter implements Filter +{ + /** + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + public void init(FilterConfig config) throws ServletException + { + // nothing to do + } + + /** + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException + { + // as we get here means we are inside a servlet engine as portal servers + // do not support the calling of filters yet + Application.setInPortalServer(false); + + // continue filter chaining + chain.doFilter(req, res); + } + + /** + * @see javax.servlet.Filter#destroy() + */ + public void destroy() + { + // nothing to do + } +} diff --git a/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java b/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java new file mode 100644 index 0000000000..27a51fbc46 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; +import java.net.SocketException; +import java.text.MessageFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.template.DateCompareMethod; +import org.alfresco.repo.template.HasAspectMethod; +import org.alfresco.repo.template.I18NMessageMethod; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateException; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.LoginBean; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Servlet responsible for streaming content from a template processed against a node directly + * to the response stream. + *

+ * The URL to the servlet should be generated thus: + *

/alfresco/template/workspace/SpacesStore/0000-0000-0000-0000
+ * or
+ * 
/alfresco/template/workspace/SpacesStore/0000-0000-0000-0000/workspace/SpacesStore/0000-0000-0000-0000
+ * 

+ * The store protocol, followed by the store ID, followed by the content Node Id used to + * identify the node to execute the default template for. The second set of elements encode + * the store and node Id of the template to used if a default is not set or not requested. + *

+ * The URL may be followed by a valid 'ticket' argument for authentication: ?ticket=1234567890 + *
+ * And may be followed by a 'mimetype' argument specifying the mimetype to return the result as + * on the stream. Otherwise it is assumed that HTML is the default response mimetype. + * + * @author Kevin Roast + */ +public class TemplateContentServlet extends HttpServlet +{ + private static final String MIMETYPE_HTML = "text/html"; + + private static final long serialVersionUID = -4123407921997235977L; + + private static Log logger = LogFactory.getLog(TemplateContentServlet.class); + + private static final String DEFAULT_URL = "/template/{0}/{1}/{2}"; + private static final String TEMPALTE_URL = "/template/{0}/{1}/{2}/{3}/{4}/{5}"; + + private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing"; + + private static final String ARG_TICKET = "ticket"; + private static final String ARG_MIMETYPE = "mimetype"; + + /** + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + try + { + String uri = req.getRequestURI(); + + if (logger.isDebugEnabled()) + logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : "")); + + // see if a ticket has been supplied + String ticket = req.getParameter(ARG_TICKET); + if (ticket == null || ticket.length() == 0) + { + if (AuthenticationHelper.authenticate(getServletContext(), req, res) == false) + { + // authentication failed - no point returning the content as we haven't logged in yet + // so end servlet execution and save the URL so the login page knows what to do later + req.getSession().setAttribute(LoginBean.LOGIN_REDIRECT_KEY, uri); + return; + } + } + else + { + AuthenticationHelper.authenticate(getServletContext(), req, res, ticket); + } + + StringTokenizer t = new StringTokenizer(uri, "/"); + int tokenCount = t.countTokens(); + if (tokenCount < 5) + { + throw new IllegalArgumentException("Download URL did not contain all required args: " + uri); + } + + t.nextToken(); // skip web app name + t.nextToken(); // skip servlet name + + // get NodeRef to the content + StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken()); + NodeRef nodeRef = new NodeRef(storeRef, t.nextToken()); + + // get NodeRef to the template if supplied + NodeRef templateRef = null; + if (tokenCount == 8) + { + storeRef = new StoreRef(t.nextToken(), t.nextToken()); + templateRef = new NodeRef(storeRef, t.nextToken()); + } + + // get the services we need to retrieve the content + WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + ServiceRegistry serviceRegistry = (ServiceRegistry)context.getBean(ServiceRegistry.SERVICE_REGISTRY); + NodeService nodeService = serviceRegistry.getNodeService(); + TemplateService templateService = serviceRegistry.getTemplateService(); + + UserTransaction txn = null; + try + { + txn = serviceRegistry.getTransactionService().getUserTransaction(true); + txn.begin(); + + // if template not supplied, then use the default against the node + if (templateRef == null) + { + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPLATABLE)) + { + templateRef = (NodeRef)nodeService.getProperty(nodeRef, ContentModel.PROP_TEMPLATE); + } + if (templateRef == null) + { + throw new TemplateException("Template reference not set against node or not supplied in URL."); + } + } + + // create the model - put the supplied noderef in as space/document as appropriate + Object model = getModel(serviceRegistry, req.getSession(), nodeRef); + + // process the template against the node content directly to the response output stream + // assuming the repo is capable of streaming in chunks, this should allow large files + // to be streamed directly to the browser response stream. + try + { + templateService.processTemplate( + null, + templateRef.toString(), + model, + res.getWriter()); + + // commit the transaction + txn.commit(); + } + catch (SocketException e) + { + if (e.getMessage().contains("ClientAbortException")) + { + // the client cut the connection - our mission was accomplished apart from a little error message + logger.error("Client aborted stream read:\n node: " + nodeRef + "\n template: " + templateRef); + try { if (txn != null) {txn.rollback();} } catch (Exception tex) {} + } + else + { + throw e; + } + } + } + catch (Throwable txnErr) + { + try { if (txn != null) {txn.rollback();} } catch (Exception tex) {} + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err); + } + } + + private Object getModel(ServiceRegistry services, HttpSession session, NodeRef nodeRef) + { + // create FreeMarker default model and merge + Map root = new HashMap(11, 1.0f); + + // supply the CompanyHome space as "companyhome" + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + TemplateNode companyRootNode = new TemplateNode(companyRootRef, services, imageResolver); + root.put("companyhome", companyRootNode); + + // supply the users Home Space as "userhome" + User user = Application.getCurrentUser(session); + NodeRef userRootRef = new NodeRef(Repository.getStoreRef(), user.getHomeSpaceId()); + TemplateNode userRootNode = new TemplateNode(userRootRef, services, imageResolver); + root.put("userhome", userRootNode); + + // put the current NodeRef in as "space" and "document" + TemplateNode node = new TemplateNode(nodeRef, services, imageResolver); + root.put("space", node); + root.put("document", node); + + // supply the current user Node as "person" + root.put("person", new TemplateNode(user.getPerson(), services, imageResolver)); + + // current date/time is useful to have and isn't supplied by FreeMarker by default + root.put("date", new Date()); + + // add custom method objects + root.put("hasAspect", new HasAspectMethod()); + root.put("message", new I18NMessageMethod()); + root.put("dateCompare", new DateCompareMethod()); + + return root; + } + + /** Template Image resolver helper */ + private TemplateImageResolver imageResolver = new TemplateImageResolver() + { + public String resolveImagePathForName(String filename, boolean small) + { + return Utils.getFileTypeImage(filename, small); + } + }; + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * The content is supplied as an HTTP1.1 attachment to the response. This generally means + * a browser should prompt the user to save the content to specified location. + * + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name to return in the URL (cannot be null) + * + * @return URL to download the content from the specified node + */ + public final static String generateURL(NodeRef ref, String name) + { + return MessageFormat.format(DEFAULT_URL, new Object[] { + ref.getStoreRef().getProtocol(), + ref.getStoreRef().getIdentifier(), + ref.getId() } ); + } +} diff --git a/source/java/org/alfresco/web/app/servlet/UploadFileServlet.java b/source/java/org/alfresco/web/app/servlet/UploadFileServlet.java new file mode 100644 index 0000000000..18fae11f39 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/UploadFileServlet.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.util.TempFileProvider; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.FileUploadBean; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Servlet that takes a file uploaded via a browser and represents it as an + * UploadFileBean in the session + * + * @author gavinc + */ +public class UploadFileServlet extends HttpServlet +{ + private static final long serialVersionUID = -5482538466491052873L; + private static Log logger = LogFactory.getLog(UploadFileServlet.class); + + /** + * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String returnPage = null; + boolean isMultipart = ServletFileUpload.isMultipartContent(request); + + try + { + AuthenticationHelper.authenticate(getServletContext(), request, response); + + if (isMultipart == false) + { + throw new AlfrescoRuntimeException("This servlet can only be used to handle file upload requests, make" + + "sure you have set the enctype attribute on your form to multipart/form-data"); + } + + if (logger.isDebugEnabled()) + logger.debug("Uploading servlet servicing..."); + + HttpSession session = request.getSession(); + ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); + List fileItems = upload.parseRequest(request); + + Iterator iter = fileItems.iterator(); + FileUploadBean bean = new FileUploadBean(); + while(iter.hasNext()) + { + FileItem item = iter.next(); + if(item.isFormField()) + { + if (item.getFieldName().equalsIgnoreCase("return-page")) + { + returnPage = item.getString(); + } + } + else + { + String filename = item.getName(); + if (filename != null && filename.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Processing uploaded file: " + filename); + + // workaround a bug in IE where the full path is returned + // IE is only available for Windows so only check for the Windows path separator + int idx = filename.lastIndexOf('\\'); + + if (idx == -1) + { + // if there is no windows path separator check for *nix + idx = filename.lastIndexOf('/'); + } + + if (idx != -1) + { + filename = filename.substring(idx + File.separator.length()); + } + + File tempFile = TempFileProvider.createTempFile("alfresco", ".upload"); + item.write(tempFile); + bean.setFile(tempFile); + bean.setFileName(filename); + bean.setFilePath(tempFile.getAbsolutePath()); + session.setAttribute(FileUploadBean.FILE_UPLOAD_BEAN_NAME, bean); + if (logger.isDebugEnabled()) + logger.debug("Temp file: " + tempFile.getAbsolutePath() + " created from upload filename: " + filename); + } + } + } + + if (returnPage == null || returnPage.length() == 0) + { + throw new AlfrescoRuntimeException("return-page parameter has not been supplied"); + } + + // finally redirect + if (logger.isDebugEnabled()) + logger.debug("Upload servicing complete, redirecting to: " + returnPage); + + response.sendRedirect(returnPage); + } + catch (Throwable error) + { + Application.handleServletError(getServletContext(), (HttpServletRequest)request, + (HttpServletResponse)response, error, logger, returnPage); + } + } +} diff --git a/source/java/org/alfresco/web/bean/AdminBean.java b/source/java/org/alfresco/web/bean/AdminBean.java new file mode 100644 index 0000000000..0be3526671 --- /dev/null +++ b/source/java/org/alfresco/web/bean/AdminBean.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.alfresco.config.xml.XMLConfigService; +import org.alfresco.web.app.Application; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Bean used for administration purposes + * + * @author gavinc + */ +public class AdminBean +{ + /** + * Resets the config service + * + * @param event Event that caused the request for the reset + */ + public void resetConfigService(ActionEvent event) + { + // get the config service + XMLConfigService configSvc = (XMLConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + + // reset it + configSvc.reset(); + } +} diff --git a/source/java/org/alfresco/web/bean/AdminNodeBrowseBean.java b/source/java/org/alfresco/web/bean/AdminNodeBrowseBean.java new file mode 100644 index 0000000000..57653e02c0 --- /dev/null +++ b/source/java/org/alfresco/web/bean/AdminNodeBrowseBean.java @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.faces.model.SelectItem; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.search.ISO9075; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.web.app.servlet.DownloadContentServlet; + + +// TODO: DownloadServlet - use of request parameter for property name? +// TODO: Anyway to switch content view url link / property value text? + + +/** + * Backing bean to support the Admin Node Browser + */ +public class AdminNodeBrowseBean +{ + /** selected query language */ + private String queryLanguage = null; + + /** available query languages */ + private static List queryLanguages = new ArrayList(); + static + { + queryLanguages.add(new SelectItem("noderef")); + queryLanguages.add(new SelectItem(SearchService.LANGUAGE_XPATH)); + queryLanguages.add(new SelectItem(SearchService.LANGUAGE_LUCENE)); + } + + // query and results + private String query = null; + private SearchResults searchResults = new SearchResults(null); + + // stores and node + private DataModel stores = null; + private NodeRef nodeRef = null; + private QName nodeType = null; + private Path primaryPath = null; + private DataModel parents = null; + private DataModel aspects = null; + private DataModel properties = null; + private DataModel children = null; + private DataModel assocs = null; + + // supporting repository services + private NodeService nodeService; + private DictionaryService dictionaryService; + private SearchService searchService; + + /** + * @param nodeService node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param dictionaryService dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + + /** + * Gets the list of repository stores + * + * @return stores + */ + public DataModel getStores() + { + if (stores == null) + { + List storeRefs = nodeService.getStores(); + stores = new ListDataModel(storeRefs); + } + return stores; + } + + /** + * Gets the selected node reference + * + * @return node reference (defaults to system store root) + */ + public NodeRef getNodeRef() + { + if (nodeRef == null) + { + nodeRef = nodeService.getRootNode(new StoreRef("system", "system")); + } + return nodeRef; + } + + /** + * Sets the selected node reference + * + * @param nodeRef node reference + */ + private void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + + // clear cache + primaryPath = null; + nodeType = null; + parents = null; + aspects = null; + properties = null; + children = null; + assocs = null; + } + + /** + * Gets the current node type + * + * @return node type + */ + public QName getNodeType() + { + if (nodeType == null) + { + nodeType = nodeService.getType(getNodeRef()); + } + return nodeType; + } + + /** + * Gets the current node primary path + * + * @return primary path + */ + public String getPrimaryPath() + { + if (primaryPath == null) + { + primaryPath = nodeService.getPath(getNodeRef()); + } + return ISO9075.decode(primaryPath.toString()); + } + + /** + * Gets the current node primary parent reference + * + * @return primary parent ref + */ + public NodeRef getPrimaryParent() + { + getPrimaryPath(); + Path.Element element = primaryPath.last(); + NodeRef parentRef = ((Path.ChildAssocElement)element).getRef().getParentRef(); + return parentRef; + } + + /** + * Gets the current node aspects + * + * @return node aspects + */ + public DataModel getAspects() + { + if (aspects == null) + { + List aspectNames = new ArrayList(nodeService.getAspects(getNodeRef())); + aspects = new ListDataModel(aspectNames); + } + return aspects; + } + + /** + * Gets the current node parents + * + * @return node parents + */ + public DataModel getParents() + { + if (parents == null) + { + List parentRefs = nodeService.getParentAssocs(getNodeRef()); + parents = new ListDataModel(parentRefs); + } + return parents; + } + + /** + * Gets the current node properties + * + * @return properties + */ + public DataModel getProperties() + { + if (properties == null) + { + Map propertyValues = nodeService.getProperties(getNodeRef()); + List nodeProperties = new ArrayList(propertyValues.size()); + for (Map.Entry property : propertyValues.entrySet()) + { + nodeProperties.add(new Property(property.getKey(), property.getValue())); + } + properties = new ListDataModel(nodeProperties); + } + return properties; + } + + /** + * Gets the current node children + * + * @return node children + */ + public DataModel getChildren() + { + if (children == null) + { + List assocRefs = nodeService.getChildAssocs(getNodeRef()); + children = new ListDataModel(assocRefs); + } + return children; + } + + /** + * Gets the current node associations + * + * @return associations + */ + public DataModel getAssocs() + { + if (assocs == null) + { + List assocRefs = nodeService.getTargetAssocs(getNodeRef(), RegexQNamePattern.MATCH_ALL); + assocs = new ListDataModel(assocRefs); + } + return assocs; + } + + /** + * Gets the current query language + * + * @return query language + */ + public String getQueryLanguage() + { + return queryLanguage; + } + + /** + * Sets the current query language + * + * @param queryLanguage query language + */ + public void setQueryLanguage(String queryLanguage) + { + this.queryLanguage = queryLanguage; + } + + /** + * Gets the current query + * + * @return query statement + */ + public String getQuery() + { + return query; + } + + /** + * Set the current query + * + * @param query query statement + */ + public void setQuery(String query) + { + this.query = query; + } + + /** + * Gets the list of available query languages + * + * @return query languages + */ + public List getQueryLanguages() + { + return queryLanguages; + } + + /** + * Gets the current search results + * + * @return search results + */ + public SearchResults getSearchResults() + { + return searchResults; + } + + /** + * Action to select a store + * + * @return next action + */ + public String selectStore() + { + StoreRef storeRef = (StoreRef)stores.getRowData(); + NodeRef rootNode = nodeService.getRootNode(storeRef); + setNodeRef(rootNode); + return "success"; + } + + /** + * Action to select stores list + * + * @return next action + */ + public String selectStores() + { + stores = null; + return "success"; + } + + /** + * Action to select primary path + * + * @return next action + */ + public String selectPrimaryPath() + { + // force refresh of self + setNodeRef(nodeRef); + return "success"; + } + + /** + * Action to select primary parent + * + * @return next action + */ + public String selectPrimaryParent() + { + setNodeRef(getPrimaryParent()); + return "success"; + } + + /** + * Action to select parent + * + * @return next action + */ + public String selectParent() + { + ChildAssociationRef assocRef = (ChildAssociationRef)parents.getRowData(); + NodeRef parentRef = assocRef.getParentRef(); + setNodeRef(parentRef); + return "success"; + } + + /** + * Action to select association To node + * + * @return next action + */ + public String selectToNode() + { + AssociationRef assocRef = (AssociationRef)assocs.getRowData(); + NodeRef targetRef = assocRef.getTargetRef(); + setNodeRef(targetRef); + return "success"; + } + + /** + * Action to select node property + * + * @return next action + */ + public String selectNodeProperty() + { + Property property = (Property)properties.getRowData(); + Property.Value value = (Property.Value)property.getValues().getRowData(); + NodeRef nodeRef = (NodeRef)value.getValue(); + setNodeRef(nodeRef); + return "success"; + } + + /** + * Action to select child + * + * @return next action + */ + public String selectChild() + { + ChildAssociationRef assocRef = (ChildAssociationRef)children.getRowData(); + NodeRef childRef = assocRef.getChildRef(); + setNodeRef(childRef); + return "success"; + } + + /** + * Action to select search result node + * + * @return next action + */ + public String selectResultNode() + { + ChildAssociationRef assocRef = (ChildAssociationRef)searchResults.getRows().getRowData(); + NodeRef childRef = assocRef.getChildRef(); + setNodeRef(childRef); + return "success"; + } + + /** + * Action to submit search + * + * @return next action + */ + public String submitSearch() + { + try + { + if (queryLanguage.equals("noderef")) + { + // ensure node exists + NodeRef nodeRef = new NodeRef(query); + boolean exists = nodeService.exists(nodeRef); + if (!exists) + { + throw new AlfrescoRuntimeException("Node " + nodeRef + " does not exist."); + } + setNodeRef(nodeRef); + return "node"; + } + + // perform search + searchResults = new SearchResults(searchService.query(getNodeRef().getStoreRef(), queryLanguage, query)); + return "search"; + } + catch(Throwable e) + { + FacesContext context = FacesContext.getCurrentInstance(); + FacesMessage message = new FacesMessage(); + message.setSeverity(FacesMessage.SEVERITY_ERROR); + message.setDetail("Search failed due to: " + e.toString()); + context.addMessage("searchForm:query", message); + return "error"; + } + } + + /** + * Property wrapper class + */ + public class Property + { + private QName name; + private boolean isCollection = false; + private DataModel values; + private String datatype; + private String residual; + + /** + * Construct + * + * @param name property name + * @param value property values + */ + public Property(QName name, Serializable value) + { + this.name = name; + + PropertyDefinition propDef = dictionaryService.getProperty(name); + if (propDef != null) + { + datatype = propDef.getDataType().getName().toString(); + residual = "false"; + } + else + { + residual = "true"; + } + + // handle multi/single values + // TODO: perhaps this is not the most efficient way - lots of list creations + List values = new ArrayList(); + if (value instanceof Collection) + { + isCollection = true; + for (Serializable multiValue : (Collection)value) + { + values.add(new Value(multiValue)); + } + } + else + { + values.add(new Value(value)); + } + this.values = new ListDataModel(values); + } + + /** + * Gets the property name + * + * @return name + */ + public QName getName() + { + return name; + } + + /** + * Gets the property data type + * + * @return data type + */ + public String getDataType() + { + return datatype; + } + + /** + * Gets the property value + * + * @return value + */ + public DataModel getValues() + { + return values; + } + + /** + * Determines whether the property is residual + * + * @return true => property is not defined in dictionary + */ + public String getResidual() + { + return residual; + } + + /** + * Determines whether the property is of ANY type + * + * @return true => is any + */ + public boolean isAny() + { + return (datatype == null) ? false : datatype.equals(DataTypeDefinition.ANY.toString()); + } + + /** + * Determines whether the property is a collection + * + * @return true => is collection + */ + public boolean isCollection() + { + return isCollection; + } + + + /** + * Value wrapper + */ + public class Value + { + private Serializable value; + + /** + * Construct + * + * @param value value + */ + public Value(Serializable value) + { + this.value = value; + } + + /** + * Gets the value + * + * @return the value + */ + public Serializable getValue() + { + return value; + } + + /** + * Gets the value datatype + * + * @return the value datatype + */ + public String getDataType() + { + String datatype = Property.this.getDataType(); + if (datatype == null || datatype.equals(DataTypeDefinition.ANY.toString())) + { + if (value != null) + { + datatype = dictionaryService.getDataType(value.getClass()).getName().toString(); + } + } + return datatype; + } + + /** + * Gets the download url (for content properties) + * + * @return url + */ + public String getUrl() + { + String url = FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath(); + url += DownloadContentServlet.generateBrowserURL(nodeRef, "file.bin"); + url += "?property=" + name; + return url; + } + + /** + * Determines whether the value is content + * + * @return true => is content + */ + public boolean isContent() + { + String datatype = getDataType(); + return (datatype == null) ? false : datatype.equals(DataTypeDefinition.CONTENT.toString()); + } + + /** + * Determines whether the value is a node ref + * + * @return true => is node ref + */ + public boolean isNodeRef() + { + String datatype = getDataType(); + return (datatype == null) ? false : datatype.equals(DataTypeDefinition.NODE_REF.toString()) || datatype.equals(DataTypeDefinition.CATEGORY.toString()); + } + + /** + * Determines whether the value is null + * + * @return true => value is null + */ + public boolean isNullValue() + { + return value == null; + } + } + } + + /** + * Wrapper class for Search Results + */ + public class SearchResults + { + private int length = 0; + private DataModel rows; + + /** + * Construct + * + * @param resultSet query result set + */ + public SearchResults(ResultSet resultSet) + { + rows = new ListDataModel(); + if (resultSet != null) + { + rows.setWrappedData(resultSet.getChildAssocRefs()); + length = resultSet.length(); + } + } + + /** + * Gets the row count + * + * @return count of rows + */ + public int getLength() + { + return length; + } + + /** + * Gets the rows + * + * @return the rows + */ + public DataModel getRows() + { + return rows; + } + } + +} diff --git a/source/java/org/alfresco/web/bean/AdvancedSearchBean.java b/source/java/org/alfresco/web/bean/AdvancedSearchBean.java new file mode 100644 index 0000000000..19de6ce188 --- /dev/null +++ b/source/java/org/alfresco/web/bean/AdvancedSearchBean.java @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.SelectItem; + +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.CachingDateFormat; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.config.ClientConfigElement.CustomProperty; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.component.UIPanel.ExpandedEvent; +import org.alfresco.web.ui.repo.component.UISearchCustomProperties; + +/** + * Provides the form state and action event handling for the Advanced Search UI. + *

+ * Integrates with the web-client ConfigService to retrieve configuration of custom + * meta-data searching fields. Custom fields can be configured to appear in the UI + * and they are they automatically added to the search query by this bean. + * + * @author Kevin Roast + */ +public class AdvancedSearchBean +{ + /** + * Default constructor + */ + public AdvancedSearchBean() + { + // initial state of progressive panels that don't use the default + panels.put("categories-panel", false); + panels.put("attrs-panel", false); + panels.put("custom-panel", false); + } + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param namespaceService The NamespaceService to set. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @return Returns the progressive panels expanded state map. + */ + public Map getPanels() + { + return this.panels; + } + + /** + * @param panels The progressive panels expanded state map. + */ + public void setPanels(Map panels) + { + this.panels = panels; + } + + /** + * @return Returns the folder to search, null for all. + */ + public String getLookin() + { + return this.lookin; + } + + /** + * @param lookin The folder to search in or null for all. + */ + public void setLookin(String lookIn) + { + this.lookin = lookIn; + } + + /** + * @return Returns the location. + */ + public NodeRef getLocation() + { + return this.location; + } + + /** + * @param location The location to set. + */ + public void setLocation(NodeRef location) + { + this.location = location; + } + + /** + * @return Returns the search mode. + */ + public String getMode() + { + return this.mode; + } + + /** + * @param mode The search mode to set. + */ + public void setMode(String mode) + { + this.mode = mode; + } + + /** + * @return Returns the text to search for. + */ + public String getText() + { + return this.text; + } + + /** + * @param text The text to set. + */ + public void setText(String text) + { + this.text = text; + } + + /** + * @return Returns the category. + */ + public NodeRef getCategory() + { + return this.category; + } + + /** + * @param category The category to set. + */ + public void setCategory(NodeRef category) + { + this.category = category; + } + + /** + * @return Returns true to search location children, false for just the specified location. + */ + public boolean getLocationChildren() + { + return this.locationChildren; + } + + /** + * @param locationChildren True to search location children, false for just the specified location. + */ + public void setLocationChildren(boolean locationChildren) + { + this.locationChildren = locationChildren; + } + + /** + * @return Returns true to search category children, false for just the specified category. + */ + public boolean getCategoryChildren() + { + return this.categoryChildren; + } + + /** + * @param categoryChildren True to search category children, false for just the specified category. + */ + public void setCategoryChildren(boolean categoryChildren) + { + this.categoryChildren = categoryChildren; + } + + /** + * @return Returns the createdDateFrom. + */ + public Date getCreatedDateFrom() + { + return this.createdDateFrom; + } + + /** + * @param createdDateFrom The createdDateFrom to set. + */ + public void setCreatedDateFrom(Date createdDate) + { + this.createdDateFrom = createdDate; + } + + /** + * @return Returns the description. + */ + public String getDescription() + { + return this.description; + } + + /** + * @param description The description to set. + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * @return Returns the modifiedDateFrom. + */ + public Date getModifiedDateFrom() + { + return this.modifiedDateFrom; + } + + /** + * @param modifiedDateFrom The modifiedDateFrom to set. + */ + public void setModifiedDateFrom(Date modifiedDate) + { + this.modifiedDateFrom = modifiedDate; + } + + /** + * @return Returns the createdDateTo. + */ + public Date getCreatedDateTo() + { + return this.createdDateTo; + } + + /** + * @param createdDateTo The createdDateTo to set. + */ + public void setCreatedDateTo(Date createdDateTo) + { + this.createdDateTo = createdDateTo; + } + + /** + * @return Returns the modifiedDateTo. + */ + public Date getModifiedDateTo() + { + return this.modifiedDateTo; + } + + /** + * @param modifiedDateTo The modifiedDateTo to set. + */ + public void setModifiedDateTo(Date modifiedDateTo) + { + this.modifiedDateTo = modifiedDateTo; + } + + /** + * @return Returns the title. + */ + public String getTitle() + { + return this.title; + } + + /** + * @param title The title to set. + */ + public void setTitle(String title) + { + this.title = title; + } + + /** + * @return Returns the author. + */ + public String getAuthor() + { + return this.author; + } + + /** + * @param author The author to set. + */ + public void setAuthor(String author) + { + this.author = author; + } + + /** + * @return Returns the modifiedDateChecked. + */ + public boolean isModifiedDateChecked() + { + return this.modifiedDateChecked; + } + + /** + * @param modifiedDateChecked The modifiedDateChecked to set. + */ + public void setModifiedDateChecked(boolean modifiedDateChecked) + { + this.modifiedDateChecked = modifiedDateChecked; + } + + /** + * @return Returns the createdDateChecked. + */ + public boolean isCreatedDateChecked() + { + return this.createdDateChecked; + } + + /** + * @return Returns the content type currenty selected + */ + public String getContentType() + { + return this.contentType; + } + + /** + * @param contentType Sets the currently selected content type + */ + public void setContentType(String contentType) + { + this.contentType = contentType; + } + + /** + * @return Returns the contentFormat. + */ + public String getContentFormat() + { + return this.contentFormat; + } + + /** + * @param contentFormat The contentFormat to set. + */ + public void setContentFormat(String contentFormat) + { + this.contentFormat = contentFormat; + } + + /** + * @return Returns the custom properties Map. + */ + public Map getCustomProperties() + { + return this.customProperties; + } + + /** + * @param customProperties The custom properties Map to set. + */ + public void setCustomProperties(Map customProperties) + { + this.customProperties = customProperties; + } + + /** + * @param createdDateChecked The createdDateChecked to set. + */ + public void setCreatedDateChecked(boolean createdDateChecked) + { + this.createdDateChecked = createdDateChecked; + } + + /** + * @return Returns a list of content object types to allow the user to select from + */ + public List getContentTypes() + { + if (this.contentTypes == null) + { + FacesContext context = FacesContext.getCurrentInstance(); + + // add the well known cm:content object type by default + this.contentTypes = new ArrayList(5); + this.contentTypes.add(new SelectItem(ContentModel.TYPE_CONTENT.toString(), + Application.getMessage(context, MSG_CONTENT))); + + // add any configured content sub-types to the list + List types = getClientConfig().getContentTypes(); + if (types != null) + { + DictionaryService dictionaryService = Repository.getServiceRegistry(context).getDictionaryService(); + for (String type : types) + { + QName idQName = Repository.resolveToQName(type); + TypeDefinition typeDef = dictionaryService.getType(idQName); + + if (typeDef != null && dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT)) + { + // try and get label from the dictionary + String label = typeDef.getTitle(); + + // else just use the localname + if (label == null) + { + label = idQName.getLocalName(); + } + + this.contentTypes.add(new SelectItem(idQName.toString(), label)); + } + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.contentTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + } + + return this.contentTypes; + } + + /** + * @return Returns a list of content formats to allow the user to select from + */ + public List getContentFormats() + { + if (this.contentFormats == null) + { + this.contentFormats = new ArrayList(80); + ServiceRegistry registry = Repository.getServiceRegistry(FacesContext.getCurrentInstance()); + MimetypeService mimetypeService = registry.getMimetypeService(); + + // get the mime type display names + Map mimeTypes = mimetypeService.getDisplaysByMimetype(); + for (String mimeType : mimeTypes.keySet()) + { + this.contentFormats.add(new SelectItem(mimeType, mimeTypes.get(mimeType))); + } + + // make sure the list is sorted by the values + QuickSort sorter = new QuickSort(this.contentFormats, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + + // add the "All Formats" constant marker at the top of the list (default selection) + this.contentFormats.add(0, new SelectItem("", Application.getMessage(FacesContext.getCurrentInstance(), MSG_ALL_FORMATS))); + } + + return this.contentFormats; + } + + + // ------------------------------------------------------------------------------ + // Action event handlers + + /** + * Handler to clear the advanced search screen form details + */ + public void reset(ActionEvent event) + { + this.text = ""; + this.mode = MODE_ALL; + this.lookin = LOOKIN_ALL; + this.contentType = null; + this.location = null; + this.category = null; + this.title = null; + this.description = null; + this.author = null; + this.createdDateFrom = null; + this.modifiedDateFrom = null; + this.createdDateChecked = false; + this.modifiedDateChecked = false; + this.customProperties.clear(); + } + + /** + * Handler to perform a search based on the current criteria + */ + public String search() + { + String outcome = null; + + if (this.text != null && this.text.length() != 0) + { + // construct the Search Context and set on the navigation bean + // then simply navigating to the browse screen will cause it pickup the Search Context + SearchContext search = new SearchContext(); + + search.setText(this.text); + + if (this.mode.equals(MODE_ALL)) + { + search.setMode(SearchContext.SEARCH_ALL); + } + else if (this.mode.equals(MODE_FILES_TEXT)) + { + search.setMode(SearchContext.SEARCH_FILE_NAMES_CONTENTS); + } + else if (this.mode.equals(MODE_FILES)) + { + search.setMode(SearchContext.SEARCH_FILE_NAMES); + } + else if (this.mode.equals(MODE_FOLDERS)) + { + search.setMode(SearchContext.SEARCH_SPACE_NAMES); + } + + // additional attributes search + if (this.description != null && this.description.length() != 0) + { + search.addAttributeQuery(ContentModel.PROP_DESCRIPTION, this.description); + } + if (this.title != null && this.title.length() != 0) + { + search.addAttributeQuery(ContentModel.PROP_TITLE, this.title); + } + if (this.author != null && this.author.length() != 0) + { + search.addAttributeQuery(ContentModel.PROP_CREATOR, this.author); + } + if (this.contentFormat != null && this.contentFormat.length() != 0) + { + search.setMimeType(this.contentFormat); + } + if (this.createdDateChecked == true) + { + SimpleDateFormat df = CachingDateFormat.getDateFormat(); + String strCreatedDate = df.format(this.createdDateFrom); + String strCreatedDateTo = df.format(this.createdDateTo); + search.addRangeQuery(ContentModel.PROP_CREATED, strCreatedDate, strCreatedDateTo, true); + } + if (this.modifiedDateChecked == true) + { + SimpleDateFormat df = CachingDateFormat.getDateFormat(); + String strModifiedDate = df.format(this.modifiedDateFrom); + String strModifiedDateTo = df.format(this.modifiedDateTo); + search.addRangeQuery(ContentModel.PROP_MODIFIED, strModifiedDate, strModifiedDateTo, true); + } + + // walk each of the custom properties add add them as additional attributes + for (String qname : this.customProperties.keySet()) + { + Object value = this.customProperties.get(qname); + DataTypeDefinition typeDef = getCustomPropertyLookup().get(qname); + if (typeDef != null) + { + QName typeName = typeDef.getName(); + if (DataTypeDefinition.DATE.equals(typeName) || DataTypeDefinition.DATETIME.equals(typeName)) + { + // only apply date to search if the user has checked the enable checkbox + if (value != null && Boolean.valueOf(value.toString()) == true) + { + SimpleDateFormat df = CachingDateFormat.getDateFormat(); + String strDateFrom = df.format(this.customProperties.get( + UISearchCustomProperties.PREFIX_DATE_FROM + qname)); + String strDateTo = df.format(this.customProperties.get( + UISearchCustomProperties.PREFIX_DATE_TO + qname)); + search.addRangeQuery(QName.createQName(qname), strDateFrom, strDateTo, true); + } + } + else if (DataTypeDefinition.BOOLEAN.equals(typeName)) + { + if (((Boolean)value) == true) + { + search.addFixedValueQuery(QName.createQName(qname), value.toString()); + } + } + else if (DataTypeDefinition.NODE_REF.equals(typeName) || DataTypeDefinition.CATEGORY.equals(typeName)) + { + if (value != null) + { + search.addFixedValueQuery(QName.createQName(qname), value.toString()); + } + } + else if (DataTypeDefinition.INT.equals(typeName) || DataTypeDefinition.LONG.equals(typeName) || + DataTypeDefinition.FLOAT.equals(typeName) || DataTypeDefinition.DOUBLE.equals(typeName)) + { + String strVal = value.toString(); + if (strVal != null && strVal.length() != 0) + { + search.addFixedValueQuery(QName.createQName(qname), strVal); + } + } + else + { + // by default use toString() value - this is for text fields and unknown types + String strVal = value.toString(); + if (strVal != null && strVal.length() != 0) + { + search.addAttributeQuery(QName.createQName(qname), strVal); + } + } + } + } + + // location path search + if (this.lookin.equals(LOOKIN_OTHER) && this.location != null) + { + search.setLocation(SearchContext.getPathFromSpaceRef( + new NodeRef(Repository.getStoreRef(), this.location.getId()), this.locationChildren)); + } + + // category path search + if (this.category != null) + { + search.setCategories(new String[]{SearchContext.getPathFromSpaceRef( + new NodeRef(Repository.getStoreRef(), this.category.getId()), this.categoryChildren)}); + } + + // content type restriction + if (this.contentType != null) + { + search.setContentType(this.contentType); + } + + // set the Search Context onto the top-level navigator bean + // this causes the browse screen to switch into search results view + this.navigator.setSearchContext(search); + + outcome = "browse"; + } + + return outcome; + } + + /** + * @return ClientConfigElement + */ + private ClientConfigElement getClientConfig() + { + if (clientConfigElement == null) + { + ConfigService configService = Application.getConfigService(FacesContext.getCurrentInstance()); + clientConfigElement = (ClientConfigElement)configService.getGlobalConfig().getConfigElement( + ClientConfigElement.CONFIG_ELEMENT_ID); + } + return clientConfigElement; + } + + /** + * Helper map to lookup custom property QName strings against a DataTypeDefinition + * + * @return custom property lookup Map + */ + private Map getCustomPropertyLookup() + { + if (customPropertyLookup == null) + { + customPropertyLookup = new HashMap(7, 1.0f); + List customProps = getClientConfig().getCustomProperties(); + if (customProps != null) + { + DictionaryService dd = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getDictionaryService(); + for (CustomProperty customProp : customProps) + { + PropertyDefinition propDef = null; + QName propQName = Repository.resolveToQName(customProp.Property); + if (customProp.Type != null) + { + QName type = Repository.resolveToQName(customProp.Type); + TypeDefinition typeDef = dd.getType(type); + propDef = typeDef.getProperties().get(propQName); + } + else if (customProp.Aspect != null) + { + QName aspect = Repository.resolveToQName(customProp.Aspect); + AspectDefinition aspectDef = dd.getAspect(aspect); + propDef = aspectDef.getProperties().get(propQName); + } + customPropertyLookup.put(propQName.toString(), propDef.getDataType()); + } + } + } + return customPropertyLookup; + } + + /** + * Save the state of the progressive panel that was expanded/collapsed + */ + public void expandPanel(ActionEvent event) + { + if (event instanceof ExpandedEvent) + { + this.panels.put(event.getComponent().getId(), ((ExpandedEvent)event).State); + } + } + + + // ------------------------------------------------------------------------------ + // Private data + + private static final String MSG_CONTENT = "content"; + private static final String MSG_ALL_FORMATS = "all_formats"; + + private static final String MODE_ALL = "all"; + private static final String MODE_FILES_TEXT = "files_text"; + private static final String MODE_FILES = "files"; + private static final String MODE_FOLDERS = "folders"; + + private static final String LOOKIN_ALL = "all"; + private static final String LOOKIN_OTHER = "other"; + + /** The NodeService to be used by the bean */ + private NodeService nodeService; + + /** The NamespaceService to be used by the bean */ + private NamespaceService namespaceService; + + /** The NavigationBean reference */ + private NavigationBean navigator; + + /** Client Config reference */ + private ClientConfigElement clientConfigElement = null; + + /** Progressive panel UI state */ + private Map panels = new HashMap(5, 1.0f); + + /** custom property names to values */ + private Map customProperties = new HashMap(5, 1.0f); + + /** lookup of custom property QName string to DataTypeDefinition for the property */ + private Map customPropertyLookup = null; + + /** content types to for restricting searches */ + private List contentTypes; + + /** content format list restricting searches */ + private List contentFormats; + + /** content type selection */ + private String contentType; + + /** content format selection */ + private String contentFormat; + + /** the text to search for */ + private String text = ""; + + /** search mode */ + private String mode = MODE_ALL; + + /** folder lookin mode */ + private String lookin = LOOKIN_ALL; + + /** Space Selector location */ + private NodeRef location = null; + + /** categories to search */ + private NodeRef category = null; + + /** title attribute to search */ + private String title = null; + + /** description attribute to search */ + private String description = null; + + /** created attribute to search from */ + private Date createdDateFrom = null; + + /** created attribute to search to */ + private Date createdDateTo = null; + + /** modified attribute to search from */ + private Date modifiedDateFrom = null; + + /** modified attribute to search to */ + private Date modifiedDateTo = null; + + /** true to search location children as well as location */ + private boolean locationChildren = true; + + /** true to search category children as well as category */ + private boolean categoryChildren = true; + + /** author (creator) attribute to search */ + private String author = null; + + private boolean modifiedDateChecked = false; + private boolean createdDateChecked = false; +} diff --git a/source/java/org/alfresco/web/bean/BrowseBean.java b/source/java/org/alfresco/web/bean/BrowseBean.java new file mode 100644 index 0000000000..999147d0c0 --- /dev/null +++ b/source/java/org/alfresco/web/bean/BrowseBean.java @@ -0,0 +1,1477 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.DownloadContentServlet; +import org.alfresco.web.bean.repository.MapNode; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.NodePropertyResolver; +import org.alfresco.web.bean.repository.QNameNodeMap; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.wizard.NewSpaceWizard; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.Utils.URLMode; +import org.alfresco.web.ui.common.component.IBreadcrumbHandler; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIBreadcrumb; +import org.alfresco.web.ui.common.component.UIModeList; +import org.alfresco.web.ui.common.component.UIStatusMessage; +import org.alfresco.web.ui.common.component.data.UIRichList; +import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler; +import org.alfresco.web.ui.repo.component.UINodeDescendants; +import org.alfresco.web.ui.repo.component.UINodePath; +import org.alfresco.web.ui.repo.component.UISimpleSearch; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; + +/** + * Bean providing properties and behaviour for the main folder/document browse screen and + * search results screens. + * + * @author Kevin Roast + */ +public class BrowseBean implements IContextListener +{ + // ------------------------------------------------------------------------------ + // Construction + + private static final String VIEWMODE_DASHBOARD = "dashboard"; + private static final String PAGE_NAME_BROWSE = "browse"; + + /** + * Default Constructor + */ + public BrowseBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + + initFromClientConfig(); + } + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService The Searcher to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param lockService The Lock Service to set. + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param dictionaryService The DictionaryService to set. + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param fileFolderService The FileFolderService to set. + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * @return Returns the browse View mode. See UIRichList + */ + public String getBrowseViewMode() + { + return this.browseViewMode; + } + + /** + * @param browseViewMode The browse View mode to set. See UIRichList. + */ + public void setBrowseViewMode(String browseViewMode) + { + this.browseViewMode = browseViewMode; + } + + /** + * @return Returns true if dashboard view is available for the current node. + */ + public boolean isDashboardView() + { + return this.dashboardView; + } + + /** + * @param dashboardView The dashboard view mode to set. + */ + public void setDashboardView(boolean dashboardView) + { + this.dashboardView = dashboardView; + if (dashboardView == true) + { + FacesContext fc = FacesContext.getCurrentInstance(); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dashboard"); + } + else + { + navigateBrowseScreen(); + } + } + + /** + * @return Returns the browsePageSize. + */ + public int getBrowsePageSize() + { + return this.browsePageSize; + } + + /** + * @param browsePageSize The browsePageSize to set. + */ + public void setBrowsePageSize(int browsePageSize) + { + this.browsePageSize = browsePageSize; + } + + /** + * @return Returns the minimum length of a valid search string. + */ + public int getMinimumSearchLength() + { + return this.clientConfig.getSearchMinimum(); + } + + /** + * @return Returns the Space Node being used for the current browse screen action. + */ + public Node getActionSpace() + { + return this.actionSpace; + } + + /** + * @param actionSpace Set the Space Node to be used for the current browse screen action. + */ + public void setActionSpace(Node actionSpace) + { + this.actionSpace = actionSpace; + } + + /** + * @return The document node being used for the current operation + */ + public Node getDocument() + { + return this.document; + } + + /** + * @param document The document node to be used for the current operation + */ + public void setDocument(Node document) + { + this.document = document; + } + + /** + * @param contentRichList The contentRichList to set. + */ + public void setContentRichList(UIRichList browseRichList) + { + this.contentRichList = browseRichList; + if (this.contentRichList != null) + { + this.contentRichList.setInitialSortColumn( + this.clientConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); + this.contentRichList.setInitialSortDescending( + this.clientConfig.hasDescendingSort(PAGE_NAME_BROWSE)); + } + } + + /** + * @return Returns the contentRichList. + */ + public UIRichList getContentRichList() + { + return this.contentRichList; + } + + /** + * @param spacesRichList The spacesRichList to set. + */ + public void setSpacesRichList(UIRichList detailsRichList) + { + this.spacesRichList = detailsRichList; + if (this.spacesRichList != null) + { + // set the initial sort column and direction + this.spacesRichList.setInitialSortColumn( + this.clientConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); + this.spacesRichList.setInitialSortDescending( + this.clientConfig.hasDescendingSort(PAGE_NAME_BROWSE)); + } + } + + /** + * @return Returns the spacesRichList. + */ + public UIRichList getSpacesRichList() + { + return this.spacesRichList; + } + + /** + * @return Returns the statusMessage component. + */ + public UIStatusMessage getStatusMessage() + { + return this.statusMessage; + } + + /** + * @param statusMessage The statusMessage component to set. + */ + public void setStatusMessage(UIStatusMessage statusMessage) + { + this.statusMessage = statusMessage; + } + + /** + * @return Returns the deleteMessage. + */ + public String getDeleteMessage() + { + return this.deleteMessage; + } + + /** + * @param deleteMessage The deleteMessage to set. + */ + public void setDeleteMessage(String deleteMessage) + { + this.deleteMessage = deleteMessage; + } + + /** + * Page accessed bean method to get the container nodes currently being browsed + * + * @return List of container Node objects for the current browse location + */ + public List getNodes() + { + // the references to container nodes and content nodes are transient for one use only + // we do this so we only query/search once - as we cannot distinguish between node types + // until after the query. The logic is a bit confusing but otherwise we would need to + // perform the same query or search twice for every screen refresh. + if (this.containerNodes == null) + { + if (this.navigator.getSearchContext() == null) + { + queryBrowseNodes(this.navigator.getCurrentNodeId()); + } + else + { + searchBrowseNodes(this.navigator.getSearchContext()); + } + } + List result = this.containerNodes; + + // we clear the member variable during invalidateComponents() + + return result; + } + + /** + * Page accessed bean method to get the content nodes currently being browsed + * + * @return List of content Node objects for the current browse location + */ + public List getContent() + { + // see comment in getNodes() above for reasoning here + if (this.contentNodes == null) + { + if (this.navigator.getSearchContext() == null) + { + queryBrowseNodes(this.navigator.getCurrentNodeId()); + } + else + { + searchBrowseNodes(this.navigator.getSearchContext()); + } + } + List result = this.contentNodes; + + // we clear the member variable during invalidateComponents() + + return result; + } + + /** + * Setup the additional properties required at data-binding time. + *

+ * These are properties used by components on the page when iterating over the nodes. + * Information such as whether the node is locked, a working copy, download URL etc. + *

+ * We use a set of anonymous inner classes to provide the implemention for the property + * getters. The interfaces are only called when the properties are first required. + * + * @param node MapNode to add the properties too + */ + public void setupDataBindingProperties(MapNode node) + { + // special properties to be used by the value binding components on the page + node.addPropertyResolver("locked", this.resolverlocked); + node.addPropertyResolver("owner", this.resolverOwner); + node.addPropertyResolver("workingCopy", this.resolverWorkingCopy); + node.addPropertyResolver("url", this.resolverUrl); + node.addPropertyResolver("fileType16", this.resolverFileType16); + node.addPropertyResolver("fileType32", this.resolverFileType32); + node.addPropertyResolver("size", this.resolverSize); + node.addPropertyResolver("cancelCheckOut", this.resolverCancelCheckOut); + node.addPropertyResolver("checkIn", this.resolverCheckIn); + node.addPropertyResolver("editLinkType", this.resolverEditLinkType); + node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl); + node.addPropertyResolver("cifsPath", this.resolverCifsPath); + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + invalidateComponents(); + } + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Change the current view mode based on user selection + * + * @param event ActionEvent + */ + public void viewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + + // get the view mode ID + String viewMode = viewList.getValue().toString(); + + if (VIEWMODE_DASHBOARD.equals(viewMode) == false) + { + // set the page size based on the style of display + setBrowsePageSize(this.clientConfig.getDefaultPageSize(PAGE_NAME_BROWSE, + viewMode)); + + if (logger.isDebugEnabled()) + logger.debug("Browse view page size set to: " + getBrowsePageSize()); + + // in case we left for dashboard + if (isDashboardView() == true) + { + setDashboardView(false); + } + + // push the view mode into the lists + setBrowseViewMode(viewMode); + } + else + { + // special case for Dashboard view + setDashboardView(true); + } + } + + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Query a list of nodes for the specified parent node Id + * + * @param parentNodeId Id of the parent node or null for the root node + */ + private void queryBrowseNodes(String parentNodeId) + { + long startTime = 0; + if (logger.isDebugEnabled()) + startTime = System.currentTimeMillis(); + + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + NodeRef parentRef; + if (parentNodeId == null) + { + // no specific parent node specified - use the root node + parentRef = this.nodeService.getRootNode(Repository.getStoreRef()); + } + else + { + // build a NodeRef for the specified Id and our store + parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId); + } + + // TODO: can we improve the Get here with an API call for children of a specific type? + List childRefs = this.nodeService.getChildAssocs(parentRef); + this.containerNodes = new ArrayList(childRefs.size()); + this.contentNodes = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref: childRefs) + { + // create our Node representation from the NodeRef + NodeRef nodeRef = ref.getChildRef(); + + if (this.nodeService.exists(nodeRef)) + { + // find it's type so we can see if it's a node we are interested in + QName type = this.nodeService.getType(nodeRef); + + // make sure the type is defined in the data dictionary + TypeDefinition typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + // look for Space or File nodes + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) == true && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + node.addPropertyResolver("icon", this.resolverSpaceIcon); + + this.containerNodes.add(node); + } + else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + + setupDataBindingProperties(node); + + this.contentNodes.add(node); + } + } + else + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); + } + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + if (logger.isDebugEnabled()) + { + long endTime = System.currentTimeMillis(); + logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); + } + } + + /** + * Search for a list of nodes using the specific search context + * + * @param searchContext To use to perform the search + */ + private void searchBrowseNodes(SearchContext searchContext) + { + long startTime = 0; + if (logger.isDebugEnabled()) + startTime = System.currentTimeMillis(); + + // special exit case for < 3 characters in length + if (searchContext.getText().length() < getMinimumSearchLength()) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), MSG_SEARCH_MINIMUM), + new Object[] {getMinimumSearchLength()})); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + return; + } + + // get the searcher object and perform the search of the root node + String query = searchContext.buildQuery(); + + UserTransaction tx = null; + ResultSet results = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + results = this.searchService.query( + Repository.getStoreRef(), + "lucene", query, null, null); + if (logger.isDebugEnabled()) + logger.debug("Search results returned: " + results.length()); + + // create a list of items from the results + this.containerNodes = new ArrayList(results.length()); + this.contentNodes = new ArrayList(results.length()); + if (results.length() != 0) + { + for (ResultSetRow row: results) + { + NodeRef nodeRef = row.getNodeRef(); + + if (this.nodeService.exists(nodeRef)) + { + // find it's type so we can see if it's a node we are interested in + QName type = this.nodeService.getType(nodeRef); + + // make sure the type is defined in the data dictionary + TypeDefinition typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + // look for Space or File nodes + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + + // construct the path to this Node + node.addPropertyResolver("path", this.resolverPath); + node.addPropertyResolver("displayPath", this.resolverDisplayPath); + node.addPropertyResolver("icon", this.resolverSpaceIcon); + + this.containerNodes.add(node); + } + else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + + setupDataBindingProperties(node); + + // construct the path to this Node + node.addPropertyResolver("path", this.resolverPath); + node.addPropertyResolver("displayPath", this.resolverDisplayPath); + + this.contentNodes.add(node); + } + } + else + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); + } + } + else + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Missing object returned from search indexes: id = " + nodeRef + " search query: " + query); + } + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + logger.info("Search failed for: " + query); + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_SEARCH), new Object[] {err.getMessage()}), err ); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + finally + { + if (results != null) + { + results.close(); + } + } + + if (logger.isDebugEnabled()) + { + long endTime = System.currentTimeMillis(); + logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); + } + } + + + // ------------------------------------------------------------------------------ + // Property Resolvers + + public NodePropertyResolver resolverlocked = new NodePropertyResolver() { + public Object get(Node node) { + return Repository.isNodeLocked(node, lockService); + } + }; + + public NodePropertyResolver resolverOwner = new NodePropertyResolver() { + public Object get(Node node) { + return Repository.isNodeOwner(node, lockService); + } + }; + + public NodePropertyResolver resolverCancelCheckOut = new NodePropertyResolver() { + public Object get(Node node) { + return node.hasAspect(ContentModel.ASPECT_WORKING_COPY) && node.hasPermission(PermissionService.CANCEL_CHECK_OUT); + } + }; + + public NodePropertyResolver resolverCheckIn = new NodePropertyResolver() { + public Object get(Node node) { + return node.hasAspect(ContentModel.ASPECT_WORKING_COPY) && node.hasPermission(PermissionService.CHECK_IN); + } + }; + + public NodePropertyResolver resolverWorkingCopy = new NodePropertyResolver() { + public Object get(Node node) { + return node.hasAspect(ContentModel.ASPECT_WORKING_COPY); + } + }; + + public NodePropertyResolver resolverDownload = new NodePropertyResolver() { + public Object get(Node node) { + return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName()); + } + }; + + public NodePropertyResolver resolverUrl = new NodePropertyResolver() { + public Object get(Node node) { + return DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName()); + } + }; + + public NodePropertyResolver resolverWebdavUrl = new NodePropertyResolver() { + public Object get(Node node) + { + return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.WEBDAV); + } + }; + + public NodePropertyResolver resolverCifsPath = new NodePropertyResolver() { + public Object get(Node node) + { + return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.CIFS); + } + }; + + public NodePropertyResolver resolverFileType16 = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.getFileTypeImage(node.getName(), true); + } + }; + + public NodePropertyResolver resolverFileType32 = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.getFileTypeImage(node.getName(), false); + } + }; + + public NodePropertyResolver resolverPath = new NodePropertyResolver() { + public Object get(Node node) { + return nodeService.getPath(node.getNodeRef()); + } + }; + + public NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() { + public Object get(Node node) { + // TODO: replace this with a method that shows the full display name - not QNames + return Repository.getDisplayPath( (Path)node.getProperties().get("path") ); + } + }; + + public NodePropertyResolver resolverSpaceIcon = new NodePropertyResolver() { + public Object get(Node node) { + QNameNodeMap props = (QNameNodeMap)node.getProperties(); + String icon = (String)props.getRaw("app:icon"); + return (icon != null ? icon : NewSpaceWizard.SPACE_ICON_DEFAULT); + } + }; + + public NodePropertyResolver resolverMimetype = new NodePropertyResolver() { + public Object get(Node node) { + ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); + return (content != null ? content.getMimetype() : null); + } + }; + + public NodePropertyResolver resolverSize = new NodePropertyResolver() { + public Object get(Node node) { + ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); + return (content != null ? new Long(content.getSize()) : null); + } + }; + + public NodePropertyResolver resolverEditLinkType = new NodePropertyResolver() { + public Object get(Node node) + { + String editLinkType = "http"; + + // if the node is inline editable, the default http behaviour should + // always be used otherwise the configured approach is used + if (node.hasAspect(ContentModel.ASPECT_INLINEEDITABLE) == false) + { + editLinkType = clientConfig.getEditLinkType(); + if (editLinkType == null) + { + editLinkType = "http"; + } + } + + return editLinkType; + } + }; + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Action called from the Simple Search component. + * Sets up the SearchContext object with the values from the simple search menu. + */ + public void search(ActionEvent event) + { + // setup the search text string on the top-level navigation handler + UISimpleSearch search = (UISimpleSearch)event.getComponent(); + this.navigator.setSearchContext(search.getSearchContext()); + + navigateBrowseScreen(); + } + + /** + * Action called to Close the search dialog by returning to the last view node Id + */ + public void closeSearch(ActionEvent event) + { + // set the current node Id ready for page refresh + this.navigator.setCurrentNodeId( this.navigator.getCurrentNodeId() ); + } + + /** + * Action called when a folder space is clicked. + * Navigate into the space. + */ + public void clickSpace(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + try + { + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + + // refresh UI based on node selection + updateUILocation(ref); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + } + + /** + * Handler called when a path element is clicked - navigate to the appropriate Space + */ + public void clickSpacePath(ActionEvent event) + { + UINodePath.PathElementEvent pathEvent = (UINodePath.PathElementEvent)event; + NodeRef ref = pathEvent.NodeReference; + try + { + // refresh UI based on node selection + this.updateUILocation(ref); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {ref.getId()}) ); + } + } + + /** + * Action called when a folders direct descendant (in the 'list' browse mode) is clicked. + * Navigate into the the descendant space. + */ + public void clickDescendantSpace(ActionEvent event) + { + UINodeDescendants.NodeSelectedEvent nodeEvent = (UINodeDescendants.NodeSelectedEvent)event; + NodeRef nodeRef = nodeEvent.NodeReference; + if (nodeRef == null) + { + throw new IllegalStateException("NodeRef returned from UINodeDescendants.NodeSelectedEvent cannot be null!"); + } + + if (logger.isDebugEnabled()) + logger.debug("Selected noderef Id: " + nodeRef.getId()); + + try + { + // user can either select a descendant of a node display on the page which means we + // must add the it's parent and itself to the breadcrumb + List location = this.navigator.getLocation(); + ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef); + + if (logger.isDebugEnabled()) + { + logger.debug("Selected item getPrimaryParent().getChildRef() noderef Id: " + parentAssocRef.getChildRef().getId()); + logger.debug("Selected item getPrimaryParent().getParentRef() noderef Id: " + parentAssocRef.getParentRef().getId()); + logger.debug("Current value getNavigator().getCurrentNodeId() noderef Id: " + this.navigator.getCurrentNodeId()); + } + + if (nodeEvent.IsParent == false) + { + // a descendant of the displayed node was selected + // first refresh based on the parent and add to the breadcrumb + updateUILocation(parentAssocRef.getParentRef()); + + // now add our selected node + updateUILocation(nodeRef); + } + else + { + // else the parent ellipses i.e. the displayed node was selected + updateUILocation(nodeRef); + } + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {nodeRef.getId()}) ); + } + } + + /** + * Action event called by all Browse actions that need to setup a Space context + * before an action page/wizard is called. The context will be a Node in setActionSpace() which + * can be retrieved on the action page from BrowseBean.getActionSpace(). + * + * @param event ActionEvent + */ + public void setupSpaceAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + setupSpaceAction(id, true); + } + + /** + * Public helper to setup action pages with Space context + * + * @param id of the Space node to setup context for + */ + public void setupSpaceAction(String id, boolean invalidate) + { + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current space to: " + id); + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // resolve icon in-case one has not been set + node.addPropertyResolver("icon", this.resolverSpaceIcon); + + // prepare a node for the action context + setActionSpace(node); + + // setup the dispatch context in case it is required + this.navigator.setupDispatchContext(node); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + else + { + setActionSpace(null); + } + + // clear the UI state in preparation for finishing the next action + if (invalidate == true) + { + // use the context service to notify all registered beans + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + } + + /** + * Acrtion event called by Delete Space actions. We setup the action space as normal, then prepare + * any special case message string to be shown to the user if they are trying to delete specific spaces. + */ + public void setupDeleteAction(ActionEvent event) + { + String message = null; + + setupSpaceAction(event); + + Node node = getActionSpace(); + if (node != null) + { + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + if (node.getNodeRef().equals(companyRootRef)) + { + message = Application.getMessage(FacesContext.getCurrentInstance(), MSG_DELETE_COMPANYROOT); + } + } + + setDeleteMessage(message); + } + + /** + * Action event called by all actions that need to setup a Content Document context on the + * BrowseBean before an action page/wizard is called. The context will be a Node in + * setDocument() which can be retrieved on the action page from BrowseBean.getDocument(). + */ + public void setupContentAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + setupContentAction(params.get("id"), true); + } + + /** + * Public helper to setup action pages with content context + * + * @param id of the content node to setup context for + */ + public void setupContentAction(String id, boolean invalidate) + { + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current document to: " + id); + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // store the URL to for downloading the content + node.addPropertyResolver("url", this.resolverDownload); + node.addPropertyResolver("fileType32", this.resolverFileType32); + node.addPropertyResolver("mimetype", this.resolverMimetype); + node.addPropertyResolver("size", this.resolverSize); + node.addPropertyResolver("cancelCheckOut", this.resolverCancelCheckOut); + node.addPropertyResolver("checkIn", this.resolverCheckIn); + + // get hold of the DocumentDetailsBean and reset it + DocumentDetailsBean docDetails = (DocumentDetailsBean)FacesContext.getCurrentInstance(). + getExternalContext().getSessionMap().get("DocumentDetailsBean"); + if (docDetails != null) + { + docDetails.reset(); + } + + // remember the document + setDocument(node); + + // setup the dispatch context in case it is required + this.navigator.setupDispatchContext(node); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + else + { + setDocument(null); + } + + // clear the UI state in preparation for finishing the next action + if (invalidate == true) + { + invalidateComponents(); + } + } + + /** + * Handler called upon the completion of the Delete Space page + * + * @return outcome + */ + public String deleteSpaceOK() + { + String outcome = null; + + Node node = getActionSpace(); + if (node != null) + { + try + { + if (logger.isDebugEnabled()) + logger.debug("Trying to delete space: " + node.getId()); + + this.nodeService.deleteNode(node.getNodeRef()); + + // remove this node from the breadcrumb if required + List location = navigator.getLocation(); + IBreadcrumbHandler handler = location.get(location.size() - 1); + if (handler instanceof BrowseBreadcrumbHandler) + { + // see if the current breadcrumb location is our node + if ( ((BrowseBreadcrumbHandler)handler).getNodeRef().equals(node.getNodeRef()) == true ) + { + location.remove(location.size() - 1); + + // now work out which node to set the list to refresh against + if (location.size() != 0) + { + handler = location.get(location.size() - 1); + if (handler instanceof BrowseBreadcrumbHandler) + { + // change the current node Id + navigator.setCurrentNodeId(((BrowseBreadcrumbHandler)handler).getNodeRef().getId()); + } + else + { + // TODO: shouldn't do this - but for now the user home dir is the root! + navigator.setCurrentNodeId(Application.getCurrentUser(FacesContext.getCurrentInstance()).getHomeSpaceId()); + } + } + } + } + + // add a message to inform the user that the delete was OK + String statusMsg = MessageFormat.format( + Application.getMessage(FacesContext.getCurrentInstance(), "status_space_deleted"), + new Object[]{node.getName()}); + Utils.addStatusMessage(FacesMessage.SEVERITY_INFO, statusMsg); + + // clear action context + setActionSpace(null); + + // setting the outcome will show the browse view again + outcome = "browse"; + } + catch (Throwable err) + { + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_DELETE_SPACE) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: deleteSpaceOK called without a current Space!"); + } + + return outcome; + } + + /** + * Handler called upon the completion of the Delete File page + * + * @return outcome + */ + public String deleteFileOK() + { + String outcome = null; + + Node node = getDocument(); + if (node != null) + { + try + { + if (logger.isDebugEnabled()) + logger.debug("Trying to delete content node: " + node.getId()); + + this.nodeService.deleteNode(node.getNodeRef()); + + // clear action context + setDocument(null); + + // setting the outcome will show the browse view again + outcome = "browse"; + } + catch (Throwable err) + { + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_DELETE_FILE) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: deleteFileOK called without a current Document!"); + } + + return outcome; + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Initialise default values from client configuration + */ + private void initFromClientConfig() + { + this.clientConfig = (ClientConfigElement)Application.getConfigService( + FacesContext.getCurrentInstance()).getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + + this.browseViewMode = clientConfig.getDefaultView(PAGE_NAME_BROWSE); + this.browsePageSize = clientConfig.getDefaultPageSize(PAGE_NAME_BROWSE, + this.browseViewMode); + } + + /** + * Refresh the UI after a Space selection change. Adds the selected space to the breadcrumb + * location path and also updates the list components in the UI. + * + * @param ref NodeRef of the selected space + */ + /*package*/ void updateUILocation(NodeRef ref) + { + // get the current breadcrumb location and append a new handler to it + // our handler know the ID of the selected node and the display label for it + List location = this.navigator.getLocation(); + if (location.size() != 0) + { + // attempt to find the ID - if it's already in the breadcrumb then we + // navigate directly to that node - rather than add duplication to the breadcrumb path + boolean foundNode = false; + for (int i=0; i newLocation = new ArrayList(i+1); + //newLocation.addAll(location.subList(0, i + 1)); + //this.navigator.setLocation(newLocation); + // TODO: but instead for now we do this: + int count = location.size(); + for (int n=i+1; n containerNodes = null; + private List contentNodes = null; + + /** The current space and it's properties - if any */ + private Node actionSpace; + + /** The current document */ + private Node document; + + /** Special message to display when user deleting certain folders e.g. Company Home */ + private String deleteMessage; + + /** The current browse view mode - set to a well known IRichListRenderer identifier */ + private String browseViewMode; + + /** The current browse view page size */ + private int browsePageSize; + + /** True if current space has a dashboard (template) view available */ + private boolean dashboardView; +} diff --git a/source/java/org/alfresco/web/bean/CategoriesBean.java b/source/java/org/alfresco/web/bean/CategoriesBean.java new file mode 100644 index 0000000000..31ddaade30 --- /dev/null +++ b/source/java/org/alfresco/web/bean/CategoriesBean.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.cmr.search.CategoryService.Depth; +import org.alfresco.service.cmr.search.CategoryService.Mode; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.IBreadcrumbHandler; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIBreadcrumb; +import org.alfresco.web.ui.common.component.UIModeList; +import org.alfresco.web.ui.common.component.data.UIRichList; +import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler; +import org.apache.log4j.Logger; + +/** + * Backing Bean for the Category Management pages. + * + * @author Kevin Roast + */ +public class CategoriesBean implements IContextListener +{ + private static final String DEFAULT_OUTCOME = "finish"; + + private static final String MSG_CATEGORIES = "categories"; + + private static Logger logger = Logger.getLogger(CategoriesBean.class); + + /** The NodeService to be used by the bean */ + private NodeService nodeService; + + private CategoryService categoryService; + + /** Component references */ + private UIRichList categoriesRichList; + + /** Currently visible category Node*/ + private Node category = null; + + /** Current category ref */ + private NodeRef categoryRef = null; + + /** Action category node */ + private Node actionCategory = null; + + /** Member Count of the linked items of a category */ + private Integer members = null; + + /** Dialog properties */ + private String name = null; + private String description = null; + + /** RichList view mode */ + private String viewMode = "icons"; + + /** Category path breadcrumb location */ + private List location = null; + + + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public CategoriesBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + } + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param categoryService The CategoryService to set. + */ + public void setCategoryService(CategoryService categoryService) + { + this.categoryService = categoryService; + } + + /** + * @param list The categories RichList to set. + */ + public void setCategoriesRichList(UIRichList list) + { + this.categoriesRichList = list; + } + + /** + * @return Returns the categories RichList to set. + */ + public UIRichList getCategoriesRichList() + { + return this.categoriesRichList; + } + + /** + * @return Returns the description. + */ + public String getDescription() + { + return this.description; + } + + /** + * @param description The description to set. + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * @return Returns the viewMode. + */ + public String getViewMode() + { + return this.viewMode; + } + + /** + * @param viewMode The viewMode to set. + */ + public void setViewMode(String viewMode) + { + this.viewMode = viewMode; + } + + /** + * @return Returns the name. + */ + public String getName() + { + return this.name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @return Returns the members count for current action category. + */ + public int getMembers() + { + return (this.members != null ? this.members.intValue() : 0); + } + + /** + * @return Returns the Node being used for the current action screen. + */ + public Node getActionCategory() + { + return this.actionCategory; + } + + /** + * @param actionSpace Set the Node to be used for the current category screen action. + */ + public void setActionCategory(Node node) + { + this.actionCategory = node; + + if (node != null) + { + // setup form properties + this.name = node.getName(); + this.description = (String)node.getProperties().get(ContentModel.PROP_DESCRIPTION); + this.members = this.categoryService.getChildren(node.getNodeRef(), Mode.MEMBERS, Depth.ANY).size(); + } + else + { + this.name = null; + this.description = null; + this.members = 0; + } + } + + /** + * @return The currently displayed category as a Node or null if at the root. + */ + public Node getCurrentCategory() + { + if (this.category == null) + { + if (this.categoryRef != null) + { + this.category = new Node(this.categoryRef); + } + } + + return this.category; + } + + /** + * @return The ID of the currently displayed category or null if at the root. + */ + public String getCurrentCategoryId() + { + if (this.categoryRef != null) + { + return categoryRef.getId(); + } + else + { + return null; + } + } + + /** + * Set the current category node. + *

+ * Setting this value causes the UI to update and display the specified node as current. + * + * @param ref The current category node. + */ + public void setCurrentCategory(NodeRef ref) + { + if (logger.isDebugEnabled()) + logger.debug("Setting current category: " + ref); + + // set the current NodeRef for our UI context operations + this.categoryRef = ref; + + // clear current node context + this.category = null; + + // inform that the UI needs updating after this change + contextUpdated(); + } + + /** + * @return Breadcrumb location list + */ + public List getLocation() + { + if (this.location == null) + { + List loc = new ArrayList(8); + loc.add(new CategoryBreadcrumbHandler(null, + Application.getMessage(FacesContext.getCurrentInstance(), MSG_CATEGORIES))); + + this.location = loc; + } + return this.location; + } + + /** + * @param location Breadcrumb location list + */ + public void setLocation(List location) + { + this.location = location; + } + + /** + * @return The list of categories Nodes to display. Returns the list root categories or the + * list of sub-categories for the current category if set. + */ + public List getCategories() + { + List categories; + + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + Collection refs; + if (this.categoryRef == null) + { + // root categories + refs = this.categoryService.getCategories(Repository.getStoreRef(), ContentModel.ASPECT_GEN_CLASSIFIABLE, Depth.IMMEDIATE); + } + else + { + // sub-categories of an existing category + refs = this.categoryService.getChildren(this.categoryRef, Mode.SUB_CATEGORIES, Depth.IMMEDIATE); + } + categories = new ArrayList(refs.size()); + for (ChildAssociationRef child : refs) + { + Node categoryNode = new Node(child.getChildRef()); + // force early props init within transaction + categoryNode.getProperties(); + categories.add(categoryNode); + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); + categories = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + categories = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + return categories; + } + + /** + * Set the Category to be used for next action dialog + */ + public void setupCategoryAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current Category to: " + id); + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // prepare a node for the action context + setActionCategory(node); + + // clear datalist cache ready from return from action dialog + contextUpdated(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + } + + /** + * Clear the category action context - e.g. ready for a Create operation + */ + public void clearCategoryAction(ActionEvent event) + { + setActionCategory(null); + + // clear datalist cache ready from return from action dialog + contextUpdated(); + } + + /** + * Action called when a category folder is clicked. + * Navigate into the category. + */ + public void clickCategory(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + try + { + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + + // refresh UI based on node selection + updateUILocation(ref); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + } + + /** + * Action handler called on Create Category finish button click. + */ + public String finishCreate() + { + String outcome = DEFAULT_OUTCOME; + + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // create category using categoryservice + NodeRef ref; + if (categoryRef == null) + { + ref = this.categoryService.createRootCategory(Repository.getStoreRef(), ContentModel.ASPECT_GEN_CLASSIFIABLE, this.name); + } + else + { + ref = this.categoryService.createCategory(categoryRef, this.name); + } + + // apply the titled aspect - for description + Map titledProps = new HashMap(1, 1.0f); + titledProps.put(ContentModel.PROP_DESCRIPTION, this.description); + this.nodeService.addAspect(ref, ContentModel.ASPECT_TITLED, titledProps); + + // commit the transaction + tx.commit(); + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + outcome = null; + } + + return outcome; + } + + /** + * Action handler called on Edit Category finish button click. + */ + public String finishEdit() + { + String outcome = DEFAULT_OUTCOME; + + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // update the category node + NodeRef nodeRef = getActionCategory().getNodeRef(); + this.nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, this.name); + + // apply the titled aspect - for description + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TITLED) == false) + { + Map titledProps = new HashMap(1, 1.0f); + titledProps.put(ContentModel.PROP_DESCRIPTION, this.description); + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, titledProps); + } + else + { + this.nodeService.setProperty(nodeRef, ContentModel.PROP_DESCRIPTION, this.description); + } + + // commit the transaction + tx.commit(); + + // edit the node in the breadcrumb if required + List location = getLocation(); + IBreadcrumbHandler handler = location.get(location.size() - 1); + + // see if the current breadcrumb location is our node + if ( nodeRef.equals(((IRepoBreadcrumbHandler)handler).getNodeRef()) ) + { + // and update with the modified node details + IBreadcrumbHandler newHandler = new CategoryBreadcrumbHandler( + nodeRef, Repository.getNameForNode(nodeService, nodeRef)); + location.set(location.size() - 1, newHandler); + } + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + outcome = null; + } + + return outcome; + } + + /** + * Action handler called on Delete Category finish button click. + */ + public String finishDelete() + { + String outcome = DEFAULT_OUTCOME; + + if (getActionCategory() != null) + { + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // delete the category node using the nodeservice + NodeRef nodeRef = getActionCategory().getNodeRef(); + this.categoryService.deleteCategory(nodeRef); + + // commit the transaction + tx.commit(); + + // remove this node from the breadcrumb if required + List location = getLocation(); + IBreadcrumbHandler handler = location.get(location.size() - 1); + + // see if the current breadcrumb location is our node + if ( nodeRef.equals(((IRepoBreadcrumbHandler)handler).getNodeRef()) ) + { + location.remove(location.size() - 1); + + // now work out which node to set the list to refresh against + if (location.size() != 0) + { + handler = location.get(location.size() - 1); + this.setCurrentCategory(((IRepoBreadcrumbHandler)handler).getNodeRef()); + } + } + + // clear action context + setActionCategory(null); + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + outcome = null; + } + } + + return outcome; + } + + /** + * Change the current view mode based on user selection + * + * @param event ActionEvent + */ + public void viewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + + // get the view mode ID + setViewMode(viewList.getValue().toString()); + } + + /** + * Update the breadcrumb with the clicked category location + */ + private void updateUILocation(NodeRef ref) + { + String name = Repository.getNameForNode(this.nodeService, ref); + this.location.add(new CategoryBreadcrumbHandler(ref, name)); + this.setCurrentCategory(ref); + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + if (logger.isDebugEnabled()) + logger.debug("Invalidating Category Management Components..."); + + // force a requery of the current category ref properties + this.category = null; + + // force a requery of the richlist dataset + this.categoriesRichList.setValue(null); + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class to handle breadcrumb interaction for Categories pages + */ + private class CategoryBreadcrumbHandler implements IRepoBreadcrumbHandler + { + private static final long serialVersionUID = 3831234653171036630L; + + /** + * Constructor + * + * @param NodeRef The NodeRef for this browse navigation element + * @param label Element label + */ + public CategoryBreadcrumbHandler(NodeRef nodeRef, String label) + { + this.label = label; + this.nodeRef = nodeRef; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return this.label; + } + + /** + * @see org.alfresco.web.ui.common.component.IBreadcrumbHandler#navigationOutcome(org.alfresco.web.ui.common.component.UIBreadcrumb) + */ + public String navigationOutcome(UIBreadcrumb breadcrumb) + { + // All category breadcrumb elements relate to a Categiry Node Id + // when selected we set the current category Id and return + setCurrentCategory(this.nodeRef); + setLocation( (List)breadcrumb.getValue() ); + + return null; + } + + public NodeRef getNodeRef() + { + return this.nodeRef; + } + + private NodeRef nodeRef; + private String label; + } +} diff --git a/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java b/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java new file mode 100644 index 0000000000..27546a6a5c --- /dev/null +++ b/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java @@ -0,0 +1,951 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.File; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.DownloadContentServlet; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Kevin Roast + */ +public class CheckinCheckoutBean +{ + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @return Returns the BrowseBean. + */ + public BrowseBean getBrowseBean() + { + return this.browseBean; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @return Returns the NodeService. + */ + public NodeService getNodeService() + { + return this.nodeService; + } + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @return Returns the VersionOperationsService. + */ + public CheckOutCheckInService getVersionOperationsService() + { + return this.versionOperationsService; + } + + /** + * @param versionOperationsService The VersionOperationsService to set. + */ + public void setVersionOperationsService(CheckOutCheckInService versionOperationsService) + { + this.versionOperationsService = versionOperationsService; + } + + /** + * @return Returns the ContentService. + */ + public ContentService getContentService() + { + return this.contentService; + } + + /** + * @param contentService The ContentService to set. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @return The document node being used for the current operation + */ + public Node getDocument() + { + return this.document; + } + + /** + * @param document The document node to be used for the current operation + */ + public void setDocument(Node document) + { + this.document = document; + } + + /** + * @return Returns the working copy Document. + */ + public Node getWorkingDocument() + { + return this.workingDocument; + } + + /** + * @param workingDocument The working copy Document to set. + */ + public void setWorkingDocument(Node workingDocument) + { + this.workingDocument = workingDocument; + } + + /** + * Determines whether the document being checked in has + * the versionable aspect applied + * + * @return true if the versionable aspect is applied + */ + public boolean isVersionable() + { + return getDocument().hasAspect(ContentModel.ASPECT_VERSIONABLE); + } + + /** + * @param keepCheckedOut The keepCheckedOut to set. + */ + public void setKeepCheckedOut(boolean keepCheckedOut) + { + this.keepCheckedOut = keepCheckedOut; + } + + /** + * @return Returns the keepCheckedOut. + */ + public boolean getKeepCheckedOut() + { + return this.keepCheckedOut; + } + + /** + * @param minorChange The minorChange to set. + */ + public void setMinorChange(boolean minorChange) + { + this.minorChange = minorChange; + } + + /** + * @return Returns the minorChange flag. + */ + public boolean getMinorChange() + { + return this.minorChange; + } + + /** + * @return Returns the version history notes. + */ + public String getVersionNotes() + { + return this.versionNotes; + } + + /** + * @param versionNotes The version history notes to set. + */ + public void setVersionNotes(String versionNotes) + { + this.versionNotes = versionNotes; + } + + /** + * @return Returns the selected Space Id. + */ + public NodeRef getSelectedSpaceId() + { + return this.selectedSpaceId; + } + + /** + * @param selectedSpaceId The selected Space Id to set. + */ + public void setSelectedSpaceId(NodeRef selectedSpaceId) + { + this.selectedSpaceId = selectedSpaceId; + } + + /** + * @return Returns the copy location. Either the current or other space. + */ + public String getCopyLocation() + { + if (this.fileName != null) + { + return CheckinCheckoutBean.COPYLOCATION_OTHER; + } + else + { + return this.copyLocation; + } + } + + /** + * @param copyLocation The copy location. Either the current or other space. + */ + public void setCopyLocation(String copyLocation) + { + this.copyLocation = copyLocation; + } + + /** + * @return Returns the message to display when a file has been uploaded + */ + public String getFileUploadSuccessMsg() + { + String msg = Application.getMessage(FacesContext.getCurrentInstance(), "file_upload_success"); + return MessageFormat.format(msg, new Object[] {getFileName()}); + } + + /** + * @return Returns the name of the file + */ + public String getFileName() + { + // try and retrieve the file and filename from the file upload bean + // representing the file we previously uploaded. + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + this.file = fileBean.getFile(); + this.fileName = fileBean.getFileName(); + } + + return this.fileName; + } + + /** + * @param fileName The name of the file + */ + public void setFileName(String fileName) + { + this.fileName = fileName; + + // we also need to keep the file upload bean in sync + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + fileBean.setFileName(this.fileName); + } + } + + /** + * @return Returns the document content used for HTML in-line editing. + */ + public String getDocumentContent() + { + return this.documentContent; + } + + /** + * @param documentContent The document content for HTML in-line editing. + */ + public void setDocumentContent(String documentContent) + { + this.documentContent = documentContent; + } + + /** + * @return Returns output from the in-line editor page. + */ + public String getEditorOutput() + { + return this.editorOutput; + } + + /** + * @param editorOutput The output from the in-line editor page + */ + public void setEditorOutput(String editorOutput) + { + this.editorOutput = editorOutput; + } + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Action event called by all actions that need to setup a Content Document context on the + * CheckinCheckoutBean before an action page/wizard is called. The context will be a Node in + * setDocument() which can be retrieved on action pages via getDocument(). + * + * @param event ActionEvent + */ + public void setupContentAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + setupContentDocument(id); + } + else + { + setDocument(null); + } + + clearUpload(); + } + + /** + * Setup a content document node context + * + * @param id GUID of the node to setup as the content document context + * + * @return The Node + */ + private Node setupContentDocument(String id) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current document to: " + id); + + Node node = null; + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + node = new Node(ref); + + // create content URL to the content download servlet with ID and expected filename + // the myfile part will be ignored by the servlet but gives the browser a hint + String url = DownloadContentServlet.generateDownloadURL(ref, node.getName()); + node.getProperties().put("url", url); + node.getProperties().put("workingCopy", node.hasAspect(ContentModel.ASPECT_WORKING_COPY)); + node.getProperties().put("fileType32", Utils.getFileTypeImage(node.getName(), false)); + + // remember the document + setDocument(node); + + // refresh the UI, calling this method now is fine as it basically makes sure certain + // beans clear the state - so when we finish here other beans will have been reset + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + + return node; + } + + /** + * Action called upon completion of the Check Out file page + */ + public String checkoutFile() + { + String outcome = null; + + UserTransaction tx = null; + + Node node = getDocument(); + if (node != null) + { + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to checkout content node Id: " + node.getId()); + + // checkout the node content to create a working copy + if (logger.isDebugEnabled()) + { + logger.debug("Checkout copy location: " + getCopyLocation()); + logger.debug("Selected Space Id: " + this.selectedSpaceId); + } + NodeRef workingCopyRef; + if (getCopyLocation().equals(COPYLOCATION_OTHER) && this.selectedSpaceId != null) + { + // checkout to a arbituary parent Space + NodeRef destRef = this.selectedSpaceId; + + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destRef); + workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef(), + destRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); + } + else + { + workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef()); + } + + // set the working copy Node instance + Node workingCopy = new Node(workingCopyRef); + setWorkingDocument(workingCopy); + + // create content URL to the content download servlet with ID and expected filename + // the myfile part will be ignored by the servlet but gives the browser a hint + String url = DownloadContentServlet.generateDownloadURL(workingCopyRef, workingCopy.getName()); + + workingCopy.getProperties().put("url", url); + workingCopy.getProperties().put("fileType32", Utils.getFileTypeImage(workingCopy.getName(), false)); + + // commit the transaction + tx.commit(); + + // show the page that display the checkout link + outcome = "checkoutFileLink"; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_CHECKOUT) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: checkoutFile called without a current Document!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Check Out file Link download page + */ + public String checkoutFileOK() + { + String outcome = null; + + Node node = getWorkingDocument(); + if (node != null) + { + // clean up and clear action context + clearUpload(); + setDocument(null); + setWorkingDocument(null); + + outcome = "browse"; + } + else + { + logger.warn("WARNING: checkoutFileOK called without a current WorkingDocument!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Edit File download page + */ + public String editFileOK() + { + String outcome = null; + + Node node = getDocument(); + if (node != null) + { + // clean up and clear action context + clearUpload(); + setDocument(null); + setWorkingDocument(null); + + outcome = "browse"; + } + else + { + logger.warn("WARNING: editFileOK called without a current Document!"); + } + + return outcome; + } + + /** + * Action handler called to calculate which editing screen to display based on the mimetype + * of a document. If appropriate, the in-line editing screen will be shown. + */ + public void editFile(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + Node node = setupContentDocument(id); + + // detect the inline editing aspect to see which edit mode to use + if (node.hasAspect(ContentModel.ASPECT_INLINEEDITABLE) && + node.getProperties().get(ContentModel.PROP_EDITINLINE) != null && + ((Boolean)node.getProperties().get(ContentModel.PROP_EDITINLINE)).booleanValue() == true) + { + // retrieve the content reader for this node + ContentReader reader = getContentService().getReader(node.getNodeRef(), ContentModel.PROP_CONTENT); + String mimetype = reader.getMimetype(); + + // calculate which editor screen to display + if (MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(mimetype) || + MimetypeMap.MIMETYPE_XML.equals(mimetype) || + MimetypeMap.MIMETYPE_TEXT_CSS.equals(mimetype)) + { + // make content available to the editing screen + if (reader != null) + { + setEditorOutput(reader.getContentString()); + } + else + { + setEditorOutput(""); + } + + // navigate to appropriate screen + FacesContext fc = FacesContext.getCurrentInstance(); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "editTextInline"); + } + else + { + // make content available to the editing screen + if (reader != null) + { + setDocumentContent(reader.getContentString()); + } + else + { + setDocumentContent(""); + } + setEditorOutput(null); + + // navigate to appropriate screen + FacesContext fc = FacesContext.getCurrentInstance(); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "editHtmlInline"); + } + } + else + { + // normal downloadable document + FacesContext fc = FacesContext.getCurrentInstance(); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "editFile"); + } + } + } + + /** + * Action handler called to set the content of a node from an inline editing page. + */ + public String editInlineOK() + { + String outcome = null; + + UserTransaction tx = null; + + Node node = getDocument(); + if (node != null) + { + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to update content node Id: " + node.getId()); + + // get an updating writer that we can use to modify the content on the current node + ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.putContent(this.editorOutput); + + // commit the transaction + tx.commit(); + + // clean up and clear action context + clearUpload(); + setDocument(null); + setDocumentContent(null); + setEditorOutput(null); + + outcome = "browse"; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE) + err.getMessage()); + } + } + else + { + logger.warn("WARNING: editInlineOK called without a current Document!"); + } + + return outcome; + } + + /** + * Action to undo the checkout of a document just checked out from the checkout screen. + */ + public String undoCheckout() + { + String outcome = null; + + Node node = getWorkingDocument(); + if (node != null) + { + try + { + // try to cancel checkout of the working copy + this.versionOperationsService.cancelCheckout(node.getNodeRef()); + + clearUpload(); + + outcome = "browse"; + } + catch (Throwable err) + { + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_CANCELCHECKOUT) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: undoCheckout called without a current WorkingDocument!"); + } + + return outcome; + } + + /** + * Action to undo the checkout of a locked document. This document may either by the original copy + * or the working copy node. Therefore calculate which it is, if the working copy is found then + * we simply cancel checkout on that document. If the original copy is found then we need to find + * the appropriate working copy and perform the action on that node. + */ + public String undoCheckoutFile() + { + String outcome = null; + + Node node = getDocument(); + if (node != null) + { + try + { + if (node.hasAspect(ContentModel.ASPECT_WORKING_COPY)) + { + this.versionOperationsService.cancelCheckout(node.getNodeRef()); + } + else if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) + { + // TODO: find the working copy for this document and cancel the checkout on it + // is this possible? as currently only the workingcopy aspect has the copyReference + // attribute - this means we cannot find out where the copy is to cancel it! + // can we construct an XPath node lookup? + throw new RuntimeException("NOT IMPLEMENTED"); + } + else + { + throw new IllegalStateException("Node supplied for undo checkout has neither Working Copy or Locked aspect!"); + } + + clearUpload(); + + outcome = "browse"; + } + catch (Throwable err) + { + Utils.addErrorMessage(MSG_ERROR_CANCELCHECKOUT + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: undoCheckout called without a current WorkingDocument!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Check In file page + */ + public String checkinFileOK() + { + String outcome = null; + + UserTransaction tx = null; + + // NOTE: for checkin the document node _is_ the working document! + Node node = getDocument(); + if (node != null && (getCopyLocation().equals(COPYLOCATION_CURRENT) || this.getFileName() != null)) + { + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to checkin content node Id: " + node.getId()); + + // we can either checkin the content from the current working copy node + // which would have been previously updated by the user + String contentUrl; + String mimetype; + if (getCopyLocation().equals(COPYLOCATION_CURRENT)) + { + ContentData contentData = (ContentData) node.getProperties().get(ContentModel.PROP_CONTENT); + contentUrl = (contentData == null ? null : contentData.getContentUrl()); + } + // or specify a specific file as the content instead + else + { + // add the content to an anonymous but permanent writer location + // we can then retrieve the URL to the content to to be set on the node during checkin + ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, false); + // TODO: Adjust the mimetype + writer.putContent(this.file); + contentUrl = writer.getContentUrl(); + } + + if (contentUrl == null || contentUrl.length() == 0) + { + throw new IllegalStateException("Content URL is empty for specified working copy content node!"); + } + + // add version history text to props + Map props = new HashMap(1, 1.0f); + props.put(Version.PROP_DESCRIPTION, this.versionNotes); + // set the flag for minor or major change + if (this.minorChange) + { + props.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); + } + else + { + props.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); + } + + NodeRef originalDoc = this.versionOperationsService.checkin( + node.getNodeRef(), + props, + contentUrl, + this.keepCheckedOut); + + // commit the transaction + tx.commit(); + + // clear action context + setDocument(null); + clearUpload(); + + outcome = "browse"; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_CHECKIN) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: checkinFileOK called without a current Document!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Update File page + */ + public String updateFileOK() + { + String outcome = null; + + UserTransaction tx = null; + + // NOTE: for update the document node _is_ the working document! + Node node = getDocument(); + if (node != null && this.getFileName() != null) + { + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to update content node Id: " + node.getId()); + + // get an updating writer that we can use to modify the content on the current node + ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.putContent(this.file); + + // commit the transaction + tx.commit(); + + // clear action context + setDocument(null); + clearUpload(); + + outcome = "browse"; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: updateFileOK called without a current Document!"); + } + + return outcome; + } + + /** + * Deals with the cancel button being pressed on the check in file page + */ + public String cancel() + { + // reset the state + clearUpload(); + + return "browse"; + } + + /** + * Clear form state and upload file bean + */ + private void clearUpload() + { + // delete the temporary file we uploaded earlier + if (this.file != null) + { + this.file.delete(); + } + + this.file = null; + this.fileName = null; + this.keepCheckedOut = false; + this.minorChange = true; + this.copyLocation = COPYLOCATION_CURRENT; + this.versionNotes = ""; + this.selectedSpaceId = null; + + // remove the file upload bean from the session + FacesContext ctx = FacesContext.getCurrentInstance(); + ctx.getExternalContext().getSessionMap().remove(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + } + + + // ------------------------------------------------------------------------------ + // Private data + + private static Log logger = LogFactory.getLog(CheckinCheckoutBean.class); + + /** I18N messages */ + private static final String MSG_ERROR_CHECKIN = "error_checkin"; + private static final String MSG_ERROR_CANCELCHECKOUT = "error_cancel_checkout"; + private static final String MSG_ERROR_UPDATE = "error_update"; + private static final String MSG_ERROR_CHECKOUT = "error_checkout"; + + /** constants for copy location selection */ + private static final String COPYLOCATION_CURRENT = "current"; + private static final String COPYLOCATION_OTHER = "other"; + + /** The current document */ + private Node document; + + /** The working copy of the document we are checking out */ + private Node workingDocument; + + /** Content of the document used for HTML in-line editing */ + private String documentContent; + + /** Content of the document returned from in-line editing */ + private String editorOutput; + + /** transient form and upload properties */ + private File file; + private String fileName; + private boolean keepCheckedOut = false; + private boolean minorChange = true; + private String copyLocation = COPYLOCATION_CURRENT; + private String versionNotes = ""; + private NodeRef selectedSpaceId = null; + + /** The BrowseBean to be used by the bean */ + private BrowseBean browseBean; + + /** The NodeService to be used by the bean */ + private NodeService nodeService; + + /** The VersionOperationsService to be used by the bean */ + private CheckOutCheckInService versionOperationsService; + + /** The ContentService to be used by the bean */ + private ContentService contentService; +} diff --git a/source/java/org/alfresco/web/bean/DocumentDetailsBean.java b/source/java/org/alfresco/web/bean/DocumentDetailsBean.java new file mode 100644 index 0000000000..1db1ddfecf --- /dev/null +++ b/source/java/org/alfresco/web/bean/DocumentDetailsBean.java @@ -0,0 +1,1262 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.CopyService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.security.OwnableService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.DownloadContentServlet; +import org.alfresco.web.bean.repository.MapNode; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.wizard.NewRuleWizard; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.Utils.URLMode; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIPanel.ExpandedEvent; +import org.apache.log4j.Logger; + +/** + * Backing bean providing access to the details of a document + * + * @author gavinc + */ +public class DocumentDetailsBean +{ + private static final String MSG_SUCCESS_OWNERSHIP = "success_ownership"; + private static final String MSG_HAS_FOLLOWING_CATEGORIES = "has_following_categories"; + private static final String MSG_NO_CATEGORIES_APPLIED = "no_categories_applied"; + private static final String MSG_ERROR_ASPECT_INLINEEDITABLE = "error_aspect_inlineeditable"; + private static final String MSG_ERROR_ASPECT_VERSIONING = "error_aspect_versioning"; + private static final String MSG_ERROR_ASPECT_CLASSIFY = "error_aspect_classify"; + private static final String MSG_ERROR_WORKFLOW_REJECT = "error_workflow_reject"; + private static final String MSG_ERROR_WORKFLOW_APPROVE = "error_workflow_approve"; + private static final String MSG_ERROR_UPDATE_SIMPLEWORKFLOW = "error_update_simpleworkflow"; + private static final String MSG_ERROR_UPDATE_CATEGORY = "error_update_category"; + + private static Logger logger = Logger.getLogger(DocumentDetailsBean.class); + + private BrowseBean browseBean; + private NodeService nodeService; + private LockService lockService; + private CopyService copyService; + private VersionService versionService; + private OwnableService ownableService; + private NavigationBean navigator; + + private Map panels = new HashMap(5, 1.0f); + + private Map workflowProperties; + private NodeRef addedCategory; + private List categories; + + + /** + * Default constructor + */ + public DocumentDetailsBean() + { + // initial state of some panels that don't use the default + panels.put("workflow-panel", false); + panels.put("category-panel", false); + panels.put("version-history-panel", false); + } + + /** + * Resets any state that may be held by this bean + */ + public void reset() + { + // reset the workflow cache + this.workflowProperties = null; + + // reset the category caches + this.categories = null; + this.addedCategory = null; + } + + /** + * Returns the id of the current document + * + * @return The id + */ + public String getId() + { + return getDocument().getId(); + } + + /** + * Returns the name of the current document + * + * @return Name of the current document + */ + public String getName() + { + return getDocument().getName(); + } + + /** + * Returns the URL to download content for the current document + * + * @return Content url to download the current document + */ + public String getUrl() + { + return (String)getDocument().getProperties().get("url"); + } + + /** + * Returns the URL to the content for the current document + * + * @return Content url to the current document + */ + public String getBrowserUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getDocument(), URLMode.HTTP_INLINE); + } + + /** + * Returns the download URL to the content for the current document + * + * @return Download url to the current document + */ + public String getDownloadUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getDocument(), URLMode.HTTP_DOWNLOAD); + } + + /** + * Returns the WebDAV URL for the current document + * + * @return The WebDAV url + */ + public String getWebdavUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getDocument(), URLMode.WEBDAV); + } + + /** + * Returns the URL to access the details page for the current document + * + * @return The bookmark URL + */ + public String getBookmarkUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getDocument(), URLMode.SHOW_DETAILS); + } + + /** + * Returns the CIFS path for the current document + * + * @return The CIFS path + */ + public String getCifsPath() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getDocument(), URLMode.CIFS); + } + + /** + * Determines whether the current document is versionable + * + * @return true if the document has the versionable aspect + */ + public boolean isVersionable() + { + return getDocument().hasAspect(ContentModel.ASPECT_VERSIONABLE); + } + + /** + * @return true if the current document has the 'inlineeditable' aspect applied + */ + public boolean isInlineEditable() + { + return getDocument().hasAspect(ContentModel.ASPECT_INLINEEDITABLE); + } + + /** + * Returns a list of objects representing the versions of the + * current document + * + * @return List of previous versions + */ + public List getVersionHistory() + { + List versions = new ArrayList(); + + if (getDocument().hasAspect(ContentModel.ASPECT_VERSIONABLE)) + { + VersionHistory history = this.versionService.getVersionHistory(getDocument().getNodeRef()); + + if (history != null) + { + for (Version version : history.getAllVersions()) + { + // create a map node representation of the version + MapNode clientVersion = new MapNode(version.getFrozenStateNodeRef()); + clientVersion.put("versionLabel", version.getVersionLabel()); + clientVersion.put("notes", version.getDescription()); + clientVersion.put("author", clientVersion.get("creator")); + clientVersion.put("versionDate", version.getCreatedDate()); + clientVersion.put("url", DownloadContentServlet.generateBrowserURL(version.getFrozenStateNodeRef(), + clientVersion.getName())); + + // add the client side version to the list + versions.add(clientVersion); + } + } + } + + return versions; + } + + /** + * Determines whether the current document has any categories applied + * + * @return true if the document has categories attached + */ + public boolean isCategorised() + { + return getDocument().hasAspect(ContentModel.ASPECT_GEN_CLASSIFIABLE); + } + + /** + * Returns a list of objects representing the categories applied to the + * current document + * + * @return List of categories + */ + public String getCategoriesOverviewHTML() + { + String html = null; + + if (isCategorised()) + { + // we know for now that the general classifiable aspect only will be + // applied so we can retrive the categories property direclty + Collection categories = (Collection)this.nodeService.getProperty(this.browseBean.getDocument().getNodeRef(), + ContentModel.PROP_CATEGORIES); + + if (categories == null || categories.size() == 0) + { + html = Application.getMessage(FacesContext.getCurrentInstance(), MSG_NO_CATEGORIES_APPLIED); + } + else + { + StringBuilder builder = new StringBuilder(Application.getMessage(FacesContext.getCurrentInstance(), + MSG_HAS_FOLLOWING_CATEGORIES)); + + builder.append("

    "); + for (Object obj : categories) + { + if (obj instanceof NodeRef) + { + if (this.nodeService.exists((NodeRef)obj)) + { + builder.append("
  • "); + builder.append(Repository.getNameForNode(this.nodeService, (NodeRef)obj)); + builder.append("
  • "); + } + } + } + builder.append("
"); + + html = builder.toString(); + } + } + + return html; + } + + /** + * Event handler called to setup the categories for editing + * + * @param event The event + */ + public void setupCategoriesForEdit(ActionEvent event) + { + this.categories = (List)this.nodeService.getProperty(this.browseBean.getDocument().getNodeRef(), + ContentModel.PROP_CATEGORIES); + } + + /** + * Returns a Map of the initial categories on the node keyed by the NodeRef + * + * @return Map of initial categories + */ + public List getCategories() + { + return this.categories; + } + + /** + * Sets the categories Map + * + * @param categories + */ + public void setCategories(List categories) + { + this.categories = categories; + } + + /** + * Returns the last category added from the multi value editor + * + * @return The last category added + */ + public NodeRef getAddedCategory() + { + return this.addedCategory; + } + + /** + * Sets the category added from the multi value editor + * + * @param addedCategory The added category + */ + public void setAddedCategory(NodeRef addedCategory) + { + this.addedCategory = addedCategory; + } + + /** + * Updates the categories for the current document + * + * @return The outcome + */ + public String saveCategories() + { + String outcome = "cancel"; + + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // firstly retrieve all the properties for the current node + Map updateProps = this.nodeService.getProperties( + getDocument().getNodeRef()); + + // create a node ref representation of the selected id and set the new properties + updateProps.put(ContentModel.PROP_CATEGORIES, (Serializable)this.categories); + + // set the properties on the node + this.nodeService.setProperties(getDocument().getNodeRef(), updateProps); + + // commit the transaction + tx.commit(); + + // reset the state of the current document so it reflects the changes just made + getDocument().reset(); + + outcome = "finish"; + } + catch (Throwable e) + { + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE_CATEGORY), e.getMessage()), e); + } + + return outcome; + } + + /** + * Returns an overview summary of the current state of the attached + * workflow (if any) + * + * @return Summary HTML + */ + public String getWorkflowOverviewHTML() + { + String html = null; + + if (getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + // get the simple workflow aspect properties + Map props = getDocument().getProperties(); + + String approveStepName = (String)props.get( + ContentModel.PROP_APPROVE_STEP.toString()); + String rejectStepName = (String)props.get( + ContentModel.PROP_REJECT_STEP.toString()); + + Boolean approveMove = (Boolean)props.get( + ContentModel.PROP_APPROVE_MOVE.toString()); + Boolean rejectMove = (Boolean)props.get( + ContentModel.PROP_REJECT_MOVE.toString()); + + NodeRef approveFolder = (NodeRef)props.get( + ContentModel.PROP_APPROVE_FOLDER.toString()); + NodeRef rejectFolder = (NodeRef)props.get( + ContentModel.PROP_REJECT_FOLDER.toString()); + + String approveFolderName = null; + String rejectFolderName = null; + + // get the approve folder name + if (approveFolder != null) + { + Node node = new Node(approveFolder); + approveFolderName = node.getName(); + } + + // get the reject folder name + if (rejectFolder != null) + { + Node node = new Node(rejectFolder); + rejectFolderName = node.getName(); + } + + StringBuilder builder = new StringBuilder(); + + // calculate the approve action string + String action = null; + if (approveMove.booleanValue()) + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "moved"); + } + else + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "copied"); + } + + String docActionPattern = Application.getMessage(FacesContext.getCurrentInstance(), "document_action"); + Object[] params = new Object[] {action, approveFolderName, approveStepName}; + builder.append(MessageFormat.format(docActionPattern, params)); + + // add details of the reject step if there is one + if (rejectStepName != null && rejectMove != null && rejectFolderName != null) + { + if (rejectMove.booleanValue()) + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "moved"); + } + else + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "copied"); + } + + builder.append("

"); + params = new Object[] {action, rejectFolderName, rejectStepName}; + builder.append(MessageFormat.format(docActionPattern, params)); + builder.append("

"); + } + + html = builder.toString(); + } + + return html; + } + + /** + * Returns the properties for the attached workflow as a map + * + * @return Properties of the attached workflow, null if there is no workflow + */ + public Map getWorkflowProperties() + { + if (this.workflowProperties == null && + getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + // get the exisiting properties for the document + Map props = getDocument().getProperties(); + + String approveStepName = (String)props.get( + ContentModel.PROP_APPROVE_STEP.toString()); + String rejectStepName = (String)props.get( + ContentModel.PROP_REJECT_STEP.toString()); + + Boolean approveMove = (Boolean)props.get( + ContentModel.PROP_APPROVE_MOVE.toString()); + Boolean rejectMove = (Boolean)props.get( + ContentModel.PROP_REJECT_MOVE.toString()); + + NodeRef approveFolder = (NodeRef)props.get( + ContentModel.PROP_APPROVE_FOLDER.toString()); + NodeRef rejectFolder = (NodeRef)props.get( + ContentModel.PROP_REJECT_FOLDER.toString()); + + // put the workflow properties in a separate map for use by the JSP + this.workflowProperties = new HashMap(7); + this.workflowProperties.put(NewRuleWizard.PROP_APPROVE_STEP_NAME, + approveStepName); + this.workflowProperties.put(NewRuleWizard.PROP_APPROVE_ACTION, + approveMove ? "move" : "copy"); + this.workflowProperties.put(NewRuleWizard.PROP_APPROVE_FOLDER, approveFolder); + + if (rejectStepName == null || rejectMove == null || rejectFolder == null) + { + this.workflowProperties.put(NewRuleWizard.PROP_REJECT_STEP_PRESENT, "no"); + } + else + { + this.workflowProperties.put(NewRuleWizard.PROP_REJECT_STEP_PRESENT, + "yes"); + this.workflowProperties.put(NewRuleWizard.PROP_REJECT_STEP_NAME, + rejectStepName); + this.workflowProperties.put(NewRuleWizard.PROP_REJECT_ACTION, + rejectMove ? "move" : "copy"); + this.workflowProperties.put(NewRuleWizard.PROP_REJECT_FOLDER, + rejectFolder); + } + } + + return this.workflowProperties; + } + + /** + * + * @return + */ + public String cancelWorkflowEdit() + { + // resets the workflow properties map so any changes made + // don't appear to be persisted + this.workflowProperties.clear(); + this.workflowProperties = null; + return "cancel"; + } + + /** + * Saves the details of the workflow stored in workflowProperties + * to the current document + * + * @return The outcome string + */ + public String saveWorkflow() + { + String outcome = "cancel"; + + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // firstly retrieve all the properties for the current node + Map updateProps = this.nodeService.getProperties( + getDocument().getNodeRef()); + + // update the simple workflow properties + + // set the approve step name + updateProps.put(ContentModel.PROP_APPROVE_STEP, + this.workflowProperties.get(NewRuleWizard.PROP_APPROVE_STEP_NAME)); + + // specify whether the approve step will copy or move the content + boolean approveMove = true; + String approveAction = (String)this.workflowProperties.get(NewRuleWizard.PROP_APPROVE_ACTION); + if (approveAction != null && approveAction.equals("copy")) + { + approveMove = false; + } + updateProps.put(ContentModel.PROP_APPROVE_MOVE, Boolean.valueOf(approveMove)); + + // create node ref representation of the destination folder + updateProps.put(ContentModel.PROP_APPROVE_FOLDER, + this.workflowProperties.get(NewRuleWizard.PROP_APPROVE_FOLDER)); + + // determine whether there should be a reject step + boolean requireReject = true; + String rejectStepPresent = (String)this.workflowProperties.get( + NewRuleWizard.PROP_REJECT_STEP_PRESENT); + if (rejectStepPresent != null && rejectStepPresent.equals("no")) + { + requireReject = false; + } + + if (requireReject) + { + // set the reject step name + updateProps.put(ContentModel.PROP_REJECT_STEP, + this.workflowProperties.get(NewRuleWizard.PROP_REJECT_STEP_NAME)); + + // specify whether the reject step will copy or move the content + boolean rejectMove = true; + String rejectAction = (String)this.workflowProperties.get( + NewRuleWizard.PROP_REJECT_ACTION); + if (rejectAction != null && rejectAction.equals("copy")) + { + rejectMove = false; + } + updateProps.put(ContentModel.PROP_REJECT_MOVE, Boolean.valueOf(rejectMove)); + + // create node ref representation of the destination folder + updateProps.put(ContentModel.PROP_REJECT_FOLDER, + this.workflowProperties.get(NewRuleWizard.PROP_REJECT_FOLDER)); + } + else + { + // set all the reject properties to null to signify there should + // be no reject step + updateProps.put(ContentModel.PROP_REJECT_STEP, null); + updateProps.put(ContentModel.PROP_REJECT_MOVE, null); + updateProps.put(ContentModel.PROP_REJECT_FOLDER, null); + } + + // set the properties on the node + this.nodeService.setProperties(getDocument().getNodeRef(), updateProps); + + // commit the transaction + tx.commit(); + + // reset the state of the current document so it reflects the changes just made + getDocument().reset(); + + outcome = "finish"; + } + catch (Throwable e) + { + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE_SIMPLEWORKFLOW), e.getMessage()), e); + } + + return outcome; + } + + /** + * Returns the name of the approve step of the attached workflow + * + * @return The name of the approve step or null if there is no workflow + */ + public String getApproveStepName() + { + String approveStepName = null; + + if (getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + approveStepName = (String)getDocument().getProperties().get( + ContentModel.PROP_APPROVE_STEP.toString()); + } + + return approveStepName; + } + + /** + * Event handler called to handle the approve step of the simple workflow + * + * @param event The event that was triggered + */ + public void approve(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id == null || id.length() == 0) + { + throw new AlfrescoRuntimeException("approve called without an id"); + } + + NodeRef docNodeRef = new NodeRef(Repository.getStoreRef(), id); + Node docNode = new Node(docNodeRef); + + if (docNode.hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW) == false) + { + throw new AlfrescoRuntimeException("You can not approve a document that is not part of a workflow"); + } + + // get the simple workflow aspect properties + Map props = docNode.getProperties(); + + Boolean approveMove = (Boolean)props.get(ContentModel.PROP_APPROVE_MOVE.toString()); + NodeRef approveFolder = (NodeRef)props.get(ContentModel.PROP_APPROVE_FOLDER.toString()); + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // first we need to take off the simpleworkflow aspect + this.nodeService.removeAspect(docNodeRef, ContentModel.ASPECT_SIMPLE_WORKFLOW); + + if (approveMove.booleanValue()) + { + // move the document to the specified folder + String qname = QName.createValidLocalName(docNode.getName()); + this.nodeService.moveNode(docNodeRef, approveFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname)); + } + else + { + // copy the document to the specified folder + String qname = QName.createValidLocalName(docNode.getName()); + this.copyService.copy(docNodeRef, approveFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname)); + } + + // commit the transaction + tx.commit(); + + // if this was called via the document details dialog we need to reset the document node + if (getDocument() != null) + { + getDocument().reset(); + } + + // also make sure the UI will get refreshed + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + + if (logger.isDebugEnabled()) + { + String movedCopied = approveMove ? "moved" : "copied"; + logger.debug("Document has been approved and " + movedCopied + " to folder with id of " + + approveFolder.getId()); + } + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_WORKFLOW_APPROVE), e.getMessage()), e); + } + } + + /** + * Returns the name of the reject step of the attached workflow + * + * @return The name of the reject step or null if there is no workflow + */ + public String getRejectStepName() + { + String approveStepName = null; + + if (getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + approveStepName = (String)getDocument().getProperties().get( + ContentModel.PROP_REJECT_STEP.toString()); + } + + return approveStepName; + } + + /** + * Event handler called to handle the approve step of the simple workflow + * + * @param event The event that was triggered + */ + public void reject(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id == null || id.length() == 0) + { + throw new AlfrescoRuntimeException("reject called without an id"); + } + + NodeRef docNodeRef = new NodeRef(Repository.getStoreRef(), id); + Node docNode = new Node(docNodeRef); + + if (docNode.hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW) == false) + { + throw new AlfrescoRuntimeException("You can not reject a document that is not part of a workflow"); + } + + // get the simple workflow aspect properties + Map props = docNode.getProperties(); + + String rejectStep = (String)props.get(ContentModel.PROP_REJECT_STEP.toString()); + Boolean rejectMove = (Boolean)props.get(ContentModel.PROP_REJECT_MOVE.toString()); + NodeRef rejectFolder = (NodeRef)props.get(ContentModel.PROP_REJECT_FOLDER.toString()); + + if (rejectStep == null && rejectMove == null && rejectFolder == null) + { + throw new AlfrescoRuntimeException("The workflow does not have a reject step defined"); + } + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // first we need to take off the simpleworkflow aspect + this.nodeService.removeAspect(docNodeRef, ContentModel.ASPECT_SIMPLE_WORKFLOW); + + if (rejectMove.booleanValue()) + { + // move the document to the specified folder + String qname = QName.createValidLocalName(docNode.getName()); + this.nodeService.moveNode(docNodeRef, rejectFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname)); + } + else + { + // copy the document to the specified folder + String qname = QName.createValidLocalName(docNode.getName()); + this.copyService.copy(docNodeRef, rejectFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname)); + } + + // commit the transaction + tx.commit(); + + // if this was called via the document details dialog we need to reset the document node + if (getDocument() != null) + { + getDocument().reset(); + } + + // also make sure the UI will get refreshed + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + + if (logger.isDebugEnabled()) + { + String movedCopied = rejectMove ? "moved" : "copied"; + logger.debug("Document has been rejected and " + movedCopied + " to folder with id of " + + rejectFolder.getId()); + } + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_WORKFLOW_REJECT), e.getMessage()), e); + } + } + + /** + * Applies the classifiable aspect to the current document + */ + public void applyClassifiable() + { + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // add the general classifiable aspect to the node + this.nodeService.addAspect(getDocument().getNodeRef(), ContentModel.ASPECT_GEN_CLASSIFIABLE, null); + + // commit the transaction + tx.commit(); + + // reset the state of the current document + getDocument().reset(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_ASPECT_CLASSIFY), e.getMessage()), e); + } + } + + /** + * Applies the versionable aspect to the current document + */ + public void applyVersionable() + { + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // add the versionable aspect to the node + this.nodeService.addAspect(getDocument().getNodeRef(), ContentModel.ASPECT_VERSIONABLE, null); + + // commit the transaction + tx.commit(); + + // reset the state of the current document + getDocument().reset(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_ASPECT_VERSIONING), e.getMessage()), e); + } + } + + /** + * Applies the inlineeditable aspect to the current document + */ + public String applyInlineEditable() + { + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // add the inlineeditable aspect to the node + Map props = new HashMap(1, 1.0f); + String contentType = null; + ContentData contentData = (ContentData)getDocument().getProperties().get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + contentType = contentData.getMimetype(); + } + if (contentType != null) + { + // set the property to true by default if the filetype is text/HTML content + if (MimetypeMap.MIMETYPE_HTML.equals(contentType) || + MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(contentType) || + MimetypeMap.MIMETYPE_XML.equals(contentType) || + MimetypeMap.MIMETYPE_TEXT_CSS.equals(contentType)) + { + props.put(ContentModel.PROP_EDITINLINE, true); + } + } + this.nodeService.addAspect(getDocument().getNodeRef(), ContentModel.ASPECT_INLINEEDITABLE, props); + + // commit the transaction + tx.commit(); + + // reset the state of the current document + getDocument().reset(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_ASPECT_INLINEEDITABLE), e.getMessage()), e); + } + + // force recreation of the details view - this means the properties sheet component will reinit + return "showDocDetails"; + } + + /** + * Navigates to next item in the list of content for the current Space + */ + public void nextItem(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + List nodes = this.browseBean.getContent(); + if (nodes.size() > 1) + { + // perform a linear search - this is slow but stateless + // otherwise we would have to manage state of last selected node + // this gets very tricky as this bean is instantiated once and never + // reset - it does not know when the document has changed etc. + for (int i=0; i params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + List nodes = this.browseBean.getContent(); + if (nodes.size() > 1) + { + // see above + for (int i=0; i getPanels() + { + return this.panels; + } + + /** + * @param panels The panels expanded state map. + */ + public void setPanels(Map panels) + { + this.panels = panels; + } + + /** + * Sets the BrowseBean instance to use to retrieve the current document + * + * @param browseBean BrowseBean instance + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * Sets the node service instance the bean should use + * + * @param nodeService The NodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the lock service instance the bean should use + * + * @param lockService The LockService + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * Sets the version service instance the bean should use + * + * @param versionService The VersionService + */ + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + /** + * Sets the copy service instance the bean should use + * + * @param copyService The CopyService + */ + public void setCopyService(CopyService copyService) + { + this.copyService = copyService; + } + + /** + * Sets the ownable service instance the bean should use + * + * @param ownableService The OwnableService + */ + public void setOwnableService(OwnableService ownableService) + { + this.ownableService = ownableService; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } +} diff --git a/source/java/org/alfresco/web/bean/DocumentPropertiesBean.java b/source/java/org/alfresco/web/bean/DocumentPropertiesBean.java new file mode 100644 index 0000000000..af1f3a065d --- /dev/null +++ b/source/java/org/alfresco/web/bean/DocumentPropertiesBean.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.SelectItem; +import javax.transaction.UserTransaction; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigLookupContext; +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.config.PropertySheetConfigElement; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Backing bean for the edit document properties dialog + * + * @author gavinc + */ +public class DocumentPropertiesBean +{ + private static final String TEMP_PROP_MIMETYPE = "mimetype"; + + private NodeService nodeService; + private FileFolderService fileFolderService; + private DictionaryService dictionaryService; + private BrowseBean browseBean; + private List contentTypes; + private Node editableNode; + private Boolean hasOtherProperties; + + /** + * Returns the node being edited + * + * @return The node being edited + */ + public Node getEditableNode() + { + return this.editableNode; + } + + /** + * Event handler called to setup the document for property editing + * + * @param event The event + */ + public void setupDocumentForAction(ActionEvent event) + { + this.editableNode = new Node(this.browseBean.getDocument().getNodeRef()); + + // special case for Mimetype - since this is a sub-property of the ContentData object + // we must extract it so it can be edited in the client, then we check for it later + // and create a new ContentData object to wrap it and it's associated URL + ContentData content = (ContentData)this.editableNode.getProperties().get(ContentModel.PROP_CONTENT); + if (content != null) + { + this.editableNode.getProperties().put(TEMP_PROP_MIMETYPE, content.getMimetype()); + } + + this.hasOtherProperties = null; + } + + /** + * Event handler used to save the edited properties back to the repository + * + * @return The outcome + */ + public String save() + { + String outcome = "cancel"; + + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + NodeRef nodeRef = this.browseBean.getDocument().getNodeRef(); + Map props = this.editableNode.getProperties(); + + // get the name and move the node as necessary + String name = (String) props.get(ContentModel.PROP_NAME); + if (name != null) + { + fileFolderService.rename(nodeRef, name); + } + + Map properties = this.nodeService.getProperties(nodeRef); + // we need to put all the properties from the editable bag back into + // the format expected by the repository + + // but first extract and deal with the special mimetype property for ContentData + String mimetype = (String)props.get(TEMP_PROP_MIMETYPE); + if (mimetype != null) + { + // remove temporary prop from list so it isn't saved with the others + props.remove(TEMP_PROP_MIMETYPE); + ContentData contentData = (ContentData)props.get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + contentData = ContentData.setMimetype(contentData, mimetype); + props.put(ContentModel.PROP_CONTENT.toString(), contentData); + } + } + + // add the remaining properties + Iterator iterProps = props.keySet().iterator(); + while (iterProps.hasNext()) + { + String propName = iterProps.next(); + QName qname = QName.createQName(propName); + + // make sure the property is represented correctly + Serializable propValue = (Serializable)props.get(propName); + PropertyDefinition propDef = this.dictionaryService.getProperty(qname); + + properties.put(qname, propValue); + } + + // send the properties back to the repository + this.nodeService.setProperties(this.browseBean.getDocument().getNodeRef(), properties); + + // we also need to persist any association changes that may have been made + + // add any associations added in the UI + Map> addedAssocs = this.editableNode.getAddedAssociations(); + for (Map typedAssoc : addedAssocs.values()) + { + for (AssociationRef assoc : typedAssoc.values()) + { + this.nodeService.createAssociation(assoc.getSourceRef(), assoc.getTargetRef(), assoc.getTypeQName()); + } + } + + // remove any association removed in the UI + Map> removedAssocs = this.editableNode.getRemovedAssociations(); + for (Map typedAssoc : removedAssocs.values()) + { + for (AssociationRef assoc : typedAssoc.values()) + { + this.nodeService.removeAssociation(assoc.getSourceRef(), assoc.getTargetRef(), assoc.getTypeQName()); + } + } + + // add any child associations added in the UI + Map> addedChildAssocs = this.editableNode.getAddedChildAssociations(); + for (Map typedAssoc : addedChildAssocs.values()) + { + for (ChildAssociationRef assoc : typedAssoc.values()) + { + this.nodeService.addChild(assoc.getParentRef(), assoc.getChildRef(), assoc.getTypeQName(), assoc.getTypeQName()); + } + } + + // remove any child association removed in the UI + Map> removedChildAssocs = this.editableNode.getRemovedChildAssociations(); + for (Map typedAssoc : removedChildAssocs.values()) + { + for (ChildAssociationRef assoc : typedAssoc.values()) + { + this.nodeService.removeChild(assoc.getParentRef(), assoc.getChildRef()); + } + } + + // commit the transaction + tx.commit(); + + // set the outcome to refresh + outcome = "finish"; + + // reset the document held by the browse bean as it's just been updated + this.browseBean.getDocument().reset(); + } + catch (FileExistsException e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + // print status message + String statusMsg = MessageFormat.format( + Application.getMessage( + FacesContext.getCurrentInstance(), "error_exists"), + e.getExisting().getName()); + Utils.addErrorMessage(statusMsg); + // no outcome + outcome = null; + } + catch (InvalidNodeRefException err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {this.browseBean.getDocument().getId()}) ); + // this failure means the node no longer exists - we cannot show the doc properties screen + outcome = "browse"; + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + } + + return outcome; + } + + public Map getProperties() + { + return this.editableNode.getProperties(); + } + + /** + * @return Returns a list of content types to allow the user to select from + */ + public List getContentTypes() + { + if (this.contentTypes == null) + { + this.contentTypes = new ArrayList(80); + ServiceRegistry registry = Repository.getServiceRegistry(FacesContext.getCurrentInstance()); + MimetypeService mimetypeService = registry.getMimetypeService(); + + // get the mime type display names + Map mimeTypes = mimetypeService.getDisplaysByMimetype(); + for (String mimeType : mimeTypes.keySet()) + { + this.contentTypes.add(new SelectItem(mimeType, mimeTypes.get(mimeType))); + } + + // make sure the list is sorted by the values + QuickSort sorter = new QuickSort(this.contentTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + return this.contentTypes; + } + + /** + * Determines whether this document has any other properties other than the + * default set to display to the user. + * + * @return true of there are properties to show, false otherwise + */ + public boolean getOtherPropertiesPresent() + { + if (this.hasOtherProperties == null) + { + // we need to use the config service to see whether there are any + // editable properties configured for this document. + ConfigService configSvc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config configProps = configSvc.getConfig(this.editableNode, new ConfigLookupContext("edit-properties")); + PropertySheetConfigElement propsToDisplay = (PropertySheetConfigElement)configProps. + getConfigElement("property-sheet"); + this.hasOtherProperties = Boolean.valueOf(propsToDisplay != null); + } + + return this.hasOtherProperties.booleanValue(); + } + + /** + * @return Returns the nodeService. + */ + public NodeService getNodeService() + { + return this.nodeService; + } + + /** + * @param nodeService The nodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param fileFolderService the file and folder model-specific functions + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * Sets the DictionaryService to use when persisting metadata + * + * @param dictionaryService The DictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @return The BrowseBean + */ + public BrowseBean getBrowseBean() + { + return this.browseBean; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } +} diff --git a/source/java/org/alfresco/web/bean/ErrorBean.java b/source/java/org/alfresco/web/bean/ErrorBean.java new file mode 100644 index 0000000000..6a387f2c36 --- /dev/null +++ b/source/java/org/alfresco/web/bean/ErrorBean.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import javax.servlet.ServletException; + +/** + * Bean used by the error page, holds the last exception to be thrown by the system + * + * @author gavinc + */ +public class ErrorBean +{ + public static final String ERROR_BEAN_NAME = "alfresco.ErrorBean"; + + private String returnPage; + private Throwable lastError; + + /** + * @return Returns the page to go back to after the error has been displayed + */ + public String getReturnPage() + { + return returnPage; + } + + /** + * @param returnPage The page to return to after showing the error + */ + public void setReturnPage(String returnPage) + { + this.returnPage = returnPage; + } + + /** + * @return Returns the lastError. + */ + public Throwable getLastError() + { + return lastError; + } + + /** + * @param lastError The lastError to set. + */ + public void setLastError(Throwable lastError) + { + this.lastError = lastError; + } + + /** + * @return Returns the last error to occur in string form + */ + public String getLastErrorMessage() + { + String message = "No error currently stored"; + + if (this.lastError != null) + { + StringBuilder builder = null; + Throwable cause = null; + if (this.lastError instanceof ServletException && + ((ServletException)this.lastError).getRootCause() != null) + { + // servlet exception puts the actual error in root cause!! + Throwable actualError = ((ServletException)this.lastError).getRootCause(); + builder = new StringBuilder(actualError.toString()); + cause = actualError.getCause(); + } + else + { + builder = new StringBuilder(this.lastError.toString()); + cause = this.lastError.getCause(); + } + + while (cause != null) + { + builder.append("

caused by:
"); + builder.append(cause.toString()); + + if (cause instanceof ServletException && + ((ServletException)cause).getRootCause() != null) + { + cause = ((ServletException)cause).getRootCause(); + } + else + { + cause = cause.getCause(); + } + } + + message = builder.toString(); + } + + return message; + } + + /** + * @return Returns the stack trace for the last error + */ + public String getStackTrace() + { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + + if (this.lastError instanceof ServletException && + ((ServletException)this.lastError).getRootCause() != null) + { + Throwable actualError = ((ServletException)this.lastError).getRootCause(); + actualError.printStackTrace(writer); + } + else + { + this.lastError.printStackTrace(writer); + } + + return stringWriter.toString().replaceAll("\r\n", "
"); + } +} diff --git a/source/java/org/alfresco/web/bean/ExportBean.java b/source/java/org/alfresco/web/bean/ExportBean.java new file mode 100644 index 0000000000..fce8cf56b0 --- /dev/null +++ b/source/java/org/alfresco/web/bean/ExportBean.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.transaction.UserTransaction; + +import org.alfresco.repo.action.executer.ExporterActionExecuter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Backing bean implementation for the Export dialog. + * + * @author gavinc + */ +public class ExportBean +{ + private static final Log logger = LogFactory.getLog(ExportBean.class); + + private static final String ALL_SPACES = "all"; + private static final String CURRENT_SPACE = "current"; + + private static final String DEFAULT_OUTCOME = "browse"; + + private static final String MSG_ERROR = "error_export"; + + private BrowseBean browseBean; + private NodeService nodeService; + private ActionService actionService; + + private String packageName; + private String encoding = "UTF-8"; + private String mode = CURRENT_SPACE; + private NodeRef destination; + private boolean includeChildren = true; + private boolean runInBackground = true; + private boolean includeSelf; + + /** + * Performs the export operation using the current state of the bean + * + * @return The outcome + */ + public String export() + { + if (logger.isDebugEnabled()) + logger.debug("Called export for " + this.mode + " with package name: " + this.packageName); + + String outcome = DEFAULT_OUTCOME; + + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // build the action params map based on the bean's current state + Map params = new HashMap(5); + params.put(ExporterActionExecuter.PARAM_STORE, Repository.getStoreRef().toString()); + params.put(ExporterActionExecuter.PARAM_PACKAGE_NAME, this.packageName); + params.put(ExporterActionExecuter.PARAM_ENCODING, this.encoding); + params.put(ExporterActionExecuter.PARAM_DESTINATION_FOLDER, this.destination); + params.put(ExporterActionExecuter.PARAM_INCLUDE_CHILDREN, new Boolean(includeChildren)); + params.put(ExporterActionExecuter.PARAM_INCLUDE_SELF, new Boolean(includeSelf)); + + // build the action to execute + Action action = this.actionService.createAction(ExporterActionExecuter.NAME, params); + action.setExecuteAsynchronously(this.runInBackground); + + // get the appropriate node + NodeRef startNode = null; + if (this.mode.equals(ALL_SPACES)) + { + startNode = this.nodeService.getRootNode(Repository.getStoreRef()); + } + else + { + startNode = this.browseBean.getActionSpace().getNodeRef(); + } + + // execute the action on the relevant node + this.actionService.executeAction(action, startNode); + + if (logger.isDebugEnabled()) + { + logger.debug("Executed export action with action params of " + params); + } + + // commit the transaction + tx.commit(); + + // reset the bean + reset(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR), e.toString()), e); + outcome = null; + } + + return outcome; + } + + /** + * Action called when the dialog is cancelled, just resets the bean's state + * + * @return The outcome + */ + public String cancel() + { + reset(); + + return DEFAULT_OUTCOME; + } + + /** + * Resets the dialog state back to the default + */ + public void reset() + { + this.packageName = null; + this.mode = CURRENT_SPACE; + this.destination = null; + this.includeChildren = true; + this.includeSelf = false; + this.runInBackground = true; + } + + /** + * Returns the package name for the export + * + * @return The export package name + */ + public String getPackageName() + { + return this.packageName; + } + + /** + * Sets the package name for the export + * + * @param packageName The export package name + */ + public void setPackageName(String packageName) + { + this.packageName = packageName; + } + + /** + * The destination for the export as a NodeRef + * + * @return The destination + */ + public NodeRef getDestination() + { + return this.destination; + } + + /** + * Sets the destination for the export + * + * @param destination The destination for the export + */ + public void setDestination(NodeRef destination) + { + this.destination = destination; + } + + /** + * Determines whether the export will include child spaces + * + * @return true includes children + */ + public boolean getIncludeChildren() + { + return this.includeChildren; + } + + /** + * Sets whether child spaces are included in the export + * + * @param includeChildren true to include the child spaces + */ + public void setIncludeChildren(boolean includeChildren) + { + this.includeChildren = includeChildren; + } + + /** + * Determines whether the export will include the space itself + * + * @return true includes the space being exported from + */ + public boolean getIncludeSelf() + { + return this.includeSelf; + } + + /** + * Sets whether the space itself is included in the export + * + * @param includeSelf true to include the space itself + */ + public void setIncludeSelf(boolean includeSelf) + { + this.includeSelf = includeSelf; + } + + /** + * Determines whether to export only the current space or all spaces + * + * @return "all" to export all space and "current" to export the current space + */ + public String getMode() + { + return this.mode; + } + + /** + * Sets whether to export the current space or all spaces + * + * @param mode "all" to export all space and "current" to export the current space + */ + public void setMode(String mode) + { + this.mode = mode; + } + + /** + * Returns the encoding to use for the export + * + * @return The encoding + */ + public String getEncoding() + { + return this.encoding; + } + + /** + * Sets the encoding to use for the export package + * + * @param encoding The encoding + */ + public void setEncoding(String encoding) + { + this.encoding = encoding; + } + + /** + * Determines whether the import should run in the background + * + * @return true means the import will run in the background + */ + public boolean getRunInBackground() + { + return this.runInBackground; + } + + /** + * Determines whether the import will run in the background + * + * @param runInBackground true to run the import in the background + */ + public void setRunInBackground(boolean runInBackground) + { + this.runInBackground = runInBackground; + } + + /** + * Sets the BrowseBean instance to use to retrieve the current document + * + * @param browseBean BrowseBean instance + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * Sets the action service + * + * @param actionService the action service + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Sets the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } +} diff --git a/source/java/org/alfresco/web/bean/FileUploadBean.java b/source/java/org/alfresco/web/bean/FileUploadBean.java new file mode 100644 index 0000000000..fc0a5cff7d --- /dev/null +++ b/source/java/org/alfresco/web/bean/FileUploadBean.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.File; + +/** + * Bean to hold the results of a file upload + * + * @author gavinc + */ +public final class FileUploadBean +{ + public static final String FILE_UPLOAD_BEAN_NAME = "alfresco.UploadBean"; + + private File file; + private String fileName; + private String filePath; + + /** + * @return Returns the file + */ + public File getFile() + { + return file; + } + + /** + * @param file The file to set + */ + public void setFile(File file) + { + this.file = file; + } + + /** + * @return Returns the name of the file uploaded + */ + public String getFileName() + { + return fileName; + } + + /** + * @param fileName The name of the uploaded file + */ + public void setFileName(String fileName) + { + this.fileName = fileName; + } + + /** + * @return Returns the path of the file uploaded + */ + public String getFilePath() + { + return filePath; + } + + /** + * @param filePath The file path of the uploaded file + */ + public void setFilePath(String filePath) + { + this.filePath = filePath; + } +} diff --git a/source/java/org/alfresco/web/bean/ForumsBean.java b/source/java/org/alfresco/web/bean/ForumsBean.java new file mode 100644 index 0000000000..f7e6a9153d --- /dev/null +++ b/source/java/org/alfresco/web/bean/ForumsBean.java @@ -0,0 +1,1020 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.QueryParameterDefinition; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.repository.MapNode; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.NodePropertyResolver; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIModeList; +import org.alfresco.web.ui.common.component.data.UIColumn; +import org.alfresco.web.ui.common.component.data.UIRichList; +import org.alfresco.web.ui.common.renderer.data.IRichListRenderer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Bean providing properties and behaviour for the forums screens. + * + * @author gavinc + */ +public class ForumsBean implements IContextListener +{ + private static Log logger = LogFactory.getLog(ForumsBean.class); + private static final String PAGE_NAME_FORUMS = "forums"; + private static final String PAGE_NAME_FORUM = "forum"; + private static final String PAGE_NAME_TOPIC = "topic"; + + /** The NodeService to be used by the bean */ + private NodeService nodeService; + + /** The ContentService to be used by the bean */ + private ContentService contentService; + + /** The DictionaryService bean reference */ + private DictionaryService dictionaryService; + + /** The SearchService bean reference. */ + private SearchService searchService; + + /** The NamespaceService bean reference. */ + private NamespaceService namespaceService; + + /** The browse bean */ + private BrowseBean browseBean; + + /** The NavigationBean bean reference */ + private NavigationBean navigator; + + /** Client configuration object */ + private ClientConfigElement clientConfig = null; + + /** Component references */ + private UIRichList forumsRichList; + private UIRichList forumRichList; + private UIRichList topicRichList; + + /** Node lists */ + private List forums; + private List topics; + private List posts; + + /** The current forums view mode - set to a well known IRichListRenderer identifier */ + private String forumsViewMode; + + /** The current forums view page size */ + private int forumsPageSize; + + /** The current forum view mode - set to a well known IRichListRenderer identifier */ + private String forumViewMode; + + /** The current forum view page size */ + private int forumPageSize; + + /** The current topic view mode - set to a well known IRichListRenderer identifier */ + private String topicViewMode; + + /** The current topic view page size */ + private int topicPageSize; + + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public ForumsBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + + initFromClientConfig(); + } + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the content service to use + * + * @param contentService The ContentService + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param dictionaryService The DictionaryService to set. + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param searchService The SearchService to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param namespaceService The NamespaceService to set. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the BrowseBean instance to use to retrieve the current document + * + * @param browseBean BrowseBean instance + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param forumsRichList The forumsRichList to set. + */ + public void setForumsRichList(UIRichList forumsRichList) + { + this.forumsRichList = forumsRichList; + if (this.forumsRichList != null) + { + // set the initial sort column and direction + this.forumsRichList.setInitialSortColumn( + clientConfig.getDefaultSortColumn(PAGE_NAME_FORUMS)); + this.forumsRichList.setInitialSortDescending( + clientConfig.hasDescendingSort(PAGE_NAME_FORUMS)); + } + } + + /** + * @return Returns the forumsRichList. + */ + public UIRichList getForumsRichList() + { + return this.forumsRichList; + } + + /** + * @return Returns the forums View mode. See UIRichList + */ + public String getForumsViewMode() + { + return this.forumsViewMode; + } + + /** + * @param forumsViewMode The forums View mode to set. See UIRichList. + */ + public void setForumsViewMode(String forumsViewMode) + { + this.forumsViewMode = forumsViewMode; + } + + /** + * @return Returns the forumsPageSize. + */ + public int getForumsPageSize() + { + return this.forumsPageSize; + } + + /** + * @param forumsPageSize The forumsPageSize to set. + */ + public void setForumsPageSize(int forumsPageSize) + { + this.forumsPageSize = forumsPageSize; + } + + /** + * @param topicRichList The topicRichList to set. + */ + public void setTopicRichList(UIRichList topicRichList) + { + this.topicRichList = topicRichList; + + if (this.topicRichList != null) + { + // set the initial sort column and direction + this.topicRichList.setInitialSortColumn( + clientConfig.getDefaultSortColumn(PAGE_NAME_TOPIC)); + this.topicRichList.setInitialSortDescending( + clientConfig.hasDescendingSort(PAGE_NAME_TOPIC)); + } + } + + /** + * @return Returns the topicRichList. + */ + public UIRichList getTopicRichList() + { + return this.topicRichList; + } + + /** + * @return Returns the topics View mode. See UIRichList + */ + public String getTopicViewMode() + { + return this.topicViewMode; + } + + /** + * @param topicViewMode The topic View mode to set. See UIRichList. + */ + public void setTopicViewMode(String topicViewMode) + { + this.topicViewMode = topicViewMode; + } + + /** + * @return Returns the topicsPageSize. + */ + public int getTopicPageSize() + { + return this.topicPageSize; + } + + /** + * @param topicPageSize The topicPageSize to set. + */ + public void setTopicPageSize(int topicPageSize) + { + this.topicPageSize = topicPageSize; + } + + /** + * @param forumRichList The forumRichList to set. + */ + public void setForumRichList(UIRichList forumRichList) + { + this.forumRichList = forumRichList; + + if (this.forumRichList != null) + { + // set the initial sort column and direction + this.forumRichList.setInitialSortColumn( + clientConfig.getDefaultSortColumn(PAGE_NAME_FORUM)); + this.forumRichList.setInitialSortDescending( + clientConfig.hasDescendingSort(PAGE_NAME_FORUM)); + } + } + + /** + * @return Returns the forumRichList. + */ + public UIRichList getForumRichList() + { + return this.forumRichList; + } + + /** + * @return Returns the forum View mode. See UIRichList + */ + public String getForumViewMode() + { + return this.forumViewMode; + } + + /** + * @param forumViewMode The forum View mode to set. See UIRichList. + */ + public void setForumViewMode(String forumViewMode) + { + this.forumViewMode = forumViewMode; + } + + /** + * @return Returns the forumPageSize. + */ + public int getForumPageSize() + { + return this.forumPageSize; + } + + /** + * @param forumPageSize The forumPageSize to set. + */ + public void setForumPageSize(int forumPageSize) + { + this.forumPageSize = forumPageSize; + } + + public List getForums() + { + if (this.forums == null) + { + getNodes(); + } + + return this.forums; + } + + public List getTopics() + { + if (this.topics == null) + { + getNodes(); + } + + return this.topics; + } + + public List getPosts() + { + if (this.posts == null) + { + getNodes(); + } + + return this.posts; + } + + private void getNodes() + { + long startTime = 0; + if (logger.isDebugEnabled()) + startTime = System.currentTimeMillis(); + + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + // get the current space from NavigationBean + String parentNodeId = this.navigator.getCurrentNodeId(); + + NodeRef parentRef; + if (parentNodeId == null) + { + // no specific parent node specified - use the root node + parentRef = this.nodeService.getRootNode(Repository.getStoreRef()); + } + else + { + // build a NodeRef for the specified Id and our store + parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId); + } + + // TODO: can we improve the Get here with an API call for children of a specific type? + List childRefs = this.nodeService.getChildAssocs(parentRef); + this.forums = new ArrayList(childRefs.size()); + this.topics = new ArrayList(childRefs.size()); + this.posts = new ArrayList(childRefs.size()); + + for (ChildAssociationRef ref: childRefs) + { + // create our Node representation from the NodeRef + NodeRef nodeRef = ref.getChildRef(); + + if (this.nodeService.exists(nodeRef)) + { + // find it's type so we can see if it's a node we are interested in + QName type = this.nodeService.getType(nodeRef); + + // make sure the type is defined in the data dictionary + TypeDefinition typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + // extract forums, forum, topic and post types + + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + if (this.dictionaryService.isSubClass(type, ForumModel.TYPE_FORUMS) || + this.dictionaryService.isSubClass(type, ForumModel.TYPE_FORUM)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + node.addPropertyResolver("icon", this.browseBean.resolverSpaceIcon); + + this.forums.add(node); + } + if (this.dictionaryService.isSubClass(type, ForumModel.TYPE_TOPIC)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + node.addPropertyResolver("icon", this.browseBean.resolverSpaceIcon); + node.addPropertyResolver("replies", this.resolverReplies); + + this.topics.add(node); + } + else if (this.dictionaryService.isSubClass(type, ForumModel.TYPE_POST)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef, this.nodeService, true); + + this.browseBean.setupDataBindingProperties(node); + node.addPropertyResolver("message", this.resolverContent); + node.addPropertyResolver("replyTo", this.resolverReplyTo); + + this.posts.add(node); + } + } + } + else + { + if (logger.isWarnEnabled()) + logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); + } + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); + this.forums = Collections.emptyList(); + this.topics = Collections.emptyList(); + this.posts = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + this.forums = Collections.emptyList(); + this.topics = Collections.emptyList(); + this.posts = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + if (logger.isDebugEnabled()) + { + long endTime = System.currentTimeMillis(); + logger.debug("Time to query and build forums nodes: " + (endTime - startTime) + "ms"); + } + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + if (logger.isDebugEnabled()) + logger.debug("Invalidating forums components..."); + + // clear the value for the list components - will cause re-bind to it's data and refresh + if (this.forumsRichList != null) + { + this.forumsRichList.setValue(null); + if (this.forumsRichList.getInitialSortColumn() == null) + { + // set the initial sort column and direction + this.forumsRichList.setInitialSortColumn( + clientConfig.getDefaultSortColumn(PAGE_NAME_FORUMS)); + this.forumsRichList.setInitialSortDescending( + clientConfig.hasDescendingSort(PAGE_NAME_FORUMS)); + } + } + + if (this.forumRichList != null) + { + this.forumRichList.setValue(null); + if (this.forumRichList.getInitialSortColumn() == null) + { + // set the initial sort column and direction + this.forumRichList.setInitialSortColumn( + clientConfig.getDefaultSortColumn(PAGE_NAME_FORUM)); + this.forumRichList.setInitialSortDescending( + clientConfig.hasDescendingSort(PAGE_NAME_FORUM)); + } + } + + if (this.topicRichList != null) + { + this.topicRichList.setValue(null); + if (this.topicRichList.getInitialSortColumn() == null) + { + // set the initial sort column and direction + this.topicRichList.setInitialSortColumn( + clientConfig.getDefaultSortColumn(PAGE_NAME_TOPIC)); + this.topicRichList.setInitialSortDescending( + clientConfig.hasDescendingSort(PAGE_NAME_TOPIC)); + } + } + + // reset the lists + this.forums = null; + this.topics = null; + this.posts = null; + } + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Change the current forums view mode based on user selection + * + * @param event ActionEvent + */ + public void forumsViewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + + // get the view mode ID + String viewMode = viewList.getValue().toString(); + + // push the view mode into the lists + setForumsViewMode(viewMode); + } + + /** + * Change the current forum view mode based on user selection + * + * @param event ActionEvent + */ + public void forumViewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + + // get the view mode ID + String viewMode = viewList.getValue().toString(); + + // push the view mode into the lists + setForumViewMode(viewMode); + } + + /** + * Change the current topic view mode based on user selection + * + * @param event ActionEvent + */ + public void topicViewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + + // get the view mode ID + String viewMode = viewList.getValue().toString(); + + // push the view mode into the lists + setTopicViewMode(viewMode); + } + + // ------------------------------------------------------------------------------ + // Property Resolvers + + public NodePropertyResolver resolverReplies = new NodePropertyResolver() { + public Object get(Node node) + { + // query for the number of posts within the given node + String repliesXPath = "./*[(subtypeOf('" + ForumModel.TYPE_POST + "'))]"; + List replies = searchService.selectNodes( + node.getNodeRef(), + repliesXPath, + new QueryParameterDefinition[] {}, + namespaceService, + false); + + // reduce the count by 1 as one of the posts will be the initial post + int noReplies = replies.size() - 1; + + if (noReplies < 0) + { + noReplies = 0; + } + + return new Integer(noReplies); + } + }; + + public NodePropertyResolver resolverContent = new NodePropertyResolver() { + public Object get(Node node) + { + String content = null; + + // get the content property from the node and retrieve the + // full content as a string (obviously should only be used + // for small amounts of content) + + ContentReader reader = contentService.getReader(node.getNodeRef(), + ContentModel.PROP_CONTENT); + + if (reader != null) + { + content = reader.getContentString(); + } + + return content; + } + }; + + public NodePropertyResolver resolverReplyTo = new NodePropertyResolver() { + public Object get(Node node) + { + // determine if this node is a reply to another post, if so find + // the creator of the original poster + + String replyTo = null; + + List assocs = nodeService.getTargetAssocs(node.getNodeRef(), + ContentModel.ASSOC_REFERENCES); + + // there should only be one association, if there is more than one + // just get the first one + if (assocs.size() > 0) + { + AssociationRef assoc = assocs.get(0); + NodeRef target = assoc.getTargetRef(); + Node targetNode = new Node(target); + replyTo = (String)targetNode.getProperties().get("creator"); + } + + return replyTo; + } + }; + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Initialise default values from client configuration + */ + private void initFromClientConfig() + { + this.clientConfig = (ClientConfigElement)Application.getConfigService( + FacesContext.getCurrentInstance()).getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + + // get the defaults for the forums page + this.forumsViewMode = clientConfig.getDefaultView(PAGE_NAME_FORUMS); + this.forumsPageSize = this.clientConfig.getDefaultPageSize(PAGE_NAME_FORUMS, + this.forumsViewMode); + + // get the default for the forum page + this.forumViewMode = clientConfig.getDefaultView(PAGE_NAME_FORUM); + this.forumPageSize = this.clientConfig.getDefaultPageSize(PAGE_NAME_FORUM, + this.forumViewMode); + + // get the default for the topic page + this.topicViewMode = clientConfig.getDefaultView(PAGE_NAME_TOPIC); + this.topicPageSize = this.clientConfig.getDefaultPageSize(PAGE_NAME_TOPIC, + this.topicViewMode); + + if (logger.isDebugEnabled()) + { + logger.debug("Set default forums view mode to: " + this.forumsViewMode); + logger.debug("Set default forums page size to: " + this.forumsPageSize); + logger.debug("Set default forum view mode to: " + this.forumViewMode); + logger.debug("Set default forum page size to: " + this.forumPageSize); + logger.debug("Set default topic view mode to: " + this.topicViewMode); + logger.debug("Set default topic page size to: " + this.topicPageSize); + } + } + + /** + * Class to implement a bubble view for the RichList component used in the topics screen + * + * @author gavinc + */ + public static class TopicBubbleViewRenderer implements IRichListRenderer + { + public static final String VIEWMODEID = "bubble"; + + /** + * @see org.alfresco.web.ui.common.renderer.data.IRichListRenderer#getViewModeID() + */ + public String getViewModeID() + { + return VIEWMODEID; + } + + /** + * @see org.alfresco.web.ui.common.renderer.data.IRichListRenderer#renderListBefore(javax.faces.context.FacesContext, org.alfresco.web.ui.common.component.data.UIColumn[]) + */ + public void renderListBefore(FacesContext context, UIRichList richList, UIColumn[] columns) + throws IOException + { + // nothing to do + } + + /** + * @see org.alfresco.web.ui.common.renderer.data.IRichListRenderer#renderListRow(javax.faces.context.FacesContext, org.alfresco.web.ui.common.component.data.UIColumn[], java.lang.Object) + */ + public void renderListRow(FacesContext context, UIRichList richList, UIColumn[] columns, Object row) + throws IOException + { + ResponseWriter out = context.getResponseWriter(); + + // find primary column (which must exist) and the actions column (which doesn't + // have to exist) + UIColumn primaryColumn = null; + UIColumn actionsColumn = null; + for (int i = 0; i < columns.length; i++) + { + if (columns[i].isRendered()) + { + if (columns[i].getPrimary()) + { + primaryColumn = columns[i]; + } + else if (columns[i].getActions()) + { + actionsColumn = columns[i]; + } + } + } + + if (primaryColumn == null) + { + if (logger.isWarnEnabled()) + logger.warn("No primary column found for RichList definition: " + richList.getId()); + } + + out.write(""); + + Node node = (Node)row; + if (node.getProperties().get("replyTo") == null) + { + renderNewPostBubble(context, out, node, primaryColumn, actionsColumn, columns); + } + else + { + renderReplyToBubble(context, out, node, primaryColumn, actionsColumn, columns); + } + + out.write(""); + + // add a little padding + out.write("
"); + } + + /** + * @see org.alfresco.web.ui.common.renderer.data.IRichListRenderer#renderListAfter(javax.faces.context.FacesContext, org.alfresco.web.ui.common.component.data.UIColumn[]) + */ + public void renderListAfter(FacesContext context, UIRichList richList, UIColumn[] columns) + throws IOException + { + ResponseWriter out = context.getResponseWriter(); + + out.write(""); + for (Iterator i = richList.getChildren().iterator(); i.hasNext(); /**/) + { + // output all remaining child components that are not UIColumn + UIComponent child = (UIComponent)i.next(); + if (child instanceof UIColumn == false) + { + Utils.encodeRecursive(context, child); + } + } + out.write(""); + } + + /** + * Renders the new post speech bubble + * + * @param context Faces context + * @param out The response writer + * @param node The Node for the row being rendered + * @param primaryColumn The primary column containing the message content + * @param actionsColumn The actions column containing all the actions + * @param columns All configured columns + */ + private void renderNewPostBubble(FacesContext context, ResponseWriter out, Node node, + UIColumn primaryColumn, UIColumn actionsColumn, UIColumn[] columns) throws IOException + { + out.write(""); + out.write("

"); + out.write((String)node.getProperties().get("creator")); + out.write("
"); + renderBubble(context, out, "orange", "#FCC75E", primaryColumn, actionsColumn, columns); + out.write("
"); + } + + /** + * Renders the reply to post speech bubble + * + * @param context Faces context + * @param out The response writer + * @param node The Node for the row being rendered + * @param primaryColumn The primary column containing the message content + * @param actionsColumn The actions column containing all the actions + * @param columns All configured columns + */ + private void renderReplyToBubble(FacesContext context, ResponseWriter out, Node node, + UIColumn primaryColumn, UIColumn actionsColumn, UIColumn[] columns) throws IOException + { + out.write(""); + out.write("
"); + renderBubble(context, out, "yellow", "#FFF5A3", primaryColumn, actionsColumn, columns); + out.write("
"); + out.write((String)node.getProperties().get("creator")); + out.write("
"); + } + + /** + * Renders the speech bubble + * + * @param context Faces context + * @param out The response writer + * @param colour The colour of the bubble + * @param titleBgColour The colour of the background of the title area (#rrggbb) + * @param primaryColumn The primary column containing the message content + * @param actionsColumn The actions column containing all the actions + * @param columns All configured columns + */ + private void renderBubble(FacesContext context, ResponseWriter out, + String colour, String titleBgColour, + UIColumn primaryColumn, UIColumn actionsColumn, UIColumn[] columns) + throws IOException + { + String contextPath = context.getExternalContext().getRequestContextPath(); + + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write(""); + out.write("
"); + + // render the header area with the configured columns + out.write(""); + + for (int i = 0; i < columns.length; i++) + { + UIColumn column = columns[i]; + + if (column.isRendered() == true && + column.getPrimary() == false && + column.getActions() == false) + { + // render the column header as the label + UIComponent header = column.getFacet("header"); + if (header != null) + { + out.write(""); + } + + // render the contents of the column + if (column.getChildCount() != 0) + { + out.write(""); + } + } + } + + // render the actions column + out.write("
"); + Utils.encodeRecursive(context, header); + out.write(""); + Utils.encodeRecursive(context, column); + out.write(""); + if (actionsColumn != null && actionsColumn.getChildCount() != 0) + { + Utils.encodeRecursive(context, actionsColumn); + } + out.write("
"); + + out.write("
"); + + // render the primary column + if (primaryColumn != null && primaryColumn.getChildCount() != 0) + { + Utils.encodeRecursive(context, primaryColumn); + } + + out.write("
"); + } + } +} diff --git a/source/java/org/alfresco/web/bean/ImportBean.java b/source/java/org/alfresco/web/bean/ImportBean.java new file mode 100644 index 0000000000..abc3bd32b3 --- /dev/null +++ b/source/java/org/alfresco/web/bean/ImportBean.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.File; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.ImporterActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Backing bean implementation for the Import dialog. + * + * @author gavinc + */ +public class ImportBean +{ + private static final Log logger = LogFactory.getLog(ImportBean.class); + + private static final String DEFAULT_OUTCOME = "browse"; + + private static final String MSG_ERROR = "error_import"; + private static final String MSG_ERROR_NO_FILE = "error_import_no_file"; + private static final String MSG_ERROR_EMPTY_FILE = "error_import_empty_file"; + + private BrowseBean browseBean; + private NodeService nodeService; + private ActionService actionService; + private ContentService contentService; + + private File file; + private String fileName; + private String encoding = "UTF-8"; + private boolean runInBackground = true; + + /** + * Performs the import operation using the current state of the bean + * + * @return The outcome + */ + public String performImport() + { + String outcome = DEFAULT_OUTCOME; + + if (logger.isDebugEnabled()) + logger.debug("Called import for file: " + this.file); + + if (this.file != null && this.file.exists()) + { + // check the file actually has contents + if (this.file.length() > 0) + { + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // first of all we need to add the uploaded ACP file to the repository + NodeRef acpNodeRef = addACPToRepository(context); + + // build the action params map based on the bean's current state + Map params = new HashMap(3); + params.put(ImporterActionExecuter.PARAM_DESTINATION_FOLDER, this.browseBean.getActionSpace().getNodeRef()); + params.put(ImporterActionExecuter.PARAM_ENCODING, this.encoding); + + // build the action to execute + Action action = this.actionService.createAction(ImporterActionExecuter.NAME, params); + action.setExecuteAsynchronously(this.runInBackground); + + // execute the action on the ACP file + this.actionService.executeAction(action, acpNodeRef); + + if (logger.isDebugEnabled()) + { + logger.debug("Executed import action with action params of " + params); + } + + // commit the transaction + tx.commit(); + + // reset the bean + reset(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR), e.toString()), e); + outcome = null; + } + } + else + { + Utils.addErrorMessage(Application.getMessage(FacesContext.getCurrentInstance(), MSG_ERROR_EMPTY_FILE)); + outcome = null; + } + } + else + { + Utils.addErrorMessage(Application.getMessage(FacesContext.getCurrentInstance(), MSG_ERROR_NO_FILE)); + outcome = null; + } + + return outcome; + } + + /** + * Action called when the dialog is cancelled, just resets the bean's state + * + * @return The outcome + */ + public String cancel() + { + reset(); + + return DEFAULT_OUTCOME; + } + + /** + * Resets the dialog state back to the default + */ + public void reset() + { + this.file = null; + this.fileName = null; + this.runInBackground = true; + + // delete the temporary file we uploaded earlier + if (this.file != null) + { + this.file.delete(); + } + + // remove the file upload bean from the session + FacesContext ctx = FacesContext.getCurrentInstance(); + ctx.getExternalContext().getSessionMap().remove(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + } + + /** + * @return Returns the message to display when a file has been uploaded + */ + public String getFileUploadSuccessMsg() + { + String msg = Application.getMessage(FacesContext.getCurrentInstance(), "file_upload_success"); + return MessageFormat.format(msg, new Object[] {getFileName()}); + } + + /** + * @return Returns the name of the file + */ + public String getFileName() + { + // try and retrieve the file and filename from the file upload bean + // representing the file we previously uploaded. + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + this.fileName = fileBean.getFileName(); + this.file = fileBean.getFile(); + } + + return this.fileName; + } + + /** + * Returns the encoding to use for the export + * + * @return The encoding + */ + public String getEncoding() + { + return this.encoding; + } + + /** + * Sets the encoding to use for the export package + * + * @param encoding The encoding + */ + public void setEncoding(String encoding) + { + this.encoding = encoding; + } + + /** + * Determines whether the import should run in the background + * + * @return true means the import will run in the background + */ + public boolean getRunInBackground() + { + return this.runInBackground; + } + + /** + * Determines whether the import will run in the background + * + * @param runInBackground true to run the import in the background + */ + public void setRunInBackground(boolean runInBackground) + { + this.runInBackground = runInBackground; + } + + /** + * Sets the BrowseBean instance to use to retrieve the current document + * + * @param browseBean BrowseBean instance + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * Sets the action service + * + * @param actionService the action service + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Sets the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the content service + * + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Adds the uploaded ACP file to the repository + * + * @param context Faces context + * @return NodeRef representing the ACP file in the repository + */ + private NodeRef addACPToRepository(FacesContext context) + { + // set the name for the new node + Map contentProps = new HashMap(1); + contentProps.put(ContentModel.PROP_NAME, this.fileName); + + // create the node to represent the zip file + String assocName = QName.createValidLocalName(this.fileName); + ChildAssociationRef assocRef = this.nodeService.createNode( + this.browseBean.getActionSpace().getNodeRef(), ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, assocName), + ContentModel.TYPE_CONTENT, contentProps); + + NodeRef acpNodeRef = assocRef.getChildRef(); + + // apply the titled aspect to behave in the web client + Map titledProps = new HashMap(3, 1.0f); + titledProps.put(ContentModel.PROP_TITLE, this.fileName); + titledProps.put(ContentModel.PROP_DESCRIPTION, Application.getMessage(context, "import_package_description")); + this.nodeService.addAspect(acpNodeRef, ContentModel.ASPECT_TITLED, titledProps); + + // add the content to the node + ContentWriter writer = this.contentService.getWriter(acpNodeRef, ContentModel.PROP_CONTENT, true); + writer.setEncoding(this.encoding); + writer.setMimetype(MimetypeMap.MIMETYPE_ACP); + writer.putContent(this.file); + + return acpNodeRef; + } +} diff --git a/source/java/org/alfresco/web/bean/LoginBean.java b/source/java/org/alfresco/web/bean/LoginBean.java new file mode 100644 index 0000000000..0cae177aa5 --- /dev/null +++ b/source/java/org/alfresco/web/bean/LoginBean.java @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.faces.application.FacesMessage; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; +import javax.faces.validator.ValidatorException; +import javax.portlet.PortletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.webdav.WebDAVServlet; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * JSF Managed Bean. Backs the "login.jsp" view to provide the form fields used + * to enter user data for login. Also contains bean methods to validate form + * fields and action event fired in response to the Login button being pressed. + * + * @author Kevin Roast + */ +public class LoginBean +{ + // ------------------------------------------------------------------------------ + // Managed bean properties + + /** + * @param authenticationService The AuthenticationService to set. + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /** + * @param personService The personService to set. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param nodeService The nodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param configService The ConfigService to set. + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /** + * @param fileFolderService The FileFolderService to set. + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * @param val Username from login dialog + */ + public void setUsername(String val) + { + this.username = val; + } + + /** + * @return The username string from login dialog + */ + public String getUsername() + { + return this.username; + } + + /** + * @param val Password from login dialog + */ + public void setPassword(String val) + { + this.password = val; + } + + /** + * @return The password string from login dialog + */ + public String getPassword() + { + return this.password; + } + + /** + * @return the available languages + */ + public SelectItem[] getLanguages() + { + ClientConfigElement config = (ClientConfigElement) this.configService.getGlobalConfig() + .getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + + List languages = config.getLanguages(); + SelectItem[] items = new SelectItem[languages.size()]; + int count = 0; + for (String locale : languages) + { + // get label associated to the locale + String label = config.getLabelForLanguage(locale); + + // set default selection + if (count == 0 && this.language == null) + { + // first try to get the language that the current user is using + Locale lastLocale = Application.getLanguage(FacesContext.getCurrentInstance()); + if (lastLocale != null) + { + this.language = lastLocale.toString(); + } + // else we default to the first item in the list + else + { + this.language = locale; + } + } + + items[count++] = new SelectItem(locale, label); + } + + return items; + } + + /** + * @return Returns the language selection. + */ + public String getLanguage() + { + return this.language; + } + + /** + * @param language The language selection to set. + */ + public void setLanguage(String language) + { + this.language = language; + Application.setLanguage(FacesContext.getCurrentInstance(), this.language); + } + + + // ------------------------------------------------------------------------------ + // Validator methods + + /** + * Validate password field data is acceptable + */ + public void validatePassword(FacesContext context, UIComponent component, Object value) + throws ValidatorException + { + String pass = (String) value; + if (pass.length() < 3 || pass.length() > 32) + { + String err = MessageFormat.format(Application.getMessage(context, MSG_PASSWORD_LENGTH), + new Object[]{3, 32}); + throw new ValidatorException(new FacesMessage(err)); + } + } + + /** + * Validate Username field data is acceptable + */ + public void validateUsername(FacesContext context, UIComponent component, Object value) + throws ValidatorException + { + String pass = (String) value; + if (pass.length() < 3 || pass.length() > 32) + { + String err = MessageFormat.format(Application.getMessage(context, MSG_USERNAME_LENGTH), + new Object[]{3, 32}); + throw new ValidatorException(new FacesMessage(err)); + } + } + + + // ------------------------------------------------------------------------------ + // Action event methods + + /** + * Login action handler + * + * @return outcome view name + */ + public String login() + { + String outcome = null; + + FacesContext fc = FacesContext.getCurrentInstance(); + + if (this.username != null && this.password != null) + { + // Authenticate via the authentication service, then save the details of user in an object + // in the session - this is used by the servlet filter etc. on each page to check for login + try + { + this.authenticationService.authenticate(this.username, this.password.toCharArray()); + + // setup User object and Home space ID + User user = new User(this.authenticationService.getCurrentUserName(), this.authenticationService.getCurrentTicket(), + personService.getPerson(this.username)); + NodeRef homeSpaceRef = (NodeRef) this.nodeService.getProperty(personService.getPerson(this.username), ContentModel.PROP_HOMEFOLDER); + + // check that the home space node exists - else user cannot login + if (this.nodeService.exists(homeSpaceRef) == false) + { + throw new InvalidNodeRefException(homeSpaceRef); + } + user.setHomeSpaceId(homeSpaceRef.getId()); + + // put the User object in the Session - the authentication servlet will then allow + // the app to continue without redirecting to the login page + Map session = fc.getExternalContext().getSessionMap(); + session.put(AuthenticationHelper.AUTHENTICATION_USER, user); + + // if an external outcome has been provided then use that, else use default + String externalOutcome = (String)fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_KEY); + if (externalOutcome != null) + { + // TODO: This is a quick solution. It would be better to specify the (identifier?) + // of a handler class that would be responsible for processing specific outcome arguments. + + if (logger.isDebugEnabled()) + logger.debug("External outcome found: " + externalOutcome); + + // setup is required for certain outcome requests + if (OUTCOME_DOCDETAILS.equals(externalOutcome)) + { + NodeRef nodeRef = null; + + String[] args = (String[]) fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_ARGS); + if (args[0].equals(WebDAVServlet.WEBDAV_PREFIX)) + { + nodeRef = resolveWebDAVPath(fc, args); + } + else if (args.length == 3) + { + StoreRef storeRef = new StoreRef(args[0], args[1]); + nodeRef = new NodeRef(storeRef, args[2]); + } + + if (nodeRef != null) + { + // setup the Document on the browse bean + // TODO: the browse bean should accept a full NodeRef - not just an ID + this.browseBean.setupContentAction(nodeRef.getId(), true); + } + } + else if (OUTCOME_SPACEDETAILS.equals(externalOutcome)) + { + NodeRef nodeRef = null; + + String[] args = (String[]) fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_ARGS); + if (args[0].equals(WebDAVServlet.WEBDAV_PREFIX)) + { + nodeRef = resolveWebDAVPath(fc, args); + } + else if (args.length == 3) + { + StoreRef storeRef = new StoreRef(args[0], args[1]); + nodeRef = new NodeRef(storeRef, args[2]); + } + + if (nodeRef != null) + { + // setup the Space on the browse bean + // TODO: the browse bean should accept a full NodeRef - not just an ID + this.browseBean.setupSpaceAction(nodeRef.getId(), true); + } + } + else if (OUTCOME_BROWSE.equals(externalOutcome)) + { + String[] args = (String[]) fc.getExternalContext().getSessionMap().get(LOGIN_OUTCOME_ARGS); + if (args != null) + { + NodeRef nodeRef = null; + int offset = 0; + if (args.length >= 3) + { + offset = args.length - 3; + StoreRef storeRef = new StoreRef(args[0+offset], args[1+offset]); + nodeRef = new NodeRef(storeRef, args[2+offset]); + + // setup the ref as current Id in the global navigation bean + this.navigator.setCurrentNodeId(nodeRef.getId()); + + // check for view mode first argument + if (args[0].equals(LOGIN_ARG_TEMPLATE)) + { + this.browseBean.setDashboardView(true); + // the above call will auto-navigate to the correct outcome - so we don't! + externalOutcome = null; + } + } + } + } + + fc.getExternalContext().getSessionMap().remove(LOGIN_OUTCOME_KEY); + return externalOutcome; + } + else + { + // if a redirect URL has been provided then use that + String redirectURL = (String)fc.getExternalContext().getSessionMap().get(LOGIN_REDIRECT_KEY); + if (redirectURL != null) + { + if (logger.isDebugEnabled()) + logger.debug("Redirect URL found: " + redirectURL); + + // remove URL from session + fc.getExternalContext().getSessionMap().remove(LOGIN_REDIRECT_KEY); + + try + { + fc.getExternalContext().redirect(redirectURL); + fc.responseComplete(); + return null; + } + catch (IOException ioErr) + { + logger.warn("Unable to redirect to url: " + redirectURL); + } + } + else + { + return "success"; + } + } + } + catch (AuthenticationException aerr) + { + Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_UNKNOWN_USER)); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(fc, + Repository.ERROR_NOHOME), refErr.getNodeRef().getId())); + } + } + else + { + Utils.addErrorMessage(Application.getMessage(fc, MSG_ERROR_MISSING)); + } + + return outcome; + } + + /** + * Invalidate ticket and logout user + */ + public String logout() + { + FacesContext context = FacesContext.getCurrentInstance(); + + Map session = context.getExternalContext().getSessionMap(); + User user = (User) session.get(AuthenticationHelper.AUTHENTICATION_USER); + + boolean alfrescoAuth = (session.get(LOGIN_EXTERNAL_AUTH) == null); + + // invalidate Session for this user + if (Application.inPortalServer() == false) + { + HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); + request.getSession().invalidate(); + } + else + { + PortletRequest request = (PortletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); + request.getPortletSession().invalidate(); + } + + // invalidate User ticket + if (user != null) + { + this.authenticationService.invalidateTicket(user.getTicket()); + } + + // set language to last used + if (this.language != null && this.language.length() != 0) + { + Application.setLanguage(context, this.language); + } + + return alfrescoAuth ? "logout" : "relogin"; + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Resolves the given path elements to a NodeRef in the current repository + * + * @param context Faces context + * @param args The elements of the path to lookup + */ + private NodeRef resolveWebDAVPath(FacesContext context, String[] args) + { + NodeRef nodeRef = null; + + List paths = new ArrayList(args.length-1); + + FileInfo file = null; + try + { + // create a list of path elements (decode the URL as we go) + for (int x = 1; x < args.length; x++) + { + paths.add(URLDecoder.decode(args[x], "UTF-8")); + } + + if (logger.isDebugEnabled()) + logger.debug("Attempting to resolve webdav path to NodeRef: " + paths); + + // get the company home node to start the search from + NodeRef companyHome = new NodeRef(Repository.getStoreRef(), + Application.getCompanyRootId()); + + file = this.fileFolderService.resolveNamePath(companyHome, paths); + nodeRef = file.getNodeRef(); + } + catch (UnsupportedEncodingException uee) + { + if (logger.isWarnEnabled()) + logger.warn("Failed to resolve webdav path", uee); + + nodeRef = null; + } + catch (FileNotFoundException fne) + { + if (logger.isWarnEnabled()) + logger.debug("Failed to resolve webdav path", fne); + + nodeRef = null; + } + + return nodeRef; + } + + + // ------------------------------------------------------------------------------ + // Private data + + private static final Log logger = LogFactory.getLog(LoginBean.class); + + /** I18N messages */ + private static final String MSG_ERROR_MISSING = "error_login_missing"; + private static final String MSG_ERROR_UNKNOWN_USER = "error_login_user"; + private static final String MSG_USERNAME_CHARS = "login_err_username_chars"; + private static final String MSG_USERNAME_LENGTH = "login_err_username_length"; + private static final String MSG_PASSWORD_CHARS = "login_err_password_chars"; + private static final String MSG_PASSWORD_LENGTH = "login_err_password_length"; + + public static final String LOGIN_REDIRECT_KEY = "_alfRedirect"; + public static final String LOGIN_OUTCOME_KEY = "_alfOutcome"; + public static final String LOGIN_OUTCOME_ARGS = "_alfOutcomeArgs"; + public static final String LOGIN_EXTERNAL_AUTH= "_alfExternalAuth"; + + public final static String OUTCOME_DOCDETAILS = "showDocDetails"; + public final static String OUTCOME_SPACEDETAILS = "showSpaceDetails"; + public final static String OUTCOME_BROWSE = "browse"; + + private static final String LOGIN_ARG_TEMPLATE = "template"; + + /** user name */ + private String username = null; + + /** password */ + private String password = null; + + /** language locale selection */ + private String language = null; + + /** PersonService bean reference */ + private PersonService personService; + + /** AuthenticationService bean reference */ + private AuthenticationService authenticationService; + + /** NodeService bean reference */ + private NodeService nodeService; + + /** The BrowseBean reference */ + private BrowseBean browseBean; + + /** The NavigationBean bean reference */ + private NavigationBean navigator; + + /** ConfigService bean reference */ + private ConfigService configService; + + /** FileFolderService bean reference */ + private FileFolderService fileFolderService; +} diff --git a/source/java/org/alfresco/web/bean/NavigationBean.java b/source/java/org/alfresco/web/bean/NavigationBean.java new file mode 100644 index 0000000000..86d30b93b2 --- /dev/null +++ b/source/java/org/alfresco/web/bean/NavigationBean.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.alfresco.config.ConfigService; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.CIFSServer; +import org.alfresco.filesys.server.filesys.DiskSharedDevice; +import org.alfresco.filesys.smb.server.repo.ContentContext; +import org.alfresco.filesys.smb.server.repo.ContentDiskInterface; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.web.app.AlfrescoNavigationHandler; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.bean.wizard.NewSpaceWizard; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.IBreadcrumbHandler; +import org.alfresco.web.ui.common.component.UIBreadcrumb; +import org.alfresco.web.ui.common.component.UIModeList; +import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler; +import org.alfresco.web.ui.repo.component.shelf.UIShelf; +import org.apache.log4j.Logger; + +/** + * @author Kevin Roast + */ +public class NavigationBean +{ + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The nodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService The searchService to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param namespaceService The namespaceService to set. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param cifsServer The cifsServer to set. + */ + public void setCifsServer(CIFSServer cifsServer) + { + this.cifsServer = cifsServer; + } + + /** + * @param contentDiskDriver The contentDiskDriver to set. + */ + public void setContentDiskDriver(ContentDiskInterface contentDiskDriver) + { + this.contentDiskDriver = contentDiskDriver; + } + + /** + * @param configService The ConfigService to set. + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /** + * @return the User object representing the current instance for this user + */ + public User getCurrentUser() + { + return Application.getCurrentUser(FacesContext.getCurrentInstance()); + } + + /** + * Return the expanded state of the Shelf panel wrapper component + * + * @return the expanded state of the Shelf panel wrapper component + */ + public boolean isShelfExpanded() + { + return this.shelfExpanded; + } + + /** + * Set the expanded state of the Shelf panel wrapper component + * + * @param expanded true to expanded the Shelf panel area, false to hide it + */ + public void setShelfExpanded(boolean expanded) + { + this.shelfExpanded = expanded; + } + + /** + * @return Returns the array containing the expanded state of the shelf items + */ + public boolean[] getShelfItemExpanded() + { + return this.shelfItemExpanded; + } + + /** + * @param shelfItemExpanded The array containing the expanded state of the shelf items + */ + public void setShelfItemExpanded(boolean[] shelfItemExpanded) + { + this.shelfItemExpanded = shelfItemExpanded; + } + + /** + * @return Returns the toolbar Location. + */ + public String getToolbarLocation() + { + return this.toolbarLocation; + } + + /** + * @param toolbarLocation The toolbar Location to set. + */ + public void setToolbarLocation(String toolbarLocation) + { + this.toolbarLocation = toolbarLocation; + } + + /** + * @return Returns the helpUrl. + */ + public String getHelpUrl() + { + if (this.clientConfig == null) + { + initFromClientConfig(); + } + return this.helpUrl; + } + + /** + * @param helpUrl The helpUrl to set. + */ + public void setHelpUrl(String helpUrl) + { + this.helpUrl = helpUrl; + } + + /** + * @return Returns the search context object if any. + */ + public SearchContext getSearchContext() + { + return this.searchContext; + } + + /** + * @param searchContext The search context object to set or null to clear search. + */ + public void setSearchContext(SearchContext searchContext) + { + this.searchContext = searchContext; + + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + + /** + * @return Returns the currently browsing node Id. + */ + public String getCurrentNodeId() + { + return this.currentNodeId; + } + + /** + * Set the node Id of the current folder/space container node. + *

+ * Setting this value causes the UI to update and display the specified node as current. + * + * @param currentNodeId The currently browsing node Id. + */ + public void setCurrentNodeId(String currentNodeId) + { + if (s_logger.isDebugEnabled()) + s_logger.debug("Setting current node id to: " + currentNodeId); + + if (currentNodeId == null) + { + throw new AlfrescoRuntimeException("Can not set the current node id to null"); + } + + // set the current Node Id for our UI context operations + this.currentNodeId = currentNodeId; + + // clear other context that is based on or relevant to the Node id + this.currentNode = null; + this.searchContext = null; + + // inform any interested beans that the UI needs updating after this change + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + + // clear current node context after the notify - this is to ensure that if any delegates + // performed operations on the current node, that we have fresh data for the next View + this.currentNode = null; + } + + /** + * @return true if the current node has a template view available + */ + public boolean getCurrentNodeHasTemplate() + { + boolean templateView = false; + Node node = getCurrentNode(); + if (node.hasAspect(ContentModel.ASPECT_TEMPLATABLE)) + { + NodeRef templateRef = (NodeRef)node.getProperties().get(ContentModel.PROP_TEMPLATE); + templateView = (templateRef != null && this.nodeService.exists(templateRef)); + } + return templateView; + } + + /** + * @return the NodeRef.toString() for the current node template view if it has one set + */ + public String getCurrentNodeTemplate() + { + String strRef = null; + if (getCurrentNodeHasTemplate() == true) + { + strRef = getCurrentNode().getProperties().get(ContentModel.PROP_TEMPLATE).toString(); + } + return strRef; + } + + /** + * Returns a model for use by a template on a space Dashboard page. + * + * @return model containing current current space info. + */ + public Map getTemplateModel() + { + HashMap model = new HashMap(1, 1.0f); + + FacesContext fc = FacesContext.getCurrentInstance(); + TemplateNode spaceNode = new TemplateNode(getCurrentNode().getNodeRef(), Repository.getServiceRegistry(fc), + new TemplateImageResolver() { + public String resolveImagePathForName(String filename, boolean small) { + return Utils.getFileTypeImage(filename, small); + } + }); + model.put("space", spaceNode); + + return model; + } + + /** + * Clear state so that the current node properties cache for the next time they are requested + */ + public void resetCurrentNodeProperties() + { + this.currentNode = null; + } + + /** + * @return The Map of properties for the current Node. + */ + public Map getNodeProperties() + { + return getCurrentNode().getProperties(); + } + + /** + * @return The current Node object for UI context operations + */ + public Node getCurrentNode() + { + if (this.currentNode == null) + { + if (this.currentNodeId == null) + { + throw new AlfrescoRuntimeException("Cannot retrieve current Node if NodeId is null!"); + } + + if (s_logger.isDebugEnabled()) + s_logger.debug("Caching properties for node id: " + this.currentNodeId); + + NodeRef nodeRef; + Node node; + Map props; + try + { + // build a node which components on the JSP page can bind too + nodeRef = new NodeRef(Repository.getStoreRef(), this.currentNodeId); + node = new Node(nodeRef); + + // early init properties for this node (by getProperties() call) + // resolve icon in-case one has not been set + props = node.getProperties(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), ERROR_DELETED_FOLDER), new Object[] {this.currentNodeId}) ); + + nodeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + node = new Node(nodeRef); + props = node.getProperties(); + } + String icon = (String)props.get("app:icon"); + props.put("icon", icon != null ? icon : NewSpaceWizard.SPACE_ICON_DEFAULT); + Path path = this.nodeService.getPath(nodeRef); + + // resolve CIFS network folder location for this node + + DiskSharedDevice diskShare = cifsServer.getConfiguration().getPrimaryFilesystem(); + + if (diskShare != null) + { + ContentContext contentCtx = (ContentContext) diskShare.getContext(); + NodeRef rootNode = contentCtx.getRootNode(); + String cifsPath = Repository.getNamePath(this.nodeService, path, rootNode, "\\", "file:///" + getCIFSServerPath(diskShare)); + + node.getProperties().put("cifsPath", cifsPath); + node.getProperties().put("cifsPathLabel", cifsPath.substring(8)); // strip file:/// part + } + + this.currentNode = node; + } + + return this.currentNode; + } + + /** + * @return Returns the breadcrumb handler elements representing the location path of the UI. + */ + public List getLocation() + { + if (this.location == null) + { + // init the location from the User object for the first time + User user = Application.getCurrentUser(FacesContext.getCurrentInstance()); + + NodeRef homeSpaceRef = new NodeRef(Repository.getStoreRef(), user.getHomeSpaceId()); + String homeSpaceName = Repository.getNameForNode(this.nodeService, homeSpaceRef); + + // set the current node to the users Home Space Id + setCurrentNodeId(user.getHomeSpaceId()); + + // setup the breadcrumb with the same location + List elements = new ArrayList(1); + elements.add(new NavigationBreadcrumbHandler(homeSpaceRef, homeSpaceName)); + setLocation(elements); + } + + return this.location; + } + + /** + * @param location The UI location representation to set. + */ + public void setLocation(List location) + { + this.location = location; + } + + /** + * Sets up the dispatch context so that the navigation handler knows + * what object is being acted upon + * + * @param node The node to be added to the dispatch context + */ + public void setupDispatchContext(Node node) + { + this.dispatchContext = node; + } + + /** + * Resets the dispatch context + */ + public void resetDispatchContext() + { + this.dispatchContext = null; + } + + /** + * Returns the node currently set in the dispatch context + * + * @return The node being dispatched or null if there is no + * dispatch context + */ + public Node getDispatchContextNode() + { + return this.dispatchContext; + } + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Action handler to toggle the expanded state of the shelf. + * The panel component wrapping the shelf area of the UI is value bound to the shelfExpanded property. + */ + public void toggleShelf(ActionEvent event) + { + this.shelfExpanded = !this.shelfExpanded; + } + + /** + * Action handler called after a Shelf Group has had its expanded state toggled by the user + */ + public void shelfGroupToggled(ActionEvent event) + { + UIShelf.ShelfEvent shelfEvent = (UIShelf.ShelfEvent)event; + this.shelfItemExpanded[shelfEvent.Index] = shelfEvent.Expanded; + } + + /** + * Action to change the toolbar location + * Currently this will changed the location from Company to the users Home space + */ + public void toolbarLocationChanged(ActionEvent event) + { + FacesContext context = FacesContext.getCurrentInstance(); + try + { + UIModeList locationList = (UIModeList)event.getComponent(); + String location = locationList.getValue().toString(); + setToolbarLocation(location); + + if (LOCATION_COMPANY.equals(location)) + { + List elements = new ArrayList(1); + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + String companySpaceName = Repository.getNameForNode(this.nodeService, companyRootRef); + elements.add(new NavigationBreadcrumbHandler(companyRootRef, companySpaceName)); + setLocation(elements); + setCurrentNodeId(companyRootRef.getId()); + } + else if (LOCATION_HOME.equals(location)) + { + List elements = new ArrayList(1); + String homeSpaceId = Application.getCurrentUser(context).getHomeSpaceId(); + NodeRef homeSpaceRef = new NodeRef(Repository.getStoreRef(), homeSpaceId); + String homeSpaceName = Repository.getNameForNode(this.nodeService, homeSpaceRef); + elements.add(new NavigationBreadcrumbHandler(homeSpaceRef, homeSpaceName)); + setLocation(elements); + setCurrentNodeId(homeSpaceRef.getId()); + } + + // we need to force a navigation to refresh the browse screen breadcrumb + context.getApplication().getNavigationHandler().handleNavigation(context, null, "browse"); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NOHOME), Application.getCurrentUser(context).getHomeSpaceId()), refErr ); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + } + } + + /** + * @param diskShare Filesystem shared device + * @return CIFS server path as network style string label + */ + public String getCIFSServerPath(DiskSharedDevice diskShare) + { + if (this.cifsServerPath == null) + { + StringBuilder buf = new StringBuilder(24); + + String serverName = this.cifsServer.getConfiguration().getServerName(); + if (serverName != null && serverName.length() != 0) + { + buf.append("\\\\") + .append(serverName) + .append("\\"); + buf.append(diskShare.getName()); + } + + this.cifsServerPath = buf.toString(); + } + + return this.cifsServerPath; + } + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Initialise default values from client configuration + */ + private void initFromClientConfig() + { + this.clientConfig = (ClientConfigElement)this.configService.getGlobalConfig().getConfigElement( + ClientConfigElement.CONFIG_ELEMENT_ID); + + this.helpUrl = clientConfig.getHelpUrl(); + } + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class to handle breadcrumb interaction for top-level navigation pages + */ + public class NavigationBreadcrumbHandler implements IRepoBreadcrumbHandler + { + private static final long serialVersionUID = 4833194653193016638L; + + /** + * Constructor + * + * @param label Element label + */ + public NavigationBreadcrumbHandler(NodeRef ref, String label) + { + this.label = label; + this.ref = ref; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return this.label; + } + + /** + * @see org.alfresco.web.ui.common.component.IBreadcrumbHandler#navigationOutcome(org.alfresco.web.ui.common.component.UIBreadcrumb) + */ + public String navigationOutcome(UIBreadcrumb breadcrumb) + { + // set the current node to the specified top level node ID + FacesContext fc = FacesContext.getCurrentInstance(); + setCurrentNodeId(ref.getId()); + setLocation( (List)breadcrumb.getValue() ); + + // setup the dispatch context + setupDispatchContext(new Node(ref)); + + if (fc.getViewRoot().getViewId().equals(BrowseBean.BROWSE_VIEW_ID)) + { + return null; + } + else + { + return "browse"; + } + } + + public NodeRef getNodeRef() + { + return this.ref; + } + + private String label; + private NodeRef ref; + } + + + // ------------------------------------------------------------------------------ + // Private data + + private static Logger s_logger = Logger.getLogger(NavigationBean.class); + + /** constant values used by the toolbar location modelist control */ + private static final String LOCATION_COMPANY = "company"; + private static final String LOCATION_HOME = "home"; + + private static final String ERROR_DELETED_FOLDER = "error_deleted_folder"; + + /** The NodeService to be used by the bean */ + private NodeService nodeService; + + /** The SearchService to be used by the bean */ + private SearchService searchService; + + /** NamespaceService bean reference */ + private NamespaceService namespaceService; + + /** CIFSServer bean reference */ + private CIFSServer cifsServer; + + /** CIFS content disk driver bean reference */ + private ContentDiskInterface contentDiskDriver; + + /** ConfigService bean reference */ + private ConfigService configService; + + /** Client configuration object */ + private ClientConfigElement clientConfig = null; + + /** Cached path to our CIFS server and top level node DIR */ + private String cifsServerPath; + + /** Node Id we are using for UI context operations */ + private String currentNodeId; + + /** Node we are using for UI context operations */ + private Node currentNode = null; + + /** Node we are using for dispatching */ + private Node dispatchContext = null; + + /** Current toolbar location */ + private String toolbarLocation = LOCATION_HOME; + + /** Search context object we are currently using or null for no search */ + private SearchContext searchContext; + + /** expanded state of the Shelf panel wrapper component */ + private boolean shelfExpanded = true; + + /** expanded state of the Shelf item components */ + private boolean[] shelfItemExpanded = new boolean[] {true, true, true, false, false}; + + /** list of the breadcrumb handler elements representing the location path of the UI */ + private List location = null; + + /** The client Help file url */ + private String helpUrl; +} diff --git a/source/java/org/alfresco/web/bean/RecentSpacesBean.java b/source/java/org/alfresco/web/bean/RecentSpacesBean.java new file mode 100644 index 0000000000..8b0542e76d --- /dev/null +++ b/source/java/org/alfresco/web/bean/RecentSpacesBean.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.alfresco.config.ConfigService; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.repo.component.shelf.UIRecentSpacesShelfItem; +import org.apache.log4j.Logger; + +/** + * This bean manages the real-time updated list of Recent Spaces in the Shelf component. + *

+ * Registers itself as a UI Context Listener so it is informed as to when the current Node ID + * has changed in the NavigationBeans. This is used to keep the list of spaces up-to-date. + * + * @author Kevin Roast + */ +public class RecentSpacesBean implements IContextListener +{ + private static Logger logger = Logger.getLogger(RecentSpacesBean.class); + + /** The NavigationBean reference */ + private NavigationBean navigator; + + /** The BrowseBean reference */ + private BrowseBean browseBean; + + /** ConfigService bean reference */ + private ConfigService configService; + + /** Maximum number of recent spaces to show */ + private Integer maxRecentSpaces = null; + + /** List of recent space nodes */ + private List recentSpaces = new LinkedList(); + + + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public RecentSpacesBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + } + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @param configService The ConfigService to set. + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /** + * @return the List of recent spaces + */ + public List getRecentSpaces() + { + return this.recentSpaces; + } + + /** + * @param spaces List of Nodes + */ + public void setRecentSpaces(List spaces) + { + this.recentSpaces = spaces; + } + + + // ------------------------------------------------------------------------------ + // Action method handlers + + /** + * Action handler bound to the recent spaces Shelf component called when a Space is clicked + */ + public void navigate(ActionEvent event) + { + // work out which node was clicked from the event data + UIRecentSpacesShelfItem.RecentSpacesEvent spaceEvent = (UIRecentSpacesShelfItem.RecentSpacesEvent)event; + Node selectedNode = this.recentSpaces.get(spaceEvent.Index); + NodeRef nodeRef = selectedNode.getNodeRef(); + try + { + // then navigate to the appropriate node in UI + // use browse bean functionality for this as it will update the breadcrumb for us + this.browseBean.updateUILocation(nodeRef); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {nodeRef.getId()}) ); + + // remove invalid node from recent spaces list + this.recentSpaces.remove(spaceEvent.Index); + } + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + // We use this listener handler to refresh the recent spaces list. At the point + // where this method is called, the current node Id in UI will probably have changed. + Node node = this.navigator.getCurrentNode(); + + // search for this node - if it's already in the list remove it so + // that it appears at the top for us + for (int i=0; i rules; + private Rule currentRule; + private UIRichList richList; + + /** + * Returns the current view mode the list of rules is in + * + * @return The current view mode + */ + public String getViewMode() + { + return this.viewMode; + } + + /** + * @return The space to work against + */ + public Node getSpace() + { + return this.browseBean.getActionSpace(); + } + + /** + * Returns the list of rules to display + * + * @return + */ + public List getRules() + { + boolean includeInherited = true; + + if (this.viewMode.equals(LOCAL)) + { + includeInherited = false; + } + + // get the rules from the repository + List repoRules = this.ruleService.getRules(getSpace().getNodeRef(), includeInherited); + this.rules = new ArrayList(repoRules.size()); + + // wrap them all passing the current space + for (Rule rule : repoRules) + { + WrappedRule wrapped = new WrappedRule(rule, getSpace().getNodeRef()); + this.rules.add(wrapped); + } + + return this.rules; + } + + /** + * Handles a rule being clicked ready for an action i.e. edit or delete + * + * @param event The event representing the click + */ + public void setupRuleAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Rule clicked, it's id is: " + id); + + this.currentRule = this.ruleService.getRule( + getSpace().getNodeRef(), id); + } + } + + /** + * Returns the current rule + * + * @return The current rule + */ + public Rule getCurrentRule() + { + return this.currentRule; + } + + /** + * Handler called upon the completion of the Delete Rule page + * + * @return outcome + */ + public String deleteOK() + { + String outcome = null; + + if (this.currentRule != null) + { + try + { + String ruleTitle = this.currentRule.getTitle(); + + this.ruleService.removeRule(getSpace().getNodeRef(), + this.currentRule); + + // clear the current rule + this.currentRule = null; + + // setting the outcome will show the browse view again + outcome = "manageRules"; + + if (logger.isDebugEnabled()) + logger.debug("Deleted rule '" + ruleTitle + "'"); + } + catch (Throwable err) + { + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_DELETE_RULE) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: deleteOK called without a current Rule!"); + } + + return outcome; + } + + /** + * Change the current view mode based on user selection + * + * @param event ActionEvent + */ + public void viewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + this.viewMode = viewList.getValue().toString(); + + // force the list to be re-queried when the page is refreshed + if (this.richList != null) + { + this.richList.setValue(null); + } + } + + /** + * Sets the UIRichList component being used by this backing bean + * + * @param richList UIRichList component + */ + public void setRichList(UIRichList richList) + { + this.richList = richList; + this.richList.setValue(null); + } + + /** + * Returns the UIRichList component being used by this backing bean + * + * @return UIRichList component + */ + public UIRichList getRichList() + { + return this.richList; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @param ruleService Sets the rule service to use + */ + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + + /** + * Inner class to wrap the Rule objects so we can expose a flag to indicate whether + * the rule is a local or inherited rule + */ + public class WrappedRule + { + private Rule rule; + private NodeRef ruleNode; + + /** + * Constructs a RuleWrapper object + * + * @param rule The rule we are wrapping + * @param ruleNode The node the rules belong to + */ + public WrappedRule(Rule rule, NodeRef ruleNode) + { + this.rule = rule; + this.ruleNode = ruleNode; + } + + /** + * Returns the rule being wrapped + * + * @return The wrapped Rule + */ + public Rule getRule() + { + return this.rule; + } + + /** + * Determines whether the current rule is a local rule or + * has been inherited from a parent + * + * @return true if the rule is defined on the current node + */ + public boolean getLocal() + { + return ruleNode.equals(this.rule.getOwningNodeRef()); + } + + /** Methods to support sorting of the rules list in a table */ + + /** + * Returns the rule id + * + * @return The id + */ + public String getId() + { + return this.rule.getId(); + } + + /** + * Returns the rule title + * + * @return The title + */ + public String getTitle() + { + return this.rule.getTitle(); + } + + /** + * Returns the rule description + * + * @return The description + */ + public String getDescription() + { + return this.rule.getDescription(); + } + + /** + * Returns the created date + * + * @return The created date + */ + public Date getCreatedDate() + { + return this.rule.getCreatedDate(); + } + + /** + * Returns the modfified date + * + * @return The modified date + */ + public Date getModifiedDate() + { + return this.rule.getModifiedDate(); + } + } +} diff --git a/source/java/org/alfresco/web/bean/SearchContext.java b/source/java/org/alfresco/web/bean/SearchContext.java new file mode 100644 index 0000000000..4384080094 --- /dev/null +++ b/source/java/org/alfresco/web/bean/SearchContext.java @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.faces.context.FacesContext; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.search.ISO9075; +import org.alfresco.repo.search.impl.lucene.QueryParser; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.bean.repository.Repository; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Holds the context required to build a search query and can return the populated query. + * + * @author Kevin Roast + */ +public final class SearchContext implements Serializable +{ + /** Search mode constants */ + public final static int SEARCH_ALL = 0; + public final static int SEARCH_FILE_NAMES_CONTENTS = 1; + public final static int SEARCH_FILE_NAMES = 2; + public final static int SEARCH_SPACE_NAMES = 3; + + /** the search text string */ + private String text = ""; + + /** mode for the search */ + private int mode = SearchContext.SEARCH_ALL; + + /** folder node location for the search */ + private String location = null; + + /** categories to add to the search */ + private String[] categories = new String[0]; + + /** true to search location children as well as location */ + private boolean locationChildren = true; + + /** true to search category children as well as category */ + private boolean categoryChildren = true; + + /** content type to restrict search against */ + private String contentType = null; + + /** content mimetype to restrict search against */ + private String mimeType = null; + + /** any extra query attributes to add to the search */ + private Map queryAttributes = new HashMap(5, 1.0f); + + /** any additional range attribute to add to the search */ + private Map rangeAttributes = new HashMap(5, 1.0f); + + /** any additional fixed value attributes to add to the search, such as boolean or noderef */ + private Map queryFixedValues = new HashMap(5, 1.0f); + + /** logger */ + private static Log logger = LogFactory.getLog(SearchContext.class); + + + /** + * Build the search query string based on the current search context members. + * + * @return prepared search query string + */ + public String buildQuery() + { + String query; + + // the QName for the well known "name" attribute + String nameAttr = Repository.escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name")); + + // match against content text + String text = this.text.trim(); + String fullTextQuery; + String nameAttrQuery; + + if (text.indexOf(' ') == -1) + { + // simple single word text search + if (text.charAt(0) != '*') + { + // escape characters and append the wildcard character + String safeText = QueryParser.escape(text); + fullTextQuery = " TEXT:" + safeText + '*'; + nameAttrQuery = " @" + nameAttr + ":" + safeText + '*'; + } + else + { + // found a leading wildcard - prepend it again after escaping the other characters + String safeText = QueryParser.escape(text.substring(1)); + fullTextQuery = " TEXT:*" + safeText + '*'; + nameAttrQuery = " @" + nameAttr + ":*" + safeText + '*'; + } + } + else + { + // multiple word search + if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') + { + // as quoted phrase + String quotedSafeText = '"' + QueryParser.escape(text.substring(1, text.length() - 1)) + '"'; + fullTextQuery = " TEXT:" + quotedSafeText; + nameAttrQuery = " @" + nameAttr + ":" + quotedSafeText; + } + else + { + // as individual search terms + StringTokenizer t = new StringTokenizer(text, " "); + StringBuilder fullTextBuf = new StringBuilder(64); + StringBuilder nameAttrBuf = new StringBuilder(64); + fullTextBuf.append('('); + nameAttrBuf.append('('); + while (t.hasMoreTokens()) + { + String term = t.nextToken(); + if (term.charAt(0) != '*') + { + String safeTerm = QueryParser.escape(term); + fullTextBuf.append("TEXT:").append(safeTerm).append('*'); + nameAttrBuf.append("@").append(nameAttr).append(":").append(safeTerm).append('*'); + } + else + { + String safeTerm = QueryParser.escape(term.substring(1)); + fullTextBuf.append("TEXT:*").append(safeTerm).append('*'); + nameAttrBuf.append("@").append(nameAttr).append(":*").append(safeTerm).append('*'); + } + if (t.hasMoreTokens()) + { + fullTextBuf.append(" OR "); + nameAttrBuf.append(" OR "); + } + } + fullTextBuf.append(')'); + nameAttrBuf.append(')'); + fullTextQuery = fullTextBuf.toString(); + nameAttrQuery = nameAttrBuf.toString(); + } + } + + // match a specific PATH for space location or categories + StringBuilder pathQuery = null; + if (location != null || (categories != null && categories.length !=0)) + { + pathQuery = new StringBuilder(128); + if (location != null) + { + pathQuery.append(" PATH:\"").append(location).append("\" "); + } + if (categories != null && categories.length != 0) + { + for (int i=0; i0) + { + elementString = '/' + (String)prefixes.iterator().next() + ':' + ISO9075.encode(elementRef.getQName().getLocalName()); + } + } + } + + buf.append(elementString); + } + if (children == true) + { + // append syntax to get all children of the path + buf.append("//*"); + } + else + { + // append syntax to just represent the path, not the children + buf.append("/*"); + } + + return buf.toString(); + } + + /** + * @return Returns the categories to use for the search + */ + public String[] getCategories() + { + return this.categories; + } + + /** + * @param categories The categories to set. + */ + public void setCategories(String[] categories) + { + if (categories != null) + { + this.categories = categories; + } + } + + /** + * @return Returns the node to search from or null for all. + */ + public String getLocation() + { + return this.location; + } + + /** + * @param location The node to search from or null for all.. + */ + public void setLocation(String location) + { + this.location = location; + } + + /** + * @return Returns the mode to use during the search (see constants) + */ + public int getMode() + { + return this.mode; + } + + /** + * @param mode The mode to use during the search (see constants) + */ + public void setMode(int mode) + { + this.mode = mode; + } + + /** + * @return Returns the search text string. + */ + public String getText() + { + return this.text; + } + + /** + * @param text The search text string. + */ + public void setText(String text) + { + this.text = text; + } + + /** + * @return Returns the contentType. + */ + public String getContentType() + { + return this.contentType; + } + + /** + * @param contentType The content type to restrict attribute search against. + */ + public void setContentType(String contentType) + { + this.contentType = contentType; + } + + /** + * @return Returns the mimeType. + */ + public String getMimeType() + { + return this.mimeType; + } + /** + * @param mimeType The mimeType to set. + */ + public void setMimeType(String mimeType) + { + this.mimeType = mimeType; + } + + /** + * @return Returns true to search location children, false for just the specified location. + */ + public boolean getLocationChildren() + { + return this.locationChildren; + } + + /** + * @param locationChildren True to search location children, false for just the specified location. + */ + public void setLocationChildren(boolean locationChildren) + { + this.locationChildren = locationChildren; + } + + /** + * @return Returns true to search category children, false for just the specified category. + */ + public boolean getCategoryChildren() + { + return this.categoryChildren; + } + + /** + * @param categoryChildren True to search category children, false for just the specified category. + */ + public void setCategoryChildren(boolean categoryChildren) + { + this.categoryChildren = categoryChildren; + } + + /** + * Add an additional attribute to search against + * + * @param qname QName of the attribute to search against + * @param value Value of the attribute to use + */ + public void addAttributeQuery(QName qname, String value) + { + this.queryAttributes.put(qname, value); + } + + /** + * Add an additional range attribute to search against + * + * @param qname QName of the attribute to search against + * @param lower Lower value for range + * @param upper Upper value for range + * @param inclusive True for inclusive within the range, false otherwise + */ + public void addRangeQuery(QName qname, String lower, String upper, boolean inclusive) + { + this.rangeAttributes.put(qname, new RangeProperties(qname, lower, upper, inclusive)); + } + + /** + * Add an additional fixed value attribute to search against + * + * @param qname QName of the attribute to search against + * @param value Fixed value of the attribute to use + */ + public void addFixedValueQuery(QName qname, String value) + { + this.queryFixedValues.put(qname, value); + } + + + /** + * Simple wrapper class for range query attribute properties + */ + private static class RangeProperties + { + QName qname; + String lower; + String upper; + boolean inclusive; + + RangeProperties(QName qname, String lower, String upper, boolean inclusive) + { + this.qname = qname; + this.lower = lower; + this.upper = upper; + this.inclusive = inclusive; + } + } +} diff --git a/source/java/org/alfresco/web/bean/SpaceDetailsBean.java b/source/java/org/alfresco/web/bean/SpaceDetailsBean.java new file mode 100644 index 0000000000..7598615a40 --- /dev/null +++ b/source/java/org/alfresco/web/bean/SpaceDetailsBean.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.SelectItem; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.Utils.URLMode; +import org.alfresco.web.ui.common.component.UIActionLink; + +/** + * Back bean provided access to the details of a Space + * + * @author Kevin Roast + */ +public class SpaceDetailsBean +{ + private static final String OUTCOME_RETURN = "showSpaceDetails"; + + /** BrowseBean instance */ + private BrowseBean browseBean; + + /** The NavigationBean bean reference */ + private NavigationBean navigator; + + /** PermissionService bean reference */ + private PermissionService permissionService; + + /** NodeServuce bean reference */ + private NodeService nodeService; + + /** Selected template Id */ + private String template; + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * Sets the BrowseBean instance to use to retrieve the current Space + * + * @param browseBean BrowseBean instance + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param nodeService The NodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param permissionService The PermissionService to set. + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Returns the Space this bean is currently representing + * + * @return The Space Node + */ + public Node getSpace() + { + return this.browseBean.getActionSpace(); + } + + /** + * Returns the id of the current document + * + * @return The id + */ + public String getId() + { + return getSpace().getId(); + } + + /** + * Returns the name of the current document + * + * @return Name of the current document + */ + public String getName() + { + return getSpace().getName(); + } + + /** + * Returns the WebDAV URL for the current document + * + * @return The WebDAV url + */ + public String getWebdavUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getSpace(), URLMode.WEBDAV); + } + + /** + * Returns the URL to access the details page for the current document + * + * @return The bookmark URL + */ + public String getBookmarkUrl() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getSpace(), URLMode.SHOW_DETAILS); + } + + /** + * Returns the CIFS path for the current document + * + * @return The CIFS path + */ + public String getCifsPath() + { + return Utils.generateURL(FacesContext.getCurrentInstance(), getSpace(), URLMode.CIFS); + } + + /** + * @return Returns the template Id. + */ + public String getTemplate() + { + // return current template if it exists + NodeRef ref = (NodeRef)getSpace().getProperties().get(ContentModel.PROP_TEMPLATE); + return ref != null ? ref.getId() : this.template; + } + + /** + * @param template The template Id to set. + */ + public void setTemplate(String template) + { + this.template = template; + } + + /** + * @return true if the current document has the 'templatable' aspect applied and + * references a template that currently exists in the system. + */ + public boolean isTemplatable() + { + NodeRef templateRef = (NodeRef)getSpace().getProperties().get(ContentModel.PROP_TEMPLATE); + return (getSpace().hasAspect(ContentModel.ASPECT_TEMPLATABLE) && + templateRef != null && nodeService.exists(templateRef)); + } + + /** + * @return String of the NodeRef for the dashboard template used by the space if any + */ + public String getTemplateRef() + { + NodeRef ref = (NodeRef)getSpace().getProperties().get(ContentModel.PROP_TEMPLATE); + return ref != null ? ref.toString() : null; + } + + /** + * Returns a model for use by a template on the Space Details page. + * + * @return model containing current current space info. + */ + public Map getTemplateModel() + { + HashMap model = new HashMap(1, 1.0f); + + FacesContext fc = FacesContext.getCurrentInstance(); + TemplateNode spaceNode = new TemplateNode(getSpace().getNodeRef(), Repository.getServiceRegistry(fc), + new TemplateImageResolver() { + public String resolveImagePathForName(String filename, boolean small) { + return Utils.getFileTypeImage(filename, small); + } + }); + model.put("space", spaceNode); + + return model; + } + + /** + * @return the list of available Content Templates that can be applied to the current document. + */ + public SelectItem[] getTemplates() + { + // get the template from the special Content Templates folder + FacesContext context = FacesContext.getCurrentInstance(); + String xpath = Application.getRootPath(context) + "/" + + Application.getGlossaryFolderName(context) + "/" + + Application.getContentTemplatesFolderName(context) + "//*"; + NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + NamespaceService resolver = Repository.getServiceRegistry(context).getNamespaceService(); + List results = Repository.getServiceRegistry(context).getSearchService().selectNodes( + rootNodeRef, xpath, null, resolver, false); + + List templates = new ArrayList(results.size()); + if (results.size() != 0) + { + DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService(); + for (NodeRef ref : results) + { + Node childNode = new Node(ref); + if (dd.isSubClass(childNode.getType(), ContentModel.TYPE_CONTENT)) + { + templates.add(new SelectItem(childNode.getId(), childNode.getName())); + } + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(templates, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + return templates.toArray(new SelectItem[templates.size()]); + } + + + // ------------------------------------------------------------------------------ + // Action event handlers + + /** + * Action handler to apply the selected Template and Templatable aspect to the current Space + */ + public String applyTemplate() + { + try + { + // apply the templatable aspect if required + if (getSpace().hasAspect(ContentModel.ASPECT_TEMPLATABLE) == false) + { + this.nodeService.addAspect(getSpace().getNodeRef(), ContentModel.ASPECT_TEMPLATABLE, null); + } + + // get the selected template from the Template Picker + NodeRef templateRef = new NodeRef(Repository.getStoreRef(), this.template); + + // set the template NodeRef into the templatable aspect property + this.nodeService.setProperty(getSpace().getNodeRef(), ContentModel.PROP_TEMPLATE, templateRef); + + // reset space details for next refresh of details page + getSpace().reset(); + } + catch (Exception e) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + } + return OUTCOME_RETURN; + } + + /** + * Action handler to remove a dashboard template from the current Space + */ + public String removeTemplate() + { + try + { + // clear template property + this.nodeService.setProperty(getSpace().getNodeRef(), ContentModel.PROP_TEMPLATE, null); + this.nodeService.removeAspect(getSpace().getNodeRef(), ContentModel.ASPECT_TEMPLATABLE); + + // reset space details for next refresh of details page + getSpace().reset(); + } + catch (Exception e) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + } + return OUTCOME_RETURN; + } + + /** + * Navigates to next item in the list of Spaces + */ + public void nextItem(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + List nodes = this.browseBean.getNodes(); + if (nodes.size() > 1) + { + // perform a linear search - this is slow but stateless + // otherwise we would have to manage state of last selected node + // this gets very tricky as this bean is instantiated once and never + // reset - it does not know when the document has changed etc. + for (int i=0; i params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + List nodes = this.browseBean.getNodes(); + if (nodes.size() > 1) + { + // see above + for (int i=0; i shortcuts = null; + + private QName QNAME_SHORTCUTS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "shortcuts"); + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @return the List of shortcut Nodes + */ + public List getShortcuts() + { + if (this.shortcuts == null) + { + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // get the shortcuts from the preferences for this user + NodeRef prefRef = getShortcutsNodeRef(); + List shortcuts = (List)this.nodeService.getProperty(prefRef, QNAME_SHORTCUTS); + if (shortcuts != null) + { + // each shortcut node ID is persisted as a list item in a well known property + this.shortcuts = new ArrayList(shortcuts.size()); + for (int i=0; i(this.shortcuts.size()); + for (int i=0; i(5); + } + + tx.commit(); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + + return this.shortcuts; + } + + /** + * @param spaces List of shortcuts Nodes + */ + public void setShortcuts(List nodes) + { + this.shortcuts = nodes; + } + + + // ------------------------------------------------------------------------------ + // Action method handlers + + /** + * Action handler called when a new shortcut is to be added to the list + */ + public void createShortcut(ActionEvent event) + { + // TODO: add this action to the Details screen for Space and Document + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + try + { + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + boolean foundShortcut = false; + for (int i=0; i shortcuts = (List)this.nodeService.getProperty(prefRef, QNAME_SHORTCUTS); + if (shortcuts == null) + { + shortcuts = new ArrayList(1); + } + shortcuts.add(node.getNodeRef().getId()); + this.nodeService.setProperty(prefRef, QNAME_SHORTCUTS, (Serializable)shortcuts); + + // commit the transaction + tx.commit(); + + // add our new shortcut Node to the in-memory list + getShortcuts().add(node); + + if (logger.isDebugEnabled()) + logger.debug("Added node: " + node.getName() + " to the user shortcuts list."); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + } + + /** + * Get the node we need to store our user preferences + */ + private NodeRef getShortcutsNodeRef() + { + return Application.getCurrentUser(FacesContext.getCurrentInstance()).getUserPreferencesRef(); + } + + /** + * Action handler bound to the user shortcuts Shelf component called when a node is removed + */ + public void removeShortcut(ActionEvent event) + { + UIShortcutsShelfItem.ShortcutEvent shortcutEvent = (UIShortcutsShelfItem.ShortcutEvent)event; + + // remove from persistent store + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + NodeRef prefRef = getShortcutsNodeRef(); + List shortcuts = (List)this.nodeService.getProperty(prefRef, QNAME_SHORTCUTS); + if (shortcuts != null && shortcuts.size() > shortcutEvent.Index) + { + // remove the shortcut from the saved list and persist back + shortcuts.remove(shortcutEvent.Index); + this.nodeService.setProperty(prefRef, QNAME_SHORTCUTS, (Serializable)shortcuts); + + // commit the transaction + tx.commit(); + + // remove shortcut Node from the in-memory list + Node node = getShortcuts().remove(shortcutEvent.Index); + + if (logger.isDebugEnabled()) + logger.debug("Removed node: " + node.getName() + " from the user shortcuts list."); + } + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + + /** + * Action handler bound to the user shortcuts Shelf component called when a node is clicked + */ + public void click(ActionEvent event) + { + // work out which node was clicked from the event data + UIShortcutsShelfItem.ShortcutEvent shortcutEvent = (UIShortcutsShelfItem.ShortcutEvent)event; + Node selectedNode = getShortcuts().get(shortcutEvent.Index); + + try + { + DictionaryService dd = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getDictionaryService(); + if (dd.isSubClass(selectedNode.getType(), ContentModel.TYPE_FOLDER)) + { + // then navigate to the appropriate node in UI + // use browse bean functionality for this as it will update the breadcrumb for us + this.browseBean.updateUILocation(selectedNode.getNodeRef()); + } + else if (dd.isSubClass(selectedNode.getType(), ContentModel.TYPE_CONTENT)) + { + // view details for document + this.browseBean.setupContentAction(selectedNode.getId(), true); + FacesContext fc = FacesContext.getCurrentInstance(); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "showDocDetails"); + } + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {selectedNode.getId()}) ); + + // remove item from the shortcut list + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + NodeRef prefRef = getShortcutsNodeRef(); + List shortcuts = (List)this.nodeService.getProperty(prefRef, QNAME_SHORTCUTS); + if (shortcuts != null && shortcuts.size() > shortcutEvent.Index) + { + // remove the shortcut from the saved list and persist back + shortcuts.remove(shortcutEvent.Index); + this.nodeService.setProperty(prefRef, QNAME_SHORTCUTS, (Serializable)shortcuts); + + // commit the transaction + tx.commit(); + + // remove shortcut Node from the in-memory list + Node node = getShortcuts().remove(shortcutEvent.Index); + + if (logger.isDebugEnabled()) + logger.debug("Removed deleted node: " + node.getName() + " from the user shortcuts list."); + } + } + catch (Exception err) + { + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + } +} diff --git a/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java b/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java new file mode 100644 index 0000000000..e507fdfc4c --- /dev/null +++ b/source/java/org/alfresco/web/bean/clipboard/ClipboardBean.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.clipboard; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.CopyService; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.repo.component.shelf.UIClipboardShelfItem; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Kevin Roast + */ +public class ClipboardBean +{ + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @return Returns the NodeService. + */ + public NodeService getNodeService() + { + return this.nodeService; + } + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @return Returns the NodeOperationsService. + */ + public CopyService getNodeOperationsService() + { + return this.nodeOperationsService; + } + + /** + * @param nodeOperationsService The NodeOperationsService to set. + */ + public void setNodeOperationsService(CopyService nodeOperationsService) + { + this.nodeOperationsService = nodeOperationsService; + } + + /** + * @return Returns the navigation bean instance. + */ + public NavigationBean getNavigator() + { + return this.navigator; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @return Returns the clipboard items. + */ + public List getItems() + { + return this.items; + } + + /** + * @param items The clipboard items to set. + */ + public void setItems(List items) + { + this.items = items; + } + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Action handler called to add a node to the clipboard for a Copy operation + */ + public void copyNode(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + addClipboardNode(id, ClipboardStatus.COPY); + } + } + + /** + * Action handler called to add a node to the clipboard for a Cut operation + */ + public void cutNode(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + addClipboardNode(id, ClipboardStatus.CUT); + } + } + + /** + * Action handler call from the browse screen to Paste All clipboard items into the current Space + */ + public void pasteAll(ActionEvent event) + { + performPasteItems(-1); + } + + /** + * Action handler called to paste one or all items from the clipboard + */ + public void pasteItem(ActionEvent event) + { + UIClipboardShelfItem.ClipboardEvent clipEvent = (UIClipboardShelfItem.ClipboardEvent)event; + + int index = clipEvent.Index; + if (index >= this.items.size()) + { + throw new IllegalStateException("Clipboard attempting paste a non existent item index: " + index); + } + + performPasteItems(index); + } + + /** + * Perform a paste for the specified clipboard item(s) + * + * @param index of clipboard item to paste or -1 for all + */ + private void performPasteItems(int index) + { + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (index == -1) + { + // paste all + for (int i=0; i newItems = new ArrayList(this.items.size()); + for (int i=0; i items = new ArrayList(4); +} diff --git a/source/java/org/alfresco/web/bean/clipboard/ClipboardItem.java b/source/java/org/alfresco/web/bean/clipboard/ClipboardItem.java new file mode 100644 index 0000000000..2a82910c93 --- /dev/null +++ b/source/java/org/alfresco/web/bean/clipboard/ClipboardItem.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.clipboard; + +import org.alfresco.web.bean.repository.Node; + +/** + * @author Kevin Roast + */ +public class ClipboardItem +{ + /** + * Constructor + * + * @param node The node on the clipboard + * @param mode The ClipboardStatus enum value + */ + public ClipboardItem(Node node, ClipboardStatus mode) + { + this.Node = node; + this.Mode = mode; + } + + /** + * Override equals() to compare NodeRefs + */ + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + if (obj instanceof ClipboardItem) + { + return ((ClipboardItem)obj).Node.getNodeRef().equals(Node.getNodeRef()); + } + else + { + return false; + } + } + + /** + * Override hashCode() to use the internal NodeRef hashcode instead + */ + public int hashCode() + { + return Node.getNodeRef().hashCode(); + } + + + public Node Node; + public ClipboardStatus Mode; +} diff --git a/source/java/org/alfresco/web/bean/clipboard/ClipboardStatus.java b/source/java/org/alfresco/web/bean/clipboard/ClipboardStatus.java new file mode 100644 index 0000000000..6e746efbf3 --- /dev/null +++ b/source/java/org/alfresco/web/bean/clipboard/ClipboardStatus.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.clipboard; + +/** + * @author Kevin Roast + * + * Clipboard status for an item. + */ +public enum ClipboardStatus {CUT, COPY} diff --git a/source/java/org/alfresco/web/bean/preview/BasePreviewBean.java b/source/java/org/alfresco/web/bean/preview/BasePreviewBean.java new file mode 100644 index 0000000000..b1d7ca09f6 --- /dev/null +++ b/source/java/org/alfresco/web/bean/preview/BasePreviewBean.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.preview; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.SelectItem; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; + +/** + * Backing bean for the Preview Document in Template action page + * + * @author Kevin Roast + */ +public abstract class BasePreviewBean +{ + private static final String NO_SELECTION = "none"; + + /** BrowseBean instance */ + protected BrowseBean browseBean; + + /** NodeService instance */ + protected NodeService nodeService; + + /** The SearchService instance */ + protected SearchService searchService; + + /** The NavigationBean bean reference */ + protected NavigationBean navigator; + + protected NodeRef template; + + + /** + * @param nodeService The nodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @param searchService The searchService to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * Returns the node this bean is currently working with + * + * @return The current Node + */ + public abstract Node getNode(); + + /** + * Returns the id of the current node + * + * @return The id + */ + public String getId() + { + return getNode().getId(); + } + + /** + * Returns the name of the current node + * + * @return Name of the current node + */ + public String getName() + { + return getNode().getName(); + } + + /** + * @return the list of available Content Templates that can be applied to the current document. + */ + public SelectItem[] getTemplates() + { + // TODO: could cache this last for say 1 minute before requerying + + // get the template from the special Content Templates folder + FacesContext context = FacesContext.getCurrentInstance(); + String xpath = Application.getRootPath(context) + "/" + + Application.getGlossaryFolderName(context) + "/" + + Application.getContentTemplatesFolderName(context) + "//*"; + NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + NamespaceService resolver = Repository.getServiceRegistry(context).getNamespaceService(); + List results = this.searchService.selectNodes(rootNodeRef, xpath, null, resolver, false); + + List templates = new ArrayList(results.size()); + if (results.size() != 0) + { + DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService(); + for (NodeRef ref : results) + { + Node childNode = new Node(ref); + if (dd.isSubClass(childNode.getType(), ContentModel.TYPE_CONTENT)) + { + templates.add(new SelectItem(childNode.getId(), childNode.getName())); + } + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(templates, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + // add an entry (at the start) to instruct the user to select a template + templates.add(0, new SelectItem(NO_SELECTION, Application.getMessage(FacesContext.getCurrentInstance(), "select_a_template"))); + + return templates.toArray(new SelectItem[templates.size()]); + } + + /** + * Returns a model for use by the template on the Preview page. + * + * @return model containing current document/space info. + */ + public abstract Map getTemplateModel(); + + /** Template Image resolver helper */ + protected TemplateImageResolver imageResolver = new TemplateImageResolver() + { + public String resolveImagePathForName(String filename, boolean small) + { + return Utils.getFileTypeImage(filename, small); + } + }; + + /** + * @return the current template as a full NodeRef + */ + public NodeRef getTemplateRef() + { + return this.template; + } + + /** + * @return Returns the template Id. + */ + public String getTemplate() + { + return (this.template != null ? this.template.getId() : null); + } + + /** + * @param template The template Id to set. + */ + public void setTemplate(String template) + { + if (template != null && template.equals(NO_SELECTION) == false) + { + this.template = new NodeRef(Repository.getStoreRef(), template); + } + } + + private int findNextPreviewNode(List nodes, int start) + { + // search from start to end of list + for (int i=start; i nodes, int start) + { + // search from start to beginning of list + for (int i=start; i>=0; i--) + { + Node next = nodes.get(i); + if (next.hasAspect(ContentModel.ASPECT_TEMPLATABLE)) + { + return i; + } + } + // end of list to start + 1 (to skip original node) + for (int i=nodes.size() - 1; i>start; i--) + { + Node next = nodes.get(i); + if (next.hasAspect(ContentModel.ASPECT_TEMPLATABLE)) + { + return i; + } + } + return -1; + } +} diff --git a/source/java/org/alfresco/web/bean/preview/DocumentPreviewBean.java b/source/java/org/alfresco/web/bean/preview/DocumentPreviewBean.java new file mode 100644 index 0000000000..99e2e1c5e2 --- /dev/null +++ b/source/java/org/alfresco/web/bean/preview/DocumentPreviewBean.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.preview; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.SelectItem; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; + +/** + * Backing bean for the Preview Document in Template action page + * + * @author Kevin Roast + */ +public class DocumentPreviewBean extends BasePreviewBean +{ + /** + * Returns the document this bean is currently representing + * + * @return The document Node + */ + public Node getNode() + { + return this.browseBean.getDocument(); + } + + /** + * Returns a model for use by a template on the Document Details page. + * + * @return model containing current document and current space info. + */ + public Map getTemplateModel() + { + HashMap model = new HashMap(3, 1.0f); + + FacesContext fc = FacesContext.getCurrentInstance(); + TemplateNode documentNode = new TemplateNode(getNode().getNodeRef(), + Repository.getServiceRegistry(fc), imageResolver); + model.put("document", documentNode); + TemplateNode spaceNode = new TemplateNode(this.navigator.getCurrentNode().getNodeRef(), + Repository.getServiceRegistry(fc), imageResolver); + model.put("space", spaceNode); + + return model; + } +} diff --git a/source/java/org/alfresco/web/bean/preview/SpacePreviewBean.java b/source/java/org/alfresco/web/bean/preview/SpacePreviewBean.java new file mode 100644 index 0000000000..a9cbba1611 --- /dev/null +++ b/source/java/org/alfresco/web/bean/preview/SpacePreviewBean.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.preview; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.SelectItem; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; + +/** + * Backing bean for the Preview Space in Template action page + * + * @author Kevin Roast + */ +public class SpacePreviewBean extends BasePreviewBean +{ + /** + * Returns the Space this bean is currently representing + * + * @return The Space Node + */ + public Node getNode() + { + return this.browseBean.getActionSpace(); + } + + /** + * Returns a model for use by a template on the Document Details page. + * + * @return model containing current document and current space info. + */ + public Map getTemplateModel() + { + HashMap model = new HashMap(3, 1.0f); + + FacesContext fc = FacesContext.getCurrentInstance(); + TemplateNode spaceNode = new TemplateNode(getNode().getNodeRef(), + Repository.getServiceRegistry(fc), imageResolver); + model.put("space", spaceNode); + + return model; + } +} diff --git a/source/java/org/alfresco/web/bean/repository/DataDictionary.java b/source/java/org/alfresco/web/bean/repository/DataDictionary.java new file mode 100644 index 0000000000..3d3927e19e --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/DataDictionary.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Lighweight client side representation of the repository data dictionary. + * This allows service calls to be kept to a minimum and for bean access, thus enabling JSF + * value binding expressions. + * + * @author gavinc + */ +public final class DataDictionary +{ + private static Log logger = LogFactory.getLog(DataDictionary.class); + private DictionaryService dictionaryService; + private NamespaceService namespaceService; + private Map types = new HashMap(11, 1.0f); + + /** + * Constructor + * + * @param dictionaryService The dictionary service to use to retrieve the data + */ + public DataDictionary(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Returns the type definition for the type represented by the given qname + * + * @param type The qname of the type to lookup the definition for + * @return The type definition for the requested type + */ + public TypeDefinition getTypeDef(QName type) + { + TypeDefinition typeDef = types.get(type); + + if (typeDef == null) + { + typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + types.put(type, typeDef); + } + } + + return typeDef; + } + + /** + * Returns the type definition for the type represented by the given qname + * and for all the given aspects + * + * @param type The type to retrieve the definition for + * @param optionalAspects A list of aspects to retrieve the definition for + * @return A unified type definition of the given type and aspects + */ + public TypeDefinition getTypeDef(QName type, Collection optionalAspects) + { + return this.dictionaryService.getAnonymousType(type, optionalAspects); + } + + /** + * Returns the property definition for the given property on the given node + * + * @param node The node from which to get the property + * @param property The property to find the definition for + * @return The property definition or null if the property is not known + */ + public PropertyDefinition getPropertyDefinition(Node node, String property) + { + PropertyDefinition propDef = null; + + TypeDefinition typeDef = getTypeDef(node.getType(), node.getAspects()); + + if (typeDef != null) + { + Map properties = typeDef.getProperties(); + propDef = properties.get(Repository.resolveToQName(property)); + } + + return propDef; + } + + /** + * Returns the association definition for the given association on the given node + * + * @param node The node from which to get the association + * @param association The association to find the definition for + * @return The association definition or null if the association is not known + */ + public AssociationDefinition getAssociationDefinition(Node node, String association) + { + AssociationDefinition assocDef = null; + + TypeDefinition typeDef = getTypeDef(node.getType(), node.getAspects()); + + if (typeDef != null) + { + Map assocs = typeDef.getAssociations(); + assocDef = assocs.get(Repository.resolveToQName(association)); + } + + return assocDef; + } +} diff --git a/source/java/org/alfresco/web/bean/repository/MapNode.java b/source/java/org/alfresco/web/bean/repository/MapNode.java new file mode 100644 index 0000000000..53efb4c9f1 --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/MapNode.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; + +/** + * Lighweight client side representation of a node held in the repository, which + * is modelled as a map for use in the data tables. + * + * @author gavinc + */ +public class MapNode extends Node implements Map +{ + private static final long serialVersionUID = 4051322327734433079L; + + private boolean propsInitialised = false; + + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + */ + public MapNode(NodeRef nodeRef) + { + super(nodeRef); + } + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + * @param nodeService The node service to use to retrieve data for this node + * @param initProps True to immediately init the properties of the node, false to do nothing + */ + public MapNode(NodeRef nodeRef, NodeService nodeService, boolean initProps) + { + super(nodeRef); + if (initProps == true) + { + getProperties(); + } + } + + + // ------------------------------------------------------------------------------ + // Map implementation - allows the Node bean to be accessed using JSF expression syntax + + /** + * @see java.util.Map#clear() + */ + public void clear() + { + getProperties().clear(); + } + + /** + * @see java.util.Map#containsKey(java.lang.Object) + */ + public boolean containsKey(Object key) + { + return getProperties().containsKey(key); + } + + /** + * @see java.util.Map#containsValue(java.lang.Object) + */ + public boolean containsValue(Object value) + { + return getProperties().containsKey(value); + } + + /** + * @see java.util.Map#entrySet() + */ + public Set entrySet() + { + return getProperties().entrySet(); + } + + /** + * @see java.util.Map#get(java.lang.Object) + */ + public Object get(Object key) + { + Object obj = null; + + // there are some things that aren't available as properties + // but from method calls, so for these handle them individually + Map props = getProperties(); + if (propsInitialised == false) + { + // well known properties required as publically accessable map attributes + props.put("id", this.getId()); + props.put("name", this.getName()); // TODO: perf test pulling back single prop here instead of all! + props.put("nodeRef", this.getNodeRef()); + + propsInitialised = true; + } + + return props.get(key); + } + + /** + * @see java.util.Map#isEmpty() + */ + public boolean isEmpty() + { + return getProperties().isEmpty(); + } + + /** + * @see java.util.Map#keySet() + */ + public Set keySet() + { + return getProperties().keySet(); + } + + /** + * @see java.util.Map#put(K, V) + */ + public Object put(String key, Object value) + { + return getProperties().put(key, value); + } + + /** + * @see java.util.Map#putAll(java.util.Map) + */ + public void putAll(Map t) + { + getProperties().putAll(t); + } + + /** + * @see java.util.Map#remove(java.lang.Object) + */ + public Object remove(Object key) + { + return getProperties().remove(key); + } + + /** + * @see java.util.Map#size() + */ + public int size() + { + return getProperties().size(); + } + + /** + * @see java.util.Map#values() + */ + public Collection values() + { + return getProperties().values(); + } +} diff --git a/source/java/org/alfresco/web/bean/repository/Node.java b/source/java/org/alfresco/web/bean/repository/Node.java new file mode 100644 index 0000000000..f257be38c4 --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/Node.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.faces.context.FacesContext; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Lighweight client side representation of a node held in the repository. + * + * @author gavinc + */ +public class Node implements Serializable +{ + private static final long serialVersionUID = 3544390322739034169L; + + protected static Log logger = LogFactory.getLog(Node.class); + + protected NodeRef nodeRef; + private String name; + private QName type; + private String path; + private String id; + private Set aspects = null; + private Map permissions; + protected QNameNodeMap properties; + protected boolean propsRetrieved = false; + protected ServiceRegistry services = null; + + private boolean childAssocsRetrieved = false; + private QNameNodeMap childAssociations; + private Map> childAssociationsAdded; + private Map> childAssociationsRemoved; + + private boolean assocsRetrieved = false; + private QNameNodeMap associations; + private Map> associationsAdded; + private Map> associationsRemoved; + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + */ + public Node(NodeRef nodeRef) + { + if (nodeRef == null) + { + throw new IllegalArgumentException("NodeRef must be supplied for creation of a Node."); + } + + this.nodeRef = nodeRef; + this.id = nodeRef.getId(); + + this.properties = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this); + } + + /** + * @return All the properties known about this node. + */ + public Map getProperties() + { + if (this.propsRetrieved == false) + { + Map props = getServiceRegistry().getNodeService().getProperties(this.nodeRef); + + for (QName qname: props.keySet()) + { + Serializable propValue = props.get(qname); + this.properties.put(qname.toString(), propValue); + } + + this.propsRetrieved = true; + } + + return this.properties; + } + + /** + * @return All the associations this node has as a Map, using the association + * type as the key + */ + public final Map getAssociations() + { + if (this.assocsRetrieved == false) + { + associations = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this); + + List assocs = getServiceRegistry().getNodeService().getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); + + for (AssociationRef assocRef: assocs) + { + String assocName = assocRef.getTypeQName().toString(); + + List list = (List)this.associations.get(assocName); + // create the list if this is first association with 'assocName' + if (list == null) + { + list = new ArrayList(); + this.associations.put(assocName, list); + } + + // add the association to the list + list.add(assocRef); + } + + this.assocsRetrieved = true; + } + + return this.associations; + } + + /** + * Returns all the associations added to this node in this UI session + * + * @return Map of Maps of AssociationRefs + */ + public final Map> getAddedAssociations() + { + if (this.associationsAdded == null) + { + this.associationsAdded = new HashMap>(); + } + return this.associationsAdded; + } + + /** + * Returns all the associations removed from this node is this UI session + * + * @return Map of Maps of AssociationRefs + */ + public final Map> getRemovedAssociations() + { + if (this.associationsRemoved == null) + { + this.associationsRemoved = new HashMap>(); + } + return this.associationsRemoved; + } + + /** + * @return All the child associations this node has as a Map, using the association + * type as the key + */ + public final Map getChildAssociations() + { + if (this.childAssocsRetrieved == false) + { + this.childAssociations = new QNameNodeMap(getServiceRegistry().getNamespaceService(), this); + + List assocs = getServiceRegistry().getNodeService().getChildAssocs(this.nodeRef); + + for (ChildAssociationRef assocRef: assocs) + { + String assocName = assocRef.getTypeQName().toString(); + + List list = (List)this.childAssociations.get(assocName); + // create the list if this is first association with 'assocName' + if (list == null) + { + list = new ArrayList(); + this.childAssociations.put(assocName, list); + } + + // add the association to the list + list.add(assocRef); + } + + this.childAssocsRetrieved = true; + } + + return this.childAssociations; + } + + /** + * Returns all the child associations added to this node in this UI session + * + * @return Map of Maps of ChildAssociationRefs + */ + public final Map> getAddedChildAssociations() + { + if (this.childAssociationsAdded == null) + { + this.childAssociationsAdded = new HashMap>(); + } + return this.childAssociationsAdded; + } + + /** + * Returns all the child associations removed from this node is this UI session + * + * @return Map of Maps of ChildAssociationRefs + */ + public final Map> getRemovedChildAssociations() + { + if (this.childAssociationsRemoved == null) + { + this.childAssociationsRemoved = new HashMap>(); + } + return this.childAssociationsRemoved; + } + + /** + * Register a property resolver for the named property. + * + * @param name Name of the property this resolver is for + * @param resolver Property resolver to register + */ + public final void addPropertyResolver(String name, NodePropertyResolver resolver) + { + this.properties.addPropertyResolver(name, resolver); + } + + /** + * Determines whether the given property name is held by this node + * + * @param propertyName Property to test existence of + * @return true if property exists, false otherwise + */ + public final boolean hasProperty(String propertyName) + { + return getProperties().containsKey(propertyName); + } + + /** + * @return Returns the NodeRef this Node object represents + */ + public final NodeRef getNodeRef() + { + return this.nodeRef; + } + + /** + * @return Returns the type. + */ + public final QName getType() + { + if (this.type == null) + { + this.type = getServiceRegistry().getNodeService().getType(this.nodeRef); + } + + return type; + } + + /** + * @return The display name for the node + */ + public final String getName() + { + if (this.name == null) + { + // try and get the name from the properties first + this.name = (String)getProperties().get("cm:name"); + + // if we didn't find it as a property get the name from the association name + if (this.name == null) + { + this.name = getServiceRegistry().getNodeService().getPrimaryParent(this.nodeRef).getQName().getLocalName(); + } + } + + return this.name; + } + + /** + * @return The list of aspects applied to this node + */ + public final Set getAspects() + { + if (this.aspects == null) + { + this.aspects = getServiceRegistry().getNodeService().getAspects(this.nodeRef); + } + + return this.aspects; + } + + /** + * @param aspect The aspect to test for + * @return true if the node has the aspect false otherwise + */ + public final boolean hasAspect(QName aspect) + { + Set aspects = getAspects(); + return aspects.contains(aspect); + } + + /** + * Return whether the current user has the specified access permission on this Node + * + * @param permission Permission to validate against + * + * @return true if the permission is applied to the node for this user, false otherwise + */ + public final boolean hasPermission(String permission) + { + Boolean valid = null; + if (permissions != null) + { + valid = permissions.get(permission); + } + else + { + permissions = new HashMap(5, 1.0f); + } + + if (valid == null) + { + PermissionService service = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getPermissionService(); + valid = Boolean.valueOf(service.hasPermission(this.nodeRef, permission) == AccessStatus.ALLOWED); + permissions.put(permission, valid); + } + + return valid.booleanValue(); + } + + /** + * @return The GUID for the node + */ + public final String getId() + { + return this.id; + } + + /** + * @return The path for the node + */ + public final String getPath() + { + if (this.path == null) + { + this.path = getServiceRegistry().getNodeService().getPath(this.nodeRef).toString(); + } + + return this.path; + } + + /** + * Resets the state of the node to force re-retrieval of the data + */ + public void reset() + { + this.name = null; + this.type = null; + this.path = null; + this.properties.clear(); + this.propsRetrieved = false; + this.aspects = null; + this.permissions = null; + + this.associations = null; + this.associationsAdded = null; + this.associationsRemoved = null; + this.assocsRetrieved = false; + + this.childAssociations = null; + this.childAssociationsAdded = null; + this.childAssociationsRemoved = null; + this.childAssocsRetrieved = false; + } + + /** + * Override Object.toString() to provide useful debug output + */ + public String toString() + { + if (getServiceRegistry().getNodeService() != null) + { + if (getServiceRegistry().getNodeService().exists(nodeRef)) + { + return "Node Type: " + getType() + + "\nNode Properties: " + this.getProperties().toString() + + "\nNode Aspects: " + this.getAspects().toString(); + } + else + { + return "Node no longer exists: " + nodeRef; + } + } + else + { + return super.toString(); + } + } + + protected ServiceRegistry getServiceRegistry() + { + if (this.services == null) + { + this.services = Repository.getServiceRegistry(FacesContext.getCurrentInstance()); + } + return this.services; + } +} diff --git a/source/java/org/alfresco/web/bean/repository/NodePropertyResolver.java b/source/java/org/alfresco/web/bean/repository/NodePropertyResolver.java new file mode 100644 index 0000000000..a56ee1e8fa --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/NodePropertyResolver.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +/** + * Simple interface used to implement small classes capable of calculating dynamic property values + * for Nodes at runtime. This allows bean responsible for building large lists of Nodes to + * encapsulate the code needed to retrieve non-standard Node properties. The values are then + * calculated on demand by the property resolver. + * + * When a node is reset() the standard and other props are cleared. If property resolvers are used + * then the non-standard props will be restored automatically as well. + * + * @author Kevin Roast + */ +public interface NodePropertyResolver +{ + /** + * Get the property value for this resolver + * + * @param node Node this property is for + * + * @return property value + */ + public Object get(Node node); +} diff --git a/source/java/org/alfresco/web/bean/repository/QNameNodeMap.java b/source/java/org/alfresco/web/bean/repository/QNameNodeMap.java new file mode 100644 index 0000000000..b8d9bb53ad --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/QNameNodeMap.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QNameMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A extension of the repo QNameMap to provide custom property resolving support for Node wrappers. + * + * @author Kevin Roast + */ +public final class QNameNodeMap extends QNameMap implements Map, Cloneable +{ + private Node parent = null; + private Map resolvers = new HashMap(11, 1.0f); + + /** + * Constructor + * + * @param parent Parent Node of the QNameNodeMap + */ + public QNameNodeMap(NamespacePrefixResolver resolver, Node parent) + { + super(resolver); + if (parent == null) + { + throw new IllegalArgumentException("Parent Node cannot be null!"); + } + this.parent = parent; + } + + /** + * Register a property resolver for the named property. + * + * @param name Name of the property this resolver is for + * @param resolver Property resolver to register + */ + public void addPropertyResolver(String name, NodePropertyResolver resolver) + { + this.resolvers.put(name, resolver); + } + + /** + * @see java.util.Map#containsKey(java.lang.Object) + */ + public boolean containsKey(Object key) + { + return (this.contents.containsKey(Repository.resolveToQNameString((String)key)) || + this.resolvers.containsKey(key)); + } + + /** + * @see java.util.Map#get(java.lang.Object) + */ + public Object get(Object key) + { + String qnameKey = Repository.resolveToQNameString(key.toString()); + Object obj = this.contents.get(qnameKey); + if (obj == null) + { + // if a property resolver exists for this property name then invoke it + NodePropertyResolver resolver = this.resolvers.get(key.toString()); + if (resolver != null) + { + obj = resolver.get(this.parent); + // cache the result + // obviously the cache is useless if the result is null, in most cases it shouldn't be + this.contents.put(qnameKey, obj); + } + } + + return obj; + } + + /** + * Perform a get without using property resolvers + * + * @param key item key + * @return object + */ + public Object getRaw(Object key) + { + return this.contents.get(Repository.resolveToQNameString((String)key)); + } + + /** + * Shallow copy the map by copying keys and values into a new QNameNodeMap + */ + public Object clone() + { + QNameNodeMap map = new QNameNodeMap(this.resolver, this.parent); + map.putAll(this); + if (this.resolvers.size() != 0) + { + map.resolvers = (Map)((HashMap)this.resolvers).clone(); + } + return map; + } +} diff --git a/source/java/org/alfresco/web/bean/repository/Repository.java b/source/java/org/alfresco/web/bean/repository/Repository.java new file mode 100644 index 0000000000..be89f7b02e --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/Repository.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.servlet.ServletContext; +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.configuration.ConfigurableService; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.metadata.MetadataExtracter; +import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.app.Application; +import org.alfresco.web.ui.common.Utils; +import org.apache.log4j.Logger; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Helper class for accessing repository objects, convert values, escape values and service utilities. + * + * @author gavinc + * @author kevinr + */ +public final class Repository +{ + /** I18N error messages */ + public static final String ERROR_NODEREF = "error_noderef"; + public static final String ERROR_GENERIC = "error_generic"; + public static final String ERROR_NOHOME = "error_homespace"; + public static final String ERROR_SEARCH = "error_search"; + + private static final String METADATA_EXTACTER_REGISTRY = "metadataExtracterRegistry"; + + private static Logger logger = Logger.getLogger(Repository.class); + + /** cache of client StoreRef */ + private static StoreRef storeRef = null; + + /** reference to Person folder */ + private static NodeRef peopleRef = null; + + /** reference to System folder */ + private static NodeRef systemRef = null; + + /** reference to the namespace service */ + private static NamespaceService namespaceService = null; + + /** + * Private constructor + */ + private Repository() + { + } + + /** + * Returns a store reference object + * + * @return A StoreRef object + */ + public static StoreRef getStoreRef() + { + return storeRef; + } + + /** + * Returns a store reference object. + * This method is used to setup the cached value by the ContextListener initialisation methods + * + * @return The StoreRef object + */ + public static StoreRef getStoreRef(ServletContext context) + { + storeRef = Application.getRepositoryStoreRef(context); + + return storeRef; + } + + /** + * Helper to get the display name for a Node. + * The method will attempt to use the "name" attribute, if not found it will revert to using + * the QName.getLocalName() retrieved from the primary parent relationship. + * + * @param ref NodeRef + * + * @return display name string for the specified Node. + */ + public static String getNameForNode(NodeService nodeService, NodeRef ref) + { + String name = null; + + // try to find a display "name" property for this node + Object nameProp = nodeService.getProperty(ref, ContentModel.PROP_NAME); + if (nameProp != null) + { + name = nameProp.toString(); + } + else + { + // revert to using QName if not found + QName qname = nodeService.getPrimaryParent(ref).getQName(); + if (qname != null) + { + name = qname.getLocalName(); + } + } + + return name; + } + + /** + * Escape a QName value so it can be used in lucene search strings + * + * @param qName QName to escape + * + * @return escaped value + */ + public static String escapeQName(QName qName) + { + String string = qName.toString(); + StringBuilder buf = new StringBuilder(string.length() + 4); + for (int i = 0; i < string.length(); i++) + { + char c = string.charAt(i); + if ((c == '{') || (c == '}') || (c == ':') || (c == '-')) + { + buf.append('\\'); + } + + buf.append(c); + } + return buf.toString(); + } + + /** + * Return whether a Node is currently locked + * + * @param node The Node wrapper to test against + * @param lockService The LockService to use + * + * @return whether a Node is currently locked + */ + public static Boolean isNodeLocked(Node node, LockService lockService) + { + Boolean locked = Boolean.FALSE; + + if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) + { + LockStatus lockStatus = lockService.getLockStatus(node.getNodeRef()); + if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) + { + locked = Boolean.TRUE; + } + } + + return locked; + } + + /** + * Return whether a Node is currently locked by the current user + * + * @param node The Node wrapper to test against + * @param lockService The LockService to use + * + * @return whether a Node is currently locked by the current user + */ + public static Boolean isNodeOwnerLocked(Node node, LockService lockService) + { + Boolean locked = Boolean.FALSE; + + if (node.hasAspect(ContentModel.ASPECT_LOCKABLE) && + lockService.getLockStatus(node.getNodeRef()) == LockStatus.LOCK_OWNER) + { + locked = Boolean.TRUE; + } + + return locked; + } + + /** + * Return whether a WorkingCopy Node is owned by the current User + * + * @param node The Node wrapper to test against + * @param lockService The LockService to use + * + * @return whether a WorkingCopy Node is owned by the current User + */ + public static Boolean isNodeOwner(Node node, LockService lockService) + { + Boolean locked = Boolean.FALSE; + + if (node.hasAspect(ContentModel.ASPECT_WORKING_COPY)) + { + Object obj = node.getProperties().get("workingCopyOwner"); + if (obj instanceof String) + { + User user = Application.getCurrentUser(FacesContext.getCurrentInstance()); + if ( ((String)obj).equals(user.getUserName())) + { + locked = Boolean.TRUE; + } + } + } + + return locked; + } + + /** + * Return the human readable form of the specified node Path. Fast version of the method that + * simply converts QName localname components to Strings. + * + * @param path Path to extract readable form from, excluding the final element + * + * @return human readable form of the Path excluding the final element + */ + public static String getDisplayPath(Path path) + { + StringBuilder buf = new StringBuilder(64); + + for (int i=0; i + * The file extension will be extracted from the filename and used to lookup the mimetype. + * + * @param context FacesContext + * @param filename Non-null filename to process + * + * @return mimetype for the specified filename - falls back to 'application/octet-stream' if not found. + */ + public static String getMimeTypeForFileName(FacesContext context, String filename) + { + // base the mimetype from the file extension + MimetypeService mimetypeService = (MimetypeService)getServiceRegistry(context).getMimetypeService(); + + // fall back to binary mimetype if no match found + String mimetype = MimetypeMap.MIMETYPE_BINARY; + int extIndex = filename.lastIndexOf('.'); + if (extIndex != -1) + { + String ext = filename.substring(extIndex + 1).toLowerCase(); + String mt = mimetypeService.getMimetypesByExtension().get(ext); + if (mt != null) + { + mimetype = mt; + } + } + + return mimetype; + } + + /** + * Return a UserTransaction instance + * + * @param context FacesContext + * + * @return UserTransaction + */ + public static UserTransaction getUserTransaction(FacesContext context) + { + TransactionService transactionService = getServiceRegistry(context).getTransactionService(); + return transactionService.getUserTransaction(); + } + + /** + * Return a UserTransaction instance + * + * @param context FacesContext + * @param readonly Transaction readonly state + * + * @return UserTransaction + */ + public static UserTransaction getUserTransaction(FacesContext context, boolean readonly) + { + TransactionService transactionService = getServiceRegistry(context).getTransactionService(); + return transactionService.getUserTransaction(readonly); + } + + /** + * Return the Repository Service Registry + * + * @param context Faces Context + * @return the Service Registry + */ + public static ServiceRegistry getServiceRegistry(FacesContext context) + { + return (ServiceRegistry)FacesContextUtils.getRequiredWebApplicationContext( + context).getBean(ServiceRegistry.SERVICE_REGISTRY); + } + + /** + * Return the Repository Service Registry + * + * @param context Servlet Context + * @return the Service Registry + */ + public static ServiceRegistry getServiceRegistry(ServletContext context) + { + return (ServiceRegistry)WebApplicationContextUtils.getRequiredWebApplicationContext( + context).getBean(ServiceRegistry.SERVICE_REGISTRY); + } + + /** + * Return the Configurable Service + * + * @return the configurable service + */ + public static ConfigurableService getConfigurableService(FacesContext context) + { + return (ConfigurableService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("configurableService"); + } + + /** + * Return the Metadata Extracter Registry + * + * @param context Faces Context + * @return the MetadataExtracterRegistry + */ + public static MetadataExtracterRegistry getMetadataExtracterRegistry(FacesContext context) + { + return (MetadataExtracterRegistry)FacesContextUtils.getRequiredWebApplicationContext( + context).getBean(METADATA_EXTACTER_REGISTRY); + } + + /** + * Extracts the metadata of a "raw" piece of content into a map. + * + * @param context Faces Context + * @param reader Content reader for the source content to extract from + * @param destination Map of metadata to set metadata values into + * @return True if an extracter was found + */ + public static boolean extractMetadata(FacesContext context, ContentReader reader, Map destination) + { + // check that source mimetype is available + String mimetype = reader.getMimetype(); + if (mimetype == null) + { + throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); + } + + // look for a transformer + MetadataExtracter extracter = getMetadataExtracterRegistry(context).getExtracter(mimetype); + if (extracter == null) + { + // No metadata extracter is not a failure, but we flag it + return false; + } + + // we have a transformer, so do it + extracter.extract(reader, destination); + return true; + } + + /** + * Query a list of Person type nodes from the repo + * It is currently assumed that all Person nodes exist below the Repository root node + * + * @param context Faces Context + * @param nodeService The node service + * @param searchService used to perform the search + * @return List of Person node objects + */ + public static List getUsers(FacesContext context, NodeService nodeService, SearchService searchService) + { + List personNodes = null; + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + PersonService personService = (PersonService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("personService"); + NodeRef peopleRef = personService.getPeopleContainer(); + + // TODO: better to perform an XPath search or a get for a specific child type here? + List childRefs = nodeService.getChildAssocs(peopleRef); + personNodes = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref: childRefs) + { + // create our Node representation from the NodeRef + NodeRef nodeRef = ref.getChildRef(); + + if (nodeService.getType(nodeRef).equals(ContentModel.TYPE_PERSON)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef); + + // set data binding properties + // this will also force initialisation of the props now during the UserTransaction + // it is much better for performance to do this now rather than during page bind + Map props = node.getProperties(); + props.put("fullName", ((String)props.get("firstName")) + ' ' + ((String)props.get("lastName"))); + NodeRef homeFolderNodeRef = (NodeRef)props.get("homeFolder"); + if (homeFolderNodeRef != null) + { + props.put("homeSpace", homeFolderNodeRef); + } + + personNodes.add(node); + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_NODEREF), new Object[] {"root"}) ); + personNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_GENERIC), err.getMessage()), err ); + personNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + return personNodes; + } + + /** + * Convert a property of unknown type to a String value. A native String value will be + * returned directly, else toString() will be executed, null is returned as null. + * + * @param value Property value + * + * @return value to String or null + */ + public static String safePropertyToString(Serializable value) + { + if (value == null) + { + return null; + } + else if (value instanceof String) + { + return (String)value; + } + else + { + return value.toString(); + } + } + + /** + * Creates a QName representation for the given String. + * If the String has no namespace the Alfresco namespace is added. + * If the String has a prefix an attempt to resolve the prefix to the + * full URI will be made. + * + * @param str The string to convert + * @return A QName representation of the given string + */ + public static QName resolveToQName(String str) + { + return QName.resolveToQName(getNamespaceService(), str); + } + + /** + * Creates a string representation of a QName for the given string. + * If the given string already has a namespace, either a URL or a prefix, + * nothing the given string is returned. If it does not have a namespace + * the Alfresco namespace is added. + * + * @param str The string to convert + * @return A QName String representation of the given string + */ + public static String resolveToQNameString(String str) + { + return QName.resolveToQNameString(getNamespaceService(), str); + } + + /** + * Returns an instance of the namespace service + * + * @return The NamespaceService + */ + private static NamespaceService getNamespaceService() + { + if (namespaceService == null) + { + ServiceRegistry svcReg = getServiceRegistry(FacesContext.getCurrentInstance()); + namespaceService = svcReg.getNamespaceService(); + } + + return namespaceService; + } +} diff --git a/source/java/org/alfresco/web/bean/repository/User.java b/source/java/org/alfresco/web/bean/repository/User.java new file mode 100644 index 0000000000..8159119c6f --- /dev/null +++ b/source/java/org/alfresco/web/bean/repository/User.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.repository; + +import java.util.List; + +import javax.faces.context.FacesContext; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.configuration.ConfigurableService; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; + +/** + * Bean that represents the currently logged in user + * + * @author gavinc + */ +public final class User +{ + private String homeSpaceId; + private String userName; + private String ticket; + private NodeRef person; + private String fullName = null; + private Boolean administrator = null; + + /** cached ref to our user preferences node */ + private NodeRef preferencesFolderRef = null; + + /** + * Constructor + * + * @param userName constructor for the user + */ + public User(String userName, String ticket, NodeRef person) + { + if (userName == null || ticket == null || person == null) + { + throw new IllegalArgumentException("All user details are mandatory!"); + } + + this.userName = userName; + this.ticket = ticket; + this.person = person; + } + + /** + * @return The user name + */ + public String getUserName() + { + return this.userName; + } + + /** + * Return the full name of the Person this User represents + * + * @param service NodeService to use + * + * @return The full name + */ + public String getFullName(NodeService service) + { + if (this.fullName == null) + { + this.fullName = service.getProperty(this.person, ContentModel.PROP_FIRSTNAME) + " " + + service.getProperty(this.person, ContentModel.PROP_LASTNAME); + } + + return this.fullName; + } + + /** + * @return Retrieves the user's home space (this may be the id of the company home space) + */ + public String getHomeSpaceId() + { + return this.homeSpaceId; + } + + /** + * @param homeSpaceId Sets the id of the users home space + */ + public void setHomeSpaceId(String homeSpaceId) + { + this.homeSpaceId = homeSpaceId; + } + + /** + * @return Returns the ticket. + */ + public String getTicket() + { + return this.ticket; + } + + + /** + * @return Returns the person NodeRef + */ + public NodeRef getPerson() + { + return this.person; + } + + /** + * @return If the current user has Admin Authority + */ + public boolean isAdmin() + { + if (administrator == null) + { + administrator = Repository.getServiceRegistry(FacesContext.getCurrentInstance()) + .getAuthorityService().hasAdminAuthority(); + } + + return administrator; + } + + /** + * Get or create the node used to store user preferences. + * Utilises the 'configurable' aspect on the Person linked to this user. + */ + public synchronized NodeRef getUserPreferencesRef() + { + if (this.preferencesFolderRef == null) + { + FacesContext fc = FacesContext.getCurrentInstance(); + ServiceRegistry registry = Repository.getServiceRegistry(fc); + NodeService nodeService = registry.getNodeService(); + SearchService searchService = registry.getSearchService(); + NamespaceService namespaceService = registry.getNamespaceService(); + ConfigurableService configurableService = Repository.getConfigurableService(fc); + + NodeRef person = Application.getCurrentUser(fc).getPerson(); + if (nodeService.hasAspect(person, ContentModel.ASPECT_CONFIGURABLE) == false) + { + // create the configuration folder for this Person node + configurableService.makeConfigurable(person); + } + + // target of the assoc is the configurations folder ref + NodeRef configRef = configurableService.getConfigurationFolder(person); + if (configRef == null) + { + throw new IllegalStateException("Unable to find associated 'configurations' folder for node: " + person); + } + + String xpath = NamespaceService.APP_MODEL_PREFIX + ":" + "preferences"; + List nodes = searchService.selectNodes( + configRef, + xpath, + null, + namespaceService, + false); + + NodeRef prefRef; + if (nodes.size() == 1) + { + prefRef = nodes.get(0); + } + else + { + // create the preferences Node for this user + ChildAssociationRef childRef = nodeService.createNode( + configRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "preferences"), + ContentModel.TYPE_CMOBJECT); + + prefRef = childRef.getChildRef(); + } + + this.preferencesFolderRef = prefRef; + } + + return this.preferencesFolderRef; + } +} diff --git a/source/java/org/alfresco/web/bean/users/UserMembersBean.java b/source/java/org/alfresco/web/bean/users/UserMembersBean.java new file mode 100644 index 0000000000..5b57b41e96 --- /dev/null +++ b/source/java/org/alfresco/web/bean/users/UserMembersBean.java @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.users; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.faces.application.FacesMessage; +import javax.faces.component.UISelectOne; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.event.ValueChangeEvent; +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.OwnableService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.repository.MapNode; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.data.UIRichList; +import org.alfresco.web.ui.repo.WebResources; + +/** + * @author Kevin Roast + */ +public class UserMembersBean +{ + private static final String MSG_SUCCESS_INHERIT_NOT = "success_not_inherit_permissions"; + private static final String MSG_SUCCESS_INHERIT = "success_inherit_permissions"; + + private static final String ERROR_DELETE = "error_remove_user"; + + private static final String OUTCOME_FINISH = "finish"; + + /** NodeService bean reference */ + private NodeService nodeService; + + /** SearchService bean reference */ + private SearchService searchService; + + /** PermissionService bean reference */ + private PermissionService permissionService; + + /** PersonService bean reference */ + private PersonService personService; + + /** BrowseBean bean refernce */ + private BrowseBean browseBean; + + /** OwnableService bean reference */ + private OwnableService ownableService; + + /** Component reference for Users RichList control */ + private UIRichList usersRichList; + + /** action context */ + private String personAuthority = null; + + /** action context */ + private String personName = null; + + /** datamodel for table of roles for current person */ + private DataModel personRolesDataModel = null; + + /** roles for current person */ + private List personRoles = null; + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService The search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param permissionService The PermissionService to set. + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param ownableService The ownableService to set. + */ + public void setOwnableService(OwnableService ownableService) + { + this.ownableService = ownableService; + } + + /** + * @param personService The personService to set. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @return The space to work against + */ + public Node getSpace() + { + return this.browseBean.getActionSpace(); + } + + /** + * @return Returns the usersRichList. + */ + public UIRichList getUsersRichList() + { + return this.usersRichList; + } + + /** + * @param usersRichList The usersRichList to set. + */ + public void setUsersRichList(UIRichList usersRichList) + { + this.usersRichList = usersRichList; + + // force refresh on exit of the page (as this property is set by JSF on view restore) + this.usersRichList.setValue(null); + } + + /** + * Returns the properties for current Person roles JSF DataModel + * + * @return JSF DataModel representing the current Person roles + */ + public DataModel getPersonRolesDataModel() + { + if (this.personRolesDataModel == null) + { + this.personRolesDataModel = new ListDataModel(); + } + + this.personRolesDataModel.setWrappedData(this.personRoles); + + return this.personRolesDataModel; + } + + /** + * @return Returns the current person authority. + */ + public String getPersonAuthority() + { + return this.personAuthority; + } + + /** + * @param person The person person authority to set. + */ + public void setPersonAuthority(String person) + { + this.personAuthority = person; + } + + /** + * @return Returns the personName. + */ + public String getPersonName() + { + return this.personName; + } + + /** + * @param personName The personName to set. + */ + public void setPersonName(String personName) + { + this.personName = personName; + } + + /** + * @return true if the current user can change permissions on this Space + */ + public boolean getHasChangePermissions() + { + return getSpace().hasPermission(PermissionService.CHANGE_PERMISSIONS); + } + + /** + * @return Returns the inherit parent permissions flag set for the current space. + */ + public boolean isInheritPermissions() + { + return this.permissionService.getInheritParentPermissions(getSpace().getNodeRef()); + } + + /** + * @param inheritPermissions The inheritPermissions to set. + */ + public void setInheritPermissions(boolean inheritPermissions) + { + // stub - no impl as changes are made immediately using a ValueChanged listener + } + + /** + * Return the owner username + */ + public String getOwner() + { + return this.ownableService.getOwner(getSpace().getNodeRef()); + } + + /** + * @return the list of user nodes for list data binding + */ + public List getUsers() + { + FacesContext context = FacesContext.getCurrentInstance(); + + List personNodes = null; + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + // Return all the permissions set against the current node + // for any authentication instance (user). + // Then combine them into a single list for each authentication found. + User user = Application.getCurrentUser(context); + Map> permissionMap = new HashMap>(13, 1.0f); + Set permissions = permissionService.getAllSetPermissions(getSpace().getNodeRef()); + if (permissions != null) + { + for (AccessPermission permission : permissions) + { + // we are only interested in Allow and not groups/owner etc. + if (permission.getAccessStatus() == AccessStatus.ALLOWED && + (permission.getAuthorityType() == AuthorityType.USER || + permission.getAuthorityType() == AuthorityType.GROUP || + permission.getAuthorityType() == AuthorityType.EVERYONE)) + { + String authority = permission.getAuthority(); + + List userPermissions = permissionMap.get(authority); + if (userPermissions == null) + { + // create for first time + userPermissions = new ArrayList(4); + permissionMap.put(authority, userPermissions); + } + // add the permission name for this authority + userPermissions.add(permission.getPermission()); + } + } + } + + // for each authentication (username key) found we get the Person + // node represented by it and use that for our list databinding object + personNodes = new ArrayList(permissionMap.size()); + for (String authority : permissionMap.keySet()) + { + // check if we are dealing with a person (User Authority) + if (personService.personExists(authority)) + { + NodeRef nodeRef = personService.getPerson(authority); + if (nodeRef != null) + { + // create our Node representation + MapNode node = new MapNode(nodeRef); + + // set data binding properties + // this will also force initialisation of the props now during the UserTransaction + // it is much better for performance to do this now rather than during page bind + Map props = node.getProperties(); + props.put("fullName", ((String)props.get("firstName")) + ' ' + ((String)props.get("lastName"))); + + String userName = (String)props.get("userName"); + props.put("roles", listToString(context, permissionMap.get(authority))); + + props.put("icon", WebResources.IMAGE_PERSON); + + personNodes.add(node); + } + } + else + { + // need a map (dummy node) to represent props for this Group Authority + Map node = new HashMap(5, 1.0f); + node.put("fullName", authority.substring(PermissionService.GROUP_PREFIX.length())); + node.put("userName", authority); + node.put("id", authority); + node.put("roles", listToString(context, permissionMap.get(authority))); + node.put("icon", WebResources.IMAGE_GROUP); + personNodes.add(node); + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_NODEREF), new Object[] {"root"}) ); + personNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_GENERIC), err.getMessage()), err ); + personNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + return personNodes; + } + + private static String listToString(FacesContext context, List list) + { + StringBuilder buf = new StringBuilder(); + + if (list != null) + { + for (int i=0; i params = link.getParameterMap(); + String authority = params.get("userName"); + if (authority != null && authority.length() != 0) + { + try + { + if (this.personService.personExists(authority)) + { + // create the node ref, then our node representation + NodeRef ref = personService.getPerson(authority); + Node node = new Node(ref); + + // setup convience function for current user full name + setPersonName((String)node.getProperties().get(ContentModel.PROP_FIRSTNAME) + ' ' + + (String)node.getProperties().get(ContentModel.PROP_LASTNAME)); + } + else + { + setPersonName(authority); + } + + // setup roles for this Authority + List userPermissions = new ArrayList(4); + Set permissions = permissionService.getAllSetPermissions(getSpace().getNodeRef()); + if (permissions != null) + { + for (AccessPermission permission : permissions) + { + // we are only interested in Allow permissions + if (permission.getAccessStatus() == AccessStatus.ALLOWED) + { + if (authority.equals(permission.getAuthority())) + { + // found a permission for this user authentiaction + PermissionWrapper wrapper = new PermissionWrapper( + permission.getPermission(), + Application.getMessage(context, permission.getPermission())); + userPermissions.add(wrapper); + } + } + } + } + // action context setup + this.personRoles = userPermissions; + setPersonAuthority(authority); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext + .getCurrentInstance(), Repository.ERROR_GENERIC), new Object[] { err.getMessage() })); + } + } + else + { + setPersonAuthority(null); + } + } + + + /** + * Inherit parent Space permissions value changed by the user + */ + public void inheritPermissionsValueChanged(ValueChangeEvent event) + { + try + { + // change the value to the new selected value + boolean inheritPermissions = (Boolean)event.getNewValue(); + this.permissionService.setInheritParentPermissions(getSpace().getNodeRef(), inheritPermissions); + + // inform the user that the change occured + FacesContext context = FacesContext.getCurrentInstance(); + String msg; + if (inheritPermissions) + { + msg = Application.getMessage(context, MSG_SUCCESS_INHERIT); + } + else + { + msg = Application.getMessage(context, MSG_SUCCESS_INHERIT_NOT); + } + FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg); + context.addMessage(event.getComponent().getClientId(context), facesMsg); + } + catch (Throwable e) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + } + } + + /** + * Action handler called when the Add Role button is pressed to process the current selection + */ + public void addRole(ActionEvent event) + { + UISelectOne rolePicker = (UISelectOne)event.getComponent().findComponent("roles"); + + String role = (String)rolePicker.getValue(); + if (role != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + PermissionWrapper wrapper = new PermissionWrapper(role, Application.getMessage(context, role)); + this.personRoles.add(wrapper); + } + } + + /** + * Action handler called when the Remove button is pressed to remove a role from current user + */ + public void removeRole(ActionEvent event) + { + PermissionWrapper wrapper = (PermissionWrapper)this.personRolesDataModel.getRowData(); + if (wrapper != null) + { + this.personRoles.remove(wrapper); + } + } + + /** + * Action handler called when the Finish button is clicked on the Edit User Roles page + */ + public String finishOK() + { + String outcome = OUTCOME_FINISH; + + FacesContext context = FacesContext.getCurrentInstance(); + + // persist new user permissions + if (this.personRoles != null && getPersonAuthority() != null) + { + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(context); + tx.begin(); + + // clear the currently set permissions for this user + // and add each of the new permissions in turn + NodeRef nodeRef = getSpace().getNodeRef(); + this.permissionService.clearPermission(nodeRef, getPersonAuthority()); + for (PermissionWrapper wrapper : personRoles) + { + this.permissionService.setPermission( + nodeRef, + getPersonAuthority(), + wrapper.getPermission(), + true); + } + + tx.commit(); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_GENERIC), err.getMessage()), err ); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + + outcome = null; + } + } + + return outcome; + } + + /** + * Action handler called when the OK button is clicked on the Remove User page + */ + public String removeOK() + { + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // remove the invited User + if (getPersonAuthority() != null) + { + // clear permissions for the specified Authority + this.permissionService.clearPermission(getSpace().getNodeRef(), getPersonAuthority()); + } + + // commit the transaction + tx.commit(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext + .getCurrentInstance(), ERROR_DELETE), e.getMessage()), e); + } + + return OUTCOME_FINISH; + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Wrapper class for list data model to display current roles for user + */ + public static class PermissionWrapper + { + public PermissionWrapper(String permission, String label) + { + this.permission = permission; + this.label = label; + } + + public String getRole() + { + return this.label; + } + + public String getPermission() + { + return this.permission; + } + + private String label; + private String permission; + } +} diff --git a/source/java/org/alfresco/web/bean/users/UsersBean.java b/source/java/org/alfresco/web/bean/users/UsersBean.java new file mode 100644 index 0000000000..80f1c52020 --- /dev/null +++ b/source/java/org/alfresco/web/bean/users/UsersBean.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.users; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.LoginBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.data.UIRichList; +import org.apache.log4j.Logger; + +/** + * @author Kevin Roast + */ +public class UsersBean implements IContextListener +{ + private static Logger logger = Logger.getLogger(UsersBean.class); + + public static final String ERROR_PASSWORD_MATCH = "error_password_match"; + private static final String ERROR_DELETE = "error_delete_user"; + + private static final String DEFAULT_OUTCOME = "manageUsers"; + + /** NodeService bean reference */ + private NodeService nodeService; + + /** SearchService bean reference */ + private SearchService searchService; + + /** AuthenticationService bean reference */ + private AuthenticationService authenticationService; + + /** Component reference for Users RichList control */ + private UIRichList usersRichList; + + /** action context */ + private Node person = null; + + private String password = null; + private String confirm = null; + + + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public UsersBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + } + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService the search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param authenticationService The AuthenticationService to set. + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /** + * @return Returns the usersRichList. + */ + public UIRichList getUsersRichList() + { + return this.usersRichList; + } + + /** + * @param usersRichList The usersRichList to set. + */ + public void setUsersRichList(UIRichList usersRichList) + { + this.usersRichList = usersRichList; + } + + /** + * @return the list of user Nodes to display + */ + public List getUsers() + { + return Repository.getUsers(FacesContext.getCurrentInstance(), this.nodeService, + this.searchService); + } + + /** + * @return Returns the confirm password. + */ + public String getConfirm() + { + return this.confirm; + } + + /** + * @param confirm The confirm password to set. + */ + public void setConfirm(String confirm) + { + this.confirm = confirm; + } + + /** + * @return Returns the password. + */ + public String getPassword() + { + return this.password; + } + + /** + * @param password The password to set. + */ + public void setPassword(String password) + { + this.password = password; + } + + /** + * @return Returns the person context. + */ + public Node getPerson() + { + return this.person; + } + + /** + * @param person The person context to set. + */ + public void setPerson(Node person) + { + this.person = person; + } + + /** + * Action event called by all actions that need to setup a Person context on + * the Users bean before an action page is called. The context will be a + * Person Node in setPerson() which can be retrieved on the action page from + * UsersBean.getPerson(). + */ + public void setupUserAction(ActionEvent event) + { + UIActionLink link = (UIActionLink) event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current Person to: " + id); + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // remember the Person node + setPerson(node); + + // clear the UI state in preparation for finishing the action + // and returning to the main page + contextUpdated(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext + .getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] { id })); + } + } + else + { + setPerson(null); + } + } + + /** + * Action handler called when the OK button is clicked on the Delete User page + */ + public String deleteOK() + { + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + // we only delete the user auth if Alfresco is managing the authentication + Map session = context.getExternalContext().getSessionMap(); + if (session.get(LoginBean.LOGIN_EXTERNAL_AUTH) == null) + { + // delete the User authentication + authenticationService.deleteAuthentication((String) getPerson().getProperties().get("userName")); + } + + // delete the associated Person + this.nodeService.deleteNode(getPerson().getNodeRef()); + + // commit the transaction + tx.commit(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext + .getCurrentInstance(), ERROR_DELETE), e.getMessage()), e); + } + + return DEFAULT_OUTCOME; + } + + /** + * Action handler called for OK button press on Change Password screen + */ + public String changePasswordOK() + { + String outcome = DEFAULT_OUTCOME; + + if (this.password != null && this.confirm != null && this.password.equals(this.confirm)) + { + try + { + String userName = (String)this.person.getProperties().get(ContentModel.PROP_USERNAME); + this.authenticationService.setAuthentication(userName, this.password.toCharArray()); + } + catch (Exception e) + { + outcome = null; + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext + .getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + } + } + else + { + outcome = null; + Utils.addErrorMessage(Application.getMessage(FacesContext.getCurrentInstance(), + ERROR_PASSWORD_MATCH)); + } + + return outcome; + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + if (this.usersRichList != null) + { + this.usersRichList.setValue(null); + } + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/AbstractWizardBean.java b/source/java/org/alfresco/web/bean/wizard/AbstractWizardBean.java new file mode 100644 index 0000000000..d80b696486 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/AbstractWizardBean.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.BrowseBean; +import org.alfresco.web.bean.NavigationBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract bean used as the base class for all wizard backing beans. + * + * @author gavinc + */ +public abstract class AbstractWizardBean +{ + private static Log logger = LogFactory.getLog(AbstractWizardBean.class); + + /** I18N messages */ + private static final String MSG_NOT_SET = "value_not_set"; + + protected static final String FINISH_OUTCOME = "finish"; + protected static final String CANCEL_OUTCOME = "cancel"; + protected static final String DEFAULT_INSTRUCTION_ID = "default_instruction"; + protected static final String SUMMARY_TITLE_ID = "summary"; + protected static final String SUMMARY_DESCRIPTION_ID = "summary_desc"; + + // common wizard properties + protected int currentStep = 1; + protected boolean editMode = false; + protected NodeService nodeService; + protected FileFolderService fileFolderService; + protected SearchService searchService; + protected NavigationBean navigator; + protected BrowseBean browseBean; + + /** + * @return Returns the wizard description + */ + public abstract String getWizardDescription(); + + /** + * @return Returns the wizard title + */ + public abstract String getWizardTitle(); + + /** + * @return Returns the title for the current step + */ + public abstract String getStepTitle(); + + /** + * @return Returns the description for the current step + */ + public abstract String getStepDescription(); + + /** + * @return Returns the instructional text for the current step + */ + public abstract String getStepInstructions(); + + /** + * Determines the outcome string for the given step number + * + * @param step The step number to get the outcome for + * @return The outcome + */ + protected abstract String determineOutcomeForStep(int step); + + /** + * Handles the finish button being pressed + * + * @return The finish outcome + */ + public abstract String finish(); + + /** + * Action listener called when the wizard is being launched allowing + * state to be setup + */ + public void startWizard(ActionEvent event) + { + // refresh the UI, calling this method now is fine as it basically makes sure certain + // beans clear the state - so when we finish the wizard other beans will have been reset + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + + // make sure the wizard is not in edit mode + this.editMode = false; + + // initialise the wizard in case we are launching + // after it was navigated away from + init(); + + if (logger.isDebugEnabled()) + logger.debug("Started wizard : " + getWizardTitle()); + } + + /** + * Action listener called when the wizard is being launched for + * editing an existing node. + */ + public void startWizardForEdit(ActionEvent event) + { + // refresh the UI, calling this method now is fine as it basically makes sure certain + // beans clear the state - so when we finish the wizard other beans will have been reset + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + + // set the wizard in edit mode + this.editMode = true; + + // populate the wizard's default values with the current value + // from the node being edited + init(); + populate(); + + if (logger.isDebugEnabled()) + logger.debug("Started wizard : " + getWizardTitle() + " for editing"); + } + + /** + * Deals with the next button being pressed + * + * @return + */ + public String next() + { + this.currentStep++; + + // determine which page to go to next + String outcome = determineOutcomeForStep(this.currentStep); + + if (logger.isDebugEnabled()) + { + logger.debug("current step is now: " + this.currentStep); + logger.debug("Next outcome: " + outcome); + } + + // return the outcome for navigation + return outcome; + } + + /** + * Deals with the back button being pressed + * + * @return + */ + public String back() + { + this.currentStep--; + + // determine which page to go to next + String outcome = determineOutcomeForStep(this.currentStep); + + if (logger.isDebugEnabled()) + { + logger.debug("current step is now: " + this.currentStep); + logger.debug("Back outcome: " + outcome); + } + + // return the outcome for navigation + return outcome; + } + + /** + * Handles the cancelling of the wizard + * + * @return The cancel outcome + */ + public String cancel() + { + // reset the state + init(); + + return CANCEL_OUTCOME; + } + + /** + * Initialises the wizard + */ + public void init() + { + this.currentStep = 1; + } + + /** + * Populates the wizard's values with the current values + * of the node about to be edited + */ + public void populate() + { + // subclasses will override this method to setup accordingly + } + + /** + * @return Returns the nodeService. + */ + public NodeService getNodeService() + { + return this.nodeService; + } + + /** + * @param nodeService The nodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param fileFolderService used to manipulate folder/folder model nodes + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * @param searchService the service used to find nodes + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @return Returns the navigation bean instance. + */ + public NavigationBean getNavigator() + { + return navigator; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @return The BrowseBean + */ + public BrowseBean getBrowseBean() + { + return this.browseBean; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * Build summary table from the specified list of Labels and Values + * + * @param labels Array of labels to display + * @param values Array of values to display + * + * @return summary table HTML + */ + protected String buildSummary(String[] labels, String[] values) + { + if (labels == null || values == null || labels.length != values.length) + { + throw new IllegalArgumentException("Labels and Values passed to summary must be valid and of equal length."); + } + + String msg = Application.getMessage(FacesContext.getCurrentInstance(), MSG_NOT_SET); + String notSetMsg = "<" + msg + ">"; + + StringBuilder buf = new StringBuilder(256); + + buf.append(""); + for (int i=0; i"); + } + buf.append("
"); + buf.append(labels[i]); + buf.append(":"); + buf.append(value != null ? value : notSetMsg); + buf.append("
"); + + return buf.toString(); + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/AddContentWizard.java b/source/java/org/alfresco/web/bean/wizard/AddContentWizard.java new file mode 100644 index 0000000000..e2a514f1f6 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/AddContentWizard.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.File; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.context.FacesContext; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.FileUploadBean; +import org.alfresco.web.bean.repository.Repository; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Handler class used by the Add Content Wizard + * + * @author gavinc + */ +public class AddContentWizard extends BaseContentWizard +{ + private static Log logger = LogFactory.getLog(AddContentWizard.class); + + // TODO: retrieve these from the config service + private static final String WIZARD_TITLE_ID = "add_content_title"; + private static final String WIZARD_DESC_ID = "add_content_desc"; + private static final String STEP1_TITLE_ID = "add_conent_step1_title"; + private static final String STEP1_DESCRIPTION_ID = "add_conent_step1_desc"; + private static final String STEP2_TITLE_ID = "add_conent_step2_title"; + private static final String STEP2_DESCRIPTION_ID = "add_conent_step2_desc"; + + // add content wizard specific properties + private File file; + + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#next() + */ + public String next() + { + String outcome = super.next(); + + // if the outcome is "properties" we pre-set the content type and other + // fields accordingly + if (outcome.equals("properties")) + { + this.contentType = Repository.getMimeTypeForFileName( + FacesContext.getCurrentInstance(), this.fileName); + + // set default for in-line editing flag + this.inlineEdit = (this.contentType.equals(MimetypeMap.MIMETYPE_HTML)); + + // Try and extract metadata from the file + ContentReader cr = new FileContentReader(this.file); + cr.setMimetype(this.contentType); + // create properties for content type + Map contentProps = new HashMap(5, 1.0f); + + if (Repository.extractMetadata(FacesContext.getCurrentInstance(), cr, contentProps)) + { + this.author = (String)(contentProps.get(ContentModel.PROP_CREATOR)); + this.title = (String)(contentProps.get(ContentModel.PROP_TITLE)); + this.description = (String)(contentProps.get(ContentModel.PROP_DESCRIPTION)); + } + if (this.title == null) + { + this.title = this.fileName; + } + } + + return outcome; + } + + /** + * Deals with the finish button being pressed + * + * @return outcome + */ + public String finish() + { + String outcome = saveContent(this.file, null); + + // now we know the new details are in the repository, reset the + // client side node representation so the new details are retrieved + if (this.editMode) + { + this.browseBean.getDocument().reset(); + } + + return outcome; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + String stepDesc = null; + + switch (this.currentStep) + { + case 1: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_DESCRIPTION_ID); + break; + } + case 2: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_DESCRIPTION_ID); + break; + } + case 3: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_DESCRIPTION_ID); + break; + } + default: + { + stepDesc = ""; + } + } + + return stepDesc; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + case 3: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + clearUpload(); + + this.file = null; + } + + /** + * @return Returns the message to display when a file has been uploaded + */ + public String getFileUploadSuccessMsg() + { + String msg = Application.getMessage(FacesContext.getCurrentInstance(), "file_upload_success"); + return MessageFormat.format(msg, new Object[] {getFileName()}); + } + + /** + * @return Returns the name of the file + */ + public String getFileName() + { + // try and retrieve the file and filename from the file upload bean + // representing the file we previously uploaded. + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + this.file = fileBean.getFile(); + this.fileName = fileBean.getFileName(); + } + + return this.fileName; + } + + /** + * @param fileName The name of the file + */ + public void setFileName(String fileName) + { + this.fileName = fileName; + + // we also need to keep the file upload bean in sync + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + fileBean.setFileName(this.fileName); + } + } + + /** + * @return Returns the summary data for the wizard. + */ + public String getSummary() + { + ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); + + return buildSummary( + new String[] {bundle.getString("file_name"), bundle.getString("type"), + bundle.getString("content_type"), bundle.getString("title"), + bundle.getString("description"), bundle.getString("author"), + bundle.getString("inline_editable")}, + new String[] {this.fileName, getSummaryObjectType(), getSummaryContentType(), this.title, + this.description, this.author, Boolean.toString(this.inlineEdit)}); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch(step) + { + case 1: + { + outcome = "upload"; + break; + } + case 2: + { + outcome = "properties"; + break; + } + case 3: + { + outcome = "summary"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } + + /** + * Deletes the uploaded file and removes the FileUploadBean from the session + */ + private void clearUpload() + { + // delete the temporary file we uploaded earlier + if (this.file != null) + { + this.file.delete(); + } + + // remove the file upload bean from the session + FacesContext ctx = FacesContext.getCurrentInstance(); + ctx.getExternalContext().getSessionMap().remove(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/BaseActionWizard.java b/source/java/org/alfresco/web/bean/wizard/BaseActionWizard.java new file mode 100644 index 0000000000..4a87c98508 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/BaseActionWizard.java @@ -0,0 +1,921 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; +import org.alfresco.repo.action.executer.CheckInActionExecuter; +import org.alfresco.repo.action.executer.CheckOutActionExecuter; +import org.alfresco.repo.action.executer.CopyActionExecuter; +import org.alfresco.repo.action.executer.ImageTransformActionExecuter; +import org.alfresco.repo.action.executer.ImporterActionExecuter; +import org.alfresco.repo.action.executer.LinkCategoryActionExecuter; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.action.executer.MoveActionExecuter; +import org.alfresco.repo.action.executer.SimpleWorkflowActionExecuter; +import org.alfresco.repo.action.executer.SpecialiseTypeActionExecuter; +import org.alfresco.repo.action.executer.TransformActionExecuter; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Base handler class containing common code used by the New Space Wizard and New Action Wizard + * + * @author gavinc kevinr + */ +public abstract class BaseActionWizard extends AbstractWizardBean +{ + // parameter names for actions + public static final String PROP_CATEGORY = "category"; + public static final String PROP_ASPECT = "aspect"; + public static final String PROP_DESTINATION = "destinationLocation"; + public static final String PROP_APPROVE_STEP_NAME = "approveStepName"; + public static final String PROP_APPROVE_ACTION = "approveAction"; + public static final String PROP_APPROVE_FOLDER = "approveFolder"; + public static final String PROP_REJECT_STEP_PRESENT = "rejectStepPresent"; + public static final String PROP_REJECT_STEP_NAME = "rejectStepName"; + public static final String PROP_REJECT_ACTION = "rejectAction"; + public static final String PROP_REJECT_FOLDER = "rejectFolder"; + public static final String PROP_CHECKIN_DESC = "checkinDescription"; + public static final String PROP_CHECKIN_MINOR = "checkinMinorChange"; + public static final String PROP_TRANSFORMER = "transformer"; + public static final String PROP_IMAGE_TRANSFORMER = "imageTransformer"; + public static final String PROP_TRANSFORM_OPTIONS = "transformOptions"; + public static final String PROP_ENCODING = "encoding"; + public static final String PROP_MESSAGE = "message"; + public static final String PROP_SUBJECT = "subject"; + public static final String PROP_TO = "to"; + public static final String PROP_OBJECT_TYPE = "objecttype"; + + private static final Log logger = LogFactory.getLog(BaseActionWizard.class); + private static final String IMPORT_ENCODING = "UTF-8"; + + // new rule/action wizard specific properties + protected boolean multiActionMode = false; + protected String action; + protected ActionService actionService; + protected DictionaryService dictionaryService; + protected MimetypeService mimetypeService; + protected List actions; + protected List transformers; + protected List imageTransformers; + protected List aspects; + protected List users; + protected List encodings; + protected Map actionDescriptions; + protected Map currentActionProperties; + protected List objectTypes; + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + this.action = "add-features"; + this.users = null; + this.actions = null; + this.actionDescriptions = null; + + this.currentActionProperties = new HashMap(3); + + // default the approve and reject actions + this.currentActionProperties.put(PROP_APPROVE_ACTION, "move"); + this.currentActionProperties.put(PROP_REJECT_STEP_PRESENT, "yes"); + this.currentActionProperties.put(PROP_REJECT_ACTION, "move"); + + // default the checkin minor change + this.currentActionProperties.put(PROP_CHECKIN_MINOR, new Boolean(true)); + } + + /** + * Build the param map for the current Action instance + * + * @return param map + */ + protected Map buildActionParams() + { + // set up parameters maps for the action + Map actionParams = new HashMap(); + + if (this.action.equals(AddFeaturesActionExecuter.NAME)) + { + QName aspect = Repository.resolveToQName((String)this.currentActionProperties.get(PROP_ASPECT)); + actionParams.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, aspect); + } + else if (this.action.equals(CopyActionExecuter.NAME)) + { + // add the destination space id to the action properties + NodeRef destNodeRef = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + actionParams.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, destNodeRef); + + // add the type and name of the association to create when the copy + // is performed + actionParams.put(CopyActionExecuter.PARAM_ASSOC_TYPE_QNAME, + ContentModel.ASSOC_CONTAINS); + actionParams.put(CopyActionExecuter.PARAM_ASSOC_QNAME, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); + } + else if (this.action.equals(MoveActionExecuter.NAME)) + { + // add the destination space id to the action properties + NodeRef destNodeRef = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + actionParams.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, destNodeRef); + + // add the type and name of the association to create when the move + // is performed + actionParams.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, + ContentModel.ASSOC_CONTAINS); + actionParams.put(MoveActionExecuter.PARAM_ASSOC_QNAME, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "move")); + } + else if (this.action.equals(SimpleWorkflowActionExecuter.NAME)) + { + // add the approve step name + actionParams.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_STEP, + (String)this.currentActionProperties.get(PROP_APPROVE_STEP_NAME)); + + // add whether the approve step will copy or move the content + boolean approveMove = true; + String approveAction = (String)this.currentActionProperties.get(PROP_APPROVE_ACTION); + if (approveAction != null && approveAction.equals("copy")) + { + approveMove = false; + } + + actionParams.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_MOVE, Boolean.valueOf(approveMove)); + + // add the destination folder of the content + NodeRef approveDestNodeRef = null; + Object approveDestNode = this.currentActionProperties.get(PROP_APPROVE_FOLDER); + if (approveDestNode instanceof NodeRef) + { + approveDestNodeRef = (NodeRef)approveDestNode; + } + else if (approveDestNode instanceof String) + { + approveDestNodeRef = new NodeRef((String)approveDestNode); + } + actionParams.put(SimpleWorkflowActionExecuter.PARAM_APPROVE_FOLDER, approveDestNodeRef); + + // determine whether we have a reject step or not + boolean requireReject = true; + String rejectStepPresent = (String)this.currentActionProperties.get(PROP_REJECT_STEP_PRESENT); + if (rejectStepPresent != null && rejectStepPresent.equals("no")) + { + requireReject = false; + } + + if (requireReject) + { + // add the reject step name + actionParams.put(SimpleWorkflowActionExecuter.PARAM_REJECT_STEP, + (String)this.currentActionProperties.get(PROP_REJECT_STEP_NAME)); + + // add whether the reject step will copy or move the content + boolean rejectMove = true; + String rejectAction = (String)this.currentActionProperties.get(PROP_REJECT_ACTION); + if (rejectAction != null && rejectAction.equals("copy")) + { + rejectMove = false; + } + + actionParams.put(SimpleWorkflowActionExecuter.PARAM_REJECT_MOVE, Boolean.valueOf(rejectMove)); + + // add the destination folder of the content + NodeRef rejectDestNodeRef = null; + Object rejectDestNode = this.currentActionProperties.get(PROP_REJECT_FOLDER); + if (rejectDestNode instanceof NodeRef) + { + rejectDestNodeRef = (NodeRef)rejectDestNode; + } + else if (rejectDestNode instanceof String) + { + rejectDestNodeRef = new NodeRef((String)rejectDestNode); + } + actionParams.put(SimpleWorkflowActionExecuter.PARAM_REJECT_FOLDER, rejectDestNodeRef); + } + } + else if (this.action.equals(LinkCategoryActionExecuter.NAME)) + { + // add the classifiable aspect + actionParams.put(LinkCategoryActionExecuter.PARAM_CATEGORY_ASPECT, + ContentModel.ASPECT_GEN_CLASSIFIABLE); + + // put the selected category in the action params + NodeRef catNodeRef = (NodeRef)this.currentActionProperties.get(PROP_CATEGORY); + actionParams.put(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE, + catNodeRef); + } + else if (this.action.equals(CheckOutActionExecuter.NAME)) + { + // specify the location the checked out working copy should go + // add the destination space id to the action properties + NodeRef destNodeRef = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + actionParams.put(CheckOutActionExecuter.PARAM_DESTINATION_FOLDER, destNodeRef); + + // add the type and name of the association to create when the + // check out is performed + actionParams.put(CheckOutActionExecuter.PARAM_ASSOC_TYPE_QNAME, + ContentModel.ASSOC_CONTAINS); + actionParams.put(CheckOutActionExecuter.PARAM_ASSOC_QNAME, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "checkout")); + } + else if (this.action.equals(CheckInActionExecuter.NAME)) + { + // add the description for the checkin to the action params + actionParams.put(CheckInActionExecuter.PARAM_DESCRIPTION, + this.currentActionProperties.get(PROP_CHECKIN_DESC)); + + // add the minor change flag + actionParams.put(CheckInActionExecuter.PARAM_MINOR_CHANGE, + this.currentActionProperties.get(PROP_CHECKIN_MINOR)); + } + else if (this.action.equals(TransformActionExecuter.NAME)) + { + // add the transformer to use + actionParams.put(TransformActionExecuter.PARAM_MIME_TYPE, + this.currentActionProperties.get(PROP_TRANSFORMER)); + + // add the destination space id to the action properties + NodeRef destNodeRef = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + actionParams.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, destNodeRef); + + // add the type and name of the association to create when the copy + // is performed + actionParams.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, + ContentModel.ASSOC_CONTAINS); + actionParams.put(TransformActionExecuter.PARAM_ASSOC_QNAME, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); + } + else if (this.action.equals(ImageTransformActionExecuter.NAME)) + { + // add the transformer to use + actionParams.put(ImageTransformActionExecuter.PARAM_MIME_TYPE, + this.currentActionProperties.get(PROP_IMAGE_TRANSFORMER)); + + // add the options + actionParams.put(ImageTransformActionExecuter.PARAM_CONVERT_COMMAND, + this.currentActionProperties.get(PROP_TRANSFORM_OPTIONS)); + + // add the destination space id to the action properties + NodeRef destNodeRef = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + actionParams.put(TransformActionExecuter.PARAM_DESTINATION_FOLDER, destNodeRef); + + // add the type and name of the association to create when the copy + // is performed + actionParams.put(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, + ContentModel.ASSOC_CONTAINS); + actionParams.put(TransformActionExecuter.PARAM_ASSOC_QNAME, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); + } + else if (this.action.equals(MailActionExecuter.NAME)) + { + // add the actual email text to send + actionParams.put(MailActionExecuter.PARAM_TEXT, + this.currentActionProperties.get(PROP_MESSAGE)); + + // add the person it's going to + actionParams.put(MailActionExecuter.PARAM_TO, + this.currentActionProperties.get(PROP_TO)); + + // add the subject for the email + actionParams.put(MailActionExecuter.PARAM_SUBJECT, + this.currentActionProperties.get(PROP_SUBJECT)); + } + else if (this.action.equals(ImporterActionExecuter.NAME)) + { + // add the encoding + actionParams.put(ImporterActionExecuter.PARAM_ENCODING, IMPORT_ENCODING); + + // add the destination for the import + NodeRef destNodeRef = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + actionParams.put(ImporterActionExecuter.PARAM_DESTINATION_FOLDER, destNodeRef); + } + else if (this.action.equals(SpecialiseTypeActionExecuter.NAME) == true) + { + // add the specialisation type + String objectType = (String)this.currentActionProperties.get(PROP_OBJECT_TYPE); + actionParams.put(SpecialiseTypeActionExecuter.PARAM_TYPE_NAME, QName.createQName(objectType)); + } + + return actionParams; + } + + /** + * Populate the actionProperties member variable with correct props for the current action + * using the supplied property map. + * + * @param actionProps Map to retrieve props appropriate to the current action from + */ + protected void populateActionFromProperties(Map actionProps) + { + if (this.action.equals(AddFeaturesActionExecuter.NAME)) + { + QName aspect = (QName)actionProps.get(AddFeaturesActionExecuter.PARAM_ASPECT_NAME); + this.currentActionProperties.put(PROP_ASPECT, aspect.toString()); + } + else if (this.action.equals(CopyActionExecuter.NAME)) + { + NodeRef destNodeRef = (NodeRef)actionProps.get(CopyActionExecuter.PARAM_DESTINATION_FOLDER); + this.currentActionProperties.put(PROP_DESTINATION, destNodeRef); + } + else if (this.action.equals(MoveActionExecuter.NAME)) + { + NodeRef destNodeRef = (NodeRef)actionProps.get(MoveActionExecuter.PARAM_DESTINATION_FOLDER); + this.currentActionProperties.put(PROP_DESTINATION, destNodeRef); + } + else if (this.action.equals(SimpleWorkflowActionExecuter.NAME)) + { + String approveStep = (String)actionProps.get(SimpleWorkflowActionExecuter.PARAM_APPROVE_STEP); + Boolean approveMove = (Boolean)actionProps.get(SimpleWorkflowActionExecuter.PARAM_APPROVE_MOVE); + NodeRef approveFolderNode = (NodeRef)actionProps.get( + SimpleWorkflowActionExecuter.PARAM_APPROVE_FOLDER); + + String rejectStep = (String)actionProps.get(SimpleWorkflowActionExecuter.PARAM_REJECT_STEP); + Boolean rejectMove = (Boolean)actionProps.get(SimpleWorkflowActionExecuter.PARAM_REJECT_MOVE); + NodeRef rejectFolderNode = (NodeRef)actionProps.get( + SimpleWorkflowActionExecuter.PARAM_REJECT_FOLDER); + + this.currentActionProperties.put(PROP_APPROVE_STEP_NAME, approveStep); + this.currentActionProperties.put(PROP_APPROVE_ACTION, approveMove ? "move" : "copy"); + this.currentActionProperties.put(PROP_APPROVE_FOLDER, approveFolderNode); + + if (rejectStep == null && rejectMove == null && rejectFolderNode == null) + { + this.currentActionProperties.put(PROP_REJECT_STEP_PRESENT, "no"); + } + else + { + this.currentActionProperties.put(PROP_REJECT_STEP_PRESENT, "yes"); + this.currentActionProperties.put(PROP_REJECT_STEP_NAME, rejectStep); + this.currentActionProperties.put(PROP_REJECT_ACTION, rejectMove ? "move" : "copy"); + this.currentActionProperties.put(PROP_REJECT_FOLDER, rejectFolderNode); + } + } + else if (this.action.equals(LinkCategoryActionExecuter.NAME)) + { + NodeRef catNodeRef = (NodeRef)actionProps.get(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE); + this.currentActionProperties.put(PROP_CATEGORY, catNodeRef); + } + else if (this.action.equals(CheckOutActionExecuter.NAME)) + { + NodeRef destNodeRef = (NodeRef)actionProps.get(CheckOutActionExecuter.PARAM_DESTINATION_FOLDER); + this.currentActionProperties.put(PROP_DESTINATION, destNodeRef); + } + else if (this.action.equals(CheckInActionExecuter.NAME)) + { + String checkDesc = (String)actionProps.get(CheckInActionExecuter.PARAM_DESCRIPTION); + this.currentActionProperties.put(PROP_CHECKIN_DESC, checkDesc); + + Boolean minorChange = (Boolean)actionProps.get(CheckInActionExecuter.PARAM_MINOR_CHANGE); + this.currentActionProperties.put(PROP_CHECKIN_MINOR, minorChange); + } + else if (this.action.equals(TransformActionExecuter.NAME)) + { + String transformer = (String)actionProps.get(TransformActionExecuter.PARAM_MIME_TYPE); + this.currentActionProperties.put(PROP_TRANSFORMER, transformer); + + NodeRef destNodeRef = (NodeRef)actionProps.get(CopyActionExecuter.PARAM_DESTINATION_FOLDER); + this.currentActionProperties.put(PROP_DESTINATION, destNodeRef); + } + else if (this.action.equals(ImageTransformActionExecuter.NAME)) + { + String transformer = (String)actionProps.get(TransformActionExecuter.PARAM_MIME_TYPE); + this.currentActionProperties.put(PROP_IMAGE_TRANSFORMER, transformer); + + String options = (String)actionProps.get(ImageTransformActionExecuter.PARAM_CONVERT_COMMAND); + this.currentActionProperties.put(PROP_TRANSFORM_OPTIONS, options != null ? options : ""); + + NodeRef destNodeRef = (NodeRef)actionProps.get(CopyActionExecuter.PARAM_DESTINATION_FOLDER); + this.currentActionProperties.put(PROP_DESTINATION, destNodeRef); + } + else if (this.action.equals(MailActionExecuter.NAME)) + { + String subject = (String)actionProps.get(MailActionExecuter.PARAM_SUBJECT); + this.currentActionProperties.put(PROP_SUBJECT, subject); + + String message = (String)actionProps.get(MailActionExecuter.PARAM_TEXT); + this.currentActionProperties.put(PROP_MESSAGE, message); + + String to = (String)actionProps.get(MailActionExecuter.PARAM_TO); + this.currentActionProperties.put(PROP_TO, to); + } + else if (this.action.equals(ImporterActionExecuter.NAME)) + { + NodeRef destNodeRef = (NodeRef)actionProps.get(ImporterActionExecuter.PARAM_DESTINATION_FOLDER); + this.currentActionProperties.put(PROP_DESTINATION, destNodeRef); + } + else if (this.action.equals(SpecialiseTypeActionExecuter.NAME) == true) + { + QName specialiseType = (QName)actionProps.get(SpecialiseTypeActionExecuter.PARAM_TYPE_NAME); + this.currentActionProperties.put(PROP_OBJECT_TYPE, specialiseType.toString()); + } + } + + /** + * @return Returns the selected action + */ + public String getAction() + { + return this.action; + } + + /** + * @param action Sets the selected action + */ + public void setAction(String action) + { + this.action = action; + } + + /** + * Sets the action service + * + * @param actionRegistration the action service + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Sets the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Sets the mimetype service + * + * @param mimetypeService The mimetype service + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * @return Returns the list of selectable actions + */ + public List getActions() + { + if (this.actions == null) + { + List ruleActions = this.actionService.getActionDefinitions(); + this.actions = new ArrayList(); + for (ActionDefinition ruleActionDef : ruleActions) + { + this.actions.add(new SelectItem(ruleActionDef.getName(), ruleActionDef.getTitle())); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.actions, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + return this.actions; + } + + /** + * @return Returns a map of all the action descriptions + */ + public Map getActionDescriptions() + { + if (this.actionDescriptions == null) + { + List ruleActions = this.actionService.getActionDefinitions(); + this.actionDescriptions = new HashMap(); + for (ActionDefinition ruleActionDef : ruleActions) + { + this.actionDescriptions.put(ruleActionDef.getName(), ruleActionDef.getDescription()); + } + } + + return this.actionDescriptions; + } + + /** + * @return Gets the action settings + */ + public Map getActionProperties() + { + return this.currentActionProperties; + } + + /** + * Returns a list of encodings the import and export actions can use + * + * @return List of SelectItem objects representing the available encodings + */ + public List getEncodings() + { + if (this.encodings == null) + { + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Action Wizards"); + if (wizardCfg != null) + { + ConfigElement encodingsCfg = wizardCfg.getConfigElement("encodings"); + if (encodingsCfg != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + this.encodings = new ArrayList(); + for (ConfigElement child : encodingsCfg.getChildren()) + { + String id = child.getAttribute("name"); + + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + this.encodings.add(new SelectItem(id, label)); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.encodings, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find encodings configuration element"); + } + } + else + { + logger.warn("Could not find Action Wizards configuration section"); + } + } + + return this.encodings; + } + + /** + * Returns the transformers that are available + * + * @return List of SelectItem objects representing the available transformers + */ + public List getTransformers() + { + if (this.transformers == null) + { + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Action Wizards"); + if (wizardCfg != null) + { + ConfigElement transformersCfg = wizardCfg.getConfigElement("transformers"); + if (transformersCfg != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + Map mimeTypes = this.mimetypeService.getDisplaysByMimetype(); + this.transformers = new ArrayList(); + for (ConfigElement child : transformersCfg.getChildren()) + { + String id = child.getAttribute("name"); + + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label get it from the mime type service + if (label == null) + { + label = mimeTypes.get(id); + } + + this.transformers.add(new SelectItem(id, label)); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.transformers, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find transformers configuration element"); + } + } + else + { + logger.warn("Could not find Action Wizards configuration section"); + } + } + + return this.transformers; + } + + /** + * Returns the image transformers that are available + * + * @return List of SelectItem objects representing the available image transformers + */ + public List getImageTransformers() + { + if (this.imageTransformers == null) + { + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Action Wizards"); + if (wizardCfg != null) + { + ConfigElement transformersCfg = wizardCfg.getConfigElement("image-transformers"); + if (transformersCfg != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + Map mimeTypes = this.mimetypeService.getDisplaysByMimetype(); + this.imageTransformers = new ArrayList(); + for (ConfigElement child : transformersCfg.getChildren()) + { + String id = child.getAttribute("name"); + + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label get it from the mime type service + if (label == null) + { + label = mimeTypes.get(id); + } + + this.imageTransformers.add(new SelectItem(id, label)); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.imageTransformers, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find image-transformers configuration element"); + } + } + else + { + logger.warn("Could not find Action Wizards configuration section"); + } + } + + return this.imageTransformers; + } + + /** + * Returns the aspects that are available + * + * @return List of SelectItem objects representing the available aspects + */ + public List getAspects() + { + if (this.aspects == null) + { + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Action Wizards"); + if (wizardCfg != null) + { + ConfigElement aspectsCfg = wizardCfg.getConfigElement("aspects"); + if (aspectsCfg != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + this.aspects = new ArrayList(); + for (ConfigElement child : aspectsCfg.getChildren()) + { + QName idQName = Repository.resolveToQName(child.getAttribute("name")); + + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + AspectDefinition aspectDef = this.dictionaryService.getAspect(idQName); + if (aspectDef != null) + { + label = aspectDef.getTitle(); + } + else + { + label = idQName.getLocalName(); + } + } + + this.aspects.add(new SelectItem(idQName.toString(), label)); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.aspects, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find aspects configuration element"); + } + } + else + { + logger.warn("Could not find Action Wizards configuration section"); + } + } + + return this.aspects; + } + + /** + * @return Returns a list of object types to allow the user to select from + */ + public List getObjectTypes() + { + if (this.objectTypes == null) + { + FacesContext context = FacesContext.getCurrentInstance(); + + // add the well known object type to start with + this.objectTypes = new ArrayList(5); + this.objectTypes.add(new SelectItem(ContentModel.TYPE_CONTENT.toString(), + Application.getMessage(context, "content"))); + + // add any configured content sub-types to the list + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Custom Content Types"); + if (wizardCfg != null) + { + ConfigElement typesCfg = wizardCfg.getConfigElement("content-types"); + if (typesCfg != null) + { + for (ConfigElement child : typesCfg.getChildren()) + { + QName idQName = Repository.resolveToQName(child.getAttribute("name")); + TypeDefinition typeDef = this.dictionaryService.getType(idQName); + + if (typeDef != null && + this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT)) + { + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + label = typeDef.getTitle(); + } + + // finally, just use the localname + if (label == null) + { + label = idQName.getLocalName(); + } + + this.objectTypes.add(new SelectItem(idQName.toString(), label)); + } + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.objectTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find 'content-types' configuration element"); + } + } + else + { + logger.warn("Could not find 'Custom Content Types' configuration section"); + } + + } + + return this.objectTypes; + } + + /** + * @return the List of users in the system wrapped in SelectItem objects + */ + public List getUsers() + { + if (this.users == null) + { + List userNodes = Repository.getUsers( + FacesContext.getCurrentInstance(), + this.nodeService, + this.searchService); + this.users = new ArrayList(); + for (Node user : userNodes) + { + String email = (String)user.getProperties().get("email"); + if (email != null && email.length() > 0) + { + this.users.add(new SelectItem(email, (String)user.getProperties().get("fullName"))); + } + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.users, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + return this.users; + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java b/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java new file mode 100644 index 0000000000..746bc82c4b --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/BaseContentWizard.java @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.File; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; +import javax.transaction.UserTransaction; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Base Handler class used by the Content Wizards + * + * @author gavinc kevinr + */ +public abstract class BaseContentWizard extends AbstractWizardBean +{ + private static Log logger = LogFactory.getLog(BaseContentWizard.class); + + protected static final String FINISH_INSTRUCTION_ID = "content_finish_instruction"; + + // content wizard specific attributes + protected String fileName; + protected String author; + protected String title; + protected String description; + protected String contentType; + protected String objectType; + protected boolean inlineEdit; + protected List contentTypes; + protected List objectTypes; + protected ContentService contentService; + protected DictionaryService dictionaryService; + + // the NodeRef of the node created during finish + protected NodeRef createdNode; + + /** + * Save the specified content using the currently set wizard attributes + * + * @param fileContent File content to save + * @param strContent String content to save + */ + protected String saveContent(File fileContent, String strContent) + { + String outcome = FINISH_OUTCOME; + + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + if (this.editMode) + { + // update the existing node in the repository + Node currentDocument = this.browseBean.getDocument(); + NodeRef nodeRef = currentDocument.getNodeRef(); + + // move the file - location and name checks will be performed + this.fileFolderService.move(nodeRef, null, this.fileName); + // set up the content data + // update the modified timestamp and other content props + Map contentProps = this.nodeService.getProperties(nodeRef); + contentProps.put(ContentModel.PROP_TITLE, this.title); + contentProps.put(ContentModel.PROP_DESCRIPTION, this.description); + contentProps.put(ContentModel.PROP_CREATOR, this.author); + + // set up content properties - copy or create the compound property + ContentData contentData = (ContentData)contentProps.get(ContentModel.PROP_CONTENT); + if (contentData == null) + { + contentData = new ContentData(null, this.contentType, 0L, "UTF-8"); + } + else + { + contentData = new ContentData( + contentData.getContentUrl(), + this.contentType, + contentData.getSize(), + contentData.getEncoding()); + } + contentProps.put(ContentModel.PROP_CONTENT, contentData); + + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INLINEEDITABLE) == false) + { + Map editProps = new HashMap(1, 1.0f); + editProps.put(ContentModel.PROP_EDITINLINE, this.inlineEdit); + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_INLINEEDITABLE, editProps); + } + else + { + contentProps.put(ContentModel.PROP_EDITINLINE, this.inlineEdit); + } + this.nodeService.setProperties(nodeRef, contentProps); + } + else + { + // get the node ref of the node that will contain the content + NodeRef containerNodeRef; + String nodeId = getNavigator().getCurrentNodeId(); + if (nodeId == null) + { + containerNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + } + else + { + containerNodeRef = new NodeRef(Repository.getStoreRef(), nodeId); + } + + FileInfo fileInfo = fileFolderService.create( + containerNodeRef, + this.fileName, + Repository.resolveToQName(this.objectType)); + NodeRef fileNodeRef = fileInfo.getNodeRef(); + + // set the author (if we have) + if (this.author != null && this.author.length() > 0) + { + this.nodeService.setProperty(fileNodeRef, ContentModel.PROP_CREATOR, this.author); + } + + if (logger.isDebugEnabled()) + logger.debug("Created file node for file: " + this.fileName); + + // apply the titled aspect - title and description + Map titledProps = new HashMap(3, 1.0f); + titledProps.put(ContentModel.PROP_TITLE, this.title); + titledProps.put(ContentModel.PROP_DESCRIPTION, this.description); + this.nodeService.addAspect(fileNodeRef, ContentModel.ASPECT_TITLED, titledProps); + + if (logger.isDebugEnabled()) + logger.debug("Added titled aspect with properties: " + titledProps); + + // apply the inlineeditable aspect + if (this.inlineEdit == true) + { + Map editProps = new HashMap(1, 1.0f); + editProps.put(ContentModel.PROP_EDITINLINE, this.inlineEdit); + this.nodeService.addAspect(fileNodeRef, ContentModel.ASPECT_INLINEEDITABLE, editProps); + + if (logger.isDebugEnabled()) + logger.debug("Added inlineeditable aspect with properties: " + editProps); + } + + // get a writer for the content and put the file + ContentWriter writer = contentService.getWriter(fileNodeRef, ContentModel.PROP_CONTENT, true); + // set the mimetype and encoding + writer.setMimetype(this.contentType); + writer.setEncoding("UTF-8"); + if (fileContent != null) + { + writer.putContent(fileContent); + } + else if (strContent != null) + { + writer.putContent(strContent); + } + + // remember the created node now + this.createdNode = fileNodeRef; + } + + // give subclasses a chance to perform custom processing before committing + performCustomProcessing(); + + // commit the transaction + tx.commit(); + } + catch (FileExistsException e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + // print status message + String statusMsg = MessageFormat.format( + Application.getMessage( + FacesContext.getCurrentInstance(), "error_exists"), + e.getExisting().getName()); + Utils.addErrorMessage(statusMsg); + // no outcome + outcome = null; + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + outcome = null; + } + + return outcome; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 3: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + this.fileName = null; + this.author = null; + this.title = null; + this.description = null; + this.contentType = null; + this.inlineEdit = false; + this.contentTypes = null; + this.objectTypes = null; + + this.objectType = ContentModel.TYPE_CONTENT.toString(); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#populate() + */ + public void populate() + { + // get hold of the current document and populate the appropriate values + Node currentDocument = this.browseBean.getDocument(); + Map props = currentDocument.getProperties(); + + Boolean inline = (Boolean)props.get("editInline"); + this.inlineEdit = inline != null ? inline.booleanValue() : false; + this.author = (String)props.get("creator"); + this.contentType = null; + ContentData contentData = (ContentData)props.get(ContentModel.PROP_CONTENT); + if (contentData != null) + { + this.contentType = contentData.getMimetype(); + } + this.description = (String)props.get("description"); + this.fileName = currentDocument.getName(); + this.title = (String)props.get("title"); + } + + /** + * @return Returns the contentService. + */ + public ContentService getContentService() + { + return contentService; + } + + /** + * @param contentService The contentService to set. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Sets the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @return Returns the name of the file + */ + public String getFileName() + { + return this.fileName; + } + + /** + * @param fileName The name of the file + */ + public void setFileName(String fileName) + { + this.fileName = fileName; + } + + /** + * @return Returns the author + */ + public String getAuthor() + { + return this.author; + } + + /** + * @param author Sets the author + */ + public void setAuthor(String author) + { + this.author = author; + } + + /** + * @return Returns the content type currenty selected + */ + public String getContentType() + { + return this.contentType; + } + + /** + * @param contentType Sets the currently selected content type + */ + public void setContentType(String contentType) + { + this.contentType = contentType; + } + + /** + * @return Returns the object type currenty selected + */ + public String getObjectType() + { + return this.objectType; + } + + /** + * @param objectType Sets the currently selected object type + */ + public void setObjectType(String objectType) + { + this.objectType = objectType; + } + + /** + * @return Returns the description + */ + public String getDescription() + { + return this.description; + } + + /** + * @param description Sets the description + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * @return Returns the title + */ + public String getTitle() + { + return this.title; + } + + /** + * @param title Sets the title + */ + public void setTitle(String title) + { + this.title = title; + } + + /** + * @return Returns the inline edit flag. + */ + public boolean isInlineEdit() + { + return this.inlineEdit; + } + + /** + * @param inlineEdit The inline edit flag to set. + */ + public void setInlineEdit(boolean inlineEdit) + { + this.inlineEdit = inlineEdit; + } + + /** + * @return Returns a list of content types to allow the user to select from + */ + public List getContentTypes() + { + if (this.contentTypes == null) + { + this.contentTypes = new ArrayList(80); + ServiceRegistry registry = Repository.getServiceRegistry(FacesContext.getCurrentInstance()); + MimetypeService mimetypeService = registry.getMimetypeService(); + + // get the mime type display names + Map mimeTypes = mimetypeService.getDisplaysByMimetype(); + for (String mimeType : mimeTypes.keySet()) + { + this.contentTypes.add(new SelectItem(mimeType, mimeTypes.get(mimeType))); + } + + // make sure the list is sorted by the values + QuickSort sorter = new QuickSort(this.contentTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + return this.contentTypes; + } + + /** + * @return Returns a list of object types to allow the user to select from + */ + public List getObjectTypes() + { + if (this.objectTypes == null) + { + FacesContext context = FacesContext.getCurrentInstance(); + + // add the well known object type to start with + this.objectTypes = new ArrayList(5); + this.objectTypes.add(new SelectItem(ContentModel.TYPE_CONTENT.toString(), + Application.getMessage(context, "content"))); + + // add any configured content sub-types to the list + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Custom Content Types"); + if (wizardCfg != null) + { + ConfigElement typesCfg = wizardCfg.getConfigElement("content-types"); + if (typesCfg != null) + { + for (ConfigElement child : typesCfg.getChildren()) + { + QName idQName = Repository.resolveToQName(child.getAttribute("name")); + TypeDefinition typeDef = this.dictionaryService.getType(idQName); + + if (typeDef != null && + this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_CONTENT)) + { + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + label = typeDef.getTitle(); + } + + // finally, just use the localname + if (label == null) + { + label = idQName.getLocalName(); + } + + this.objectTypes.add(new SelectItem(idQName.toString(), label)); + } + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.objectTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find 'content-types' configuration element"); + } + } + else + { + logger.warn("Could not find 'Custom Content Types' configuration section"); + } + + } + + return this.objectTypes; + } + + /** + * @return Determines whether the next and finish button should be enabled + */ + public boolean getNextFinishDisabled() + { + boolean disabled = false; + + if (this.fileName == null || this.fileName.length() == 0 || + this.title == null || this.title.length() == 0 || + this.contentType == null) + { + disabled = true; + } + + return disabled; + } + + /** + * Returns the display label for the content type currently chosen + * + * @return The human readable version of the content type + */ + protected String getSummaryContentType() + { + ServiceRegistry registry = Repository.getServiceRegistry(FacesContext.getCurrentInstance()); + MimetypeService mimetypeService = registry.getMimetypeService(); + + // get the mime type display name + Map mimeTypes = mimetypeService.getDisplaysByMimetype(); + return mimeTypes.get(this.contentType); + } + + /** + * Returns the display label for the currently selected object type + * + * @return The objevt type label + */ + protected String getSummaryObjectType() + { + String objType = null; + + for (SelectItem item : this.getObjectTypes()) + { + if (item.getValue().equals(this.objectType)) + { + objType = item.getLabel(); + break; + } + } + + return objType; + } + + /** + * Performs any processing sub classes may wish to do before commit is called + */ + protected void performCustomProcessing() + { + // used by subclasses if necessary + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/CreateContentWizard.java b/source/java/org/alfresco/web/bean/wizard/CreateContentWizard.java new file mode 100644 index 0000000000..c767cb1b20 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/CreateContentWizard.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.util.ResourceBundle; + +import javax.faces.context.FacesContext; +import javax.faces.event.ValueChangeEvent; + +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Handler class used by the Create In-line Content Wizard + * + * @author Kevin Roast + */ +public class CreateContentWizard extends BaseContentWizard +{ + protected static final String CONTENT_TEXT = "txt"; + protected static final String CONTENT_HTML = "html"; + + private static Log logger = LogFactory.getLog(CreateContentWizard.class); + + // TODO: retrieve these from the config service + private static final String WIZARD_TITLE_ID = "create_content_title"; + private static final String WIZARD_DESC_ID = "create_content_desc"; + private static final String STEP1_TITLE_ID = "create_content_step1_title"; + private static final String STEP1_DESCRIPTION_ID = "create_content_step1_desc"; + private static final String STEP2_TITLE_ID = "create_content_step2_title"; + private static final String STEP2_DESCRIPTION_ID = "create_content_step2_desc"; + private static final String STEP3_TITLE_ID = "create_content_step3_title"; + private static final String STEP3_DESCRIPTION_ID = "create_content_step3_desc"; + + // create content wizard specific properties + protected String content; + protected String createType = CONTENT_HTML; + + + /** + * Deals with the finish button being pressed + * + * @return outcome + */ + public String finish() + { + return saveContent(null, this.content); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + String stepDesc = null; + + switch (this.currentStep) + { + case 1: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_DESCRIPTION_ID); + break; + } + case 2: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_DESCRIPTION_ID); + break; + } + case 3: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP3_DESCRIPTION_ID); + break; + } + case 4: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_DESCRIPTION_ID); + break; + } + default: + { + stepDesc = ""; + } + } + + return stepDesc; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 4: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + case 3: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP3_TITLE_ID); + break; + } + case 4: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * @return Returns the content from the edited form. + */ + public String getContent() + { + return this.content; + } + + /** + * @param content The content to edit (should be clear initially) + */ + public void setContent(String content) + { + this.content = content; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + this.content = null; + + // created content is inline editable by default + this.inlineEdit = true; + } + + /** + * @return Returns the summary data for the wizard. + */ + public String getSummary() + { + ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); + + // TODO: show first few lines of content here? + return buildSummary( + new String[] {bundle.getString("file_name"), bundle.getString("type"), + bundle.getString("content_type"), bundle.getString("title"), + bundle.getString("description"), bundle.getString("author")}, + new String[] {this.fileName, getSummaryObjectType(), getSummaryContentType(), + this.title, this.description, this.author}); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch(step) + { + case 1: + { + outcome = "select"; + break; + } + case 2: + { + if (getCreateType().equals(CONTENT_HTML)) + { + outcome = "create-html"; + } + else if (getCreateType().equals(CONTENT_TEXT)) + { + outcome = "create-text"; + } + break; + } + case 3: + { + this.fileName = "newfile." + getCreateType(); + this.contentType = Repository.getMimeTypeForFileName( + FacesContext.getCurrentInstance(), this.fileName); + this.title = this.fileName; + + outcome = "properties"; + break; + } + case 4: + { + outcome = "summary"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } + + /** + * Create content type value changed by the user + */ + public void createContentChanged(ValueChangeEvent event) + { + // clear the content as HTML is not compatible with the plain text box etc. + this.content = null; + } + + /** + * @return Returns the createType. + */ + public String getCreateType() + { + return this.createType; + } + + /** + * @param createType The createType to set. + */ + public void setCreateType(String createType) + { + this.createType = createType; + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java b/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java new file mode 100644 index 0000000000..0de1427c35 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/InviteUsersWizard.java @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.faces.component.UISelectOne; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.faces.model.SelectItem; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.ui.common.SortableSelectItem; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIGenericPicker; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; + +/** + * @author Kevin Roast + */ +public class InviteUsersWizard extends AbstractWizardBean +{ + private static Log logger = LogFactory.getLog(InviteUsersWizard.class); + + /** I18N message strings */ + private static final String MSG_USERS = "users"; + private static final String MSG_GROUPS = "groups"; + private static final String MSG_INVITED_SPACE = "invite_space"; + private static final String MSG_INVITED_ROLE = "invite_role"; + private static final String WIZARD_TITLE_ID = "invite_title"; + private static final String WIZARD_DESC_ID = "invite_desc"; + private static final String STEP1_TITLE_ID = "invite_step1_title"; + private static final String STEP1_DESCRIPTION_ID = "invite_step1_desc"; + private static final String STEP2_TITLE_ID = "invite_step2_title"; + private static final String STEP2_DESCRIPTION_ID = "invite_step2_desc"; + private static final String FINISH_INSTRUCTION_ID = "invite_finish_instruction"; + + private static final String NOTIFY_YES = "yes"; + + /** NamespaceService bean reference */ + private NamespaceService namespaceService; + + /** JavaMailSender bean reference */ + private JavaMailSender mailSender; + + /** AuthorityService bean reference */ + private AuthorityService authorityService; + + /** PermissionService bean reference */ + private PermissionService permissionService; + + /** personService bean reference */ + private PersonService personService; + + /** datamodel for table of roles for users */ + private DataModel userRolesDataModel = null; + + /** list of user/group role wrapper objects */ + private List userGroupRoles = null; + + /** Cache of available folder permissions */ + Set folderPermissions = null; + + /** dialog state */ + private String notify = NOTIFY_YES; + private String subject = null; + private String body = null; + private String internalSubject = null; + private String automaticText = null; + + + /** + * @param namespaceService The NamespaceService to set. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param mailSender The JavaMailSender to set. + */ + public void setMailSender(JavaMailSender mailSender) + { + this.mailSender = mailSender; + } + + /** + * @param permissionService The PermissionService to set. + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param permissionService The PermissionService to set. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param authorityService The authorityService to set. + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + notify = NOTIFY_YES; + userGroupRoles = new ArrayList(8); + subject = ""; + body = ""; + automaticText = ""; + internalSubject = null; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#finish() + */ + public String finish() + { + String outcome = FINISH_OUTCOME; + + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + + tx = Repository.getUserTransaction(context); + tx.begin(); + + String subject = this.subject; + if (subject == null || subject.length() == 0) + { + subject = this.internalSubject; + } + + User user = Application.getCurrentUser(context); + String from = (String)this.nodeService.getProperty(user.getPerson(), ContentModel.PROP_EMAIL); + if (from == null || from.length() == 0) + { + // TODO: get this from spring config? + from = "alfresco@alfresco.org"; + } + + // get the Space to apply changes too + NodeRef folderNodeRef = this.browseBean.getActionSpace().getNodeRef(); + + // set permissions for each user and send them a mail + for (int i=0; i perms = getFolderPermissions(); + for (String permission : perms) + { + if (userGroupRole.getRole().equals(permission)) + { + this.permissionService.setPermission( + folderNodeRef, + authority, + permission, + true); + break; + } + } + + // Create the mail message for sending to each User + if (NOTIFY_YES.equals(this.notify)) + { + // if User, email then, else if Group get all members and email them + AuthorityType authType = AuthorityType.getAuthorityType(authority); + if (authType.equals(AuthorityType.USER)) + { + if (this.personService.personExists(authority) == true) + { + notifyUser(this.personService.getPerson(authority), folderNodeRef, from, userGroupRole.getRole()); + } + } + else if (authType.equals(AuthorityType.GROUP)) + { + // else notify all members of the group + Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, authority, false); + for (String userAuth : users) + { + if (this.personService.personExists(userAuth) == true) + { + notifyUser(this.personService.getPerson(userAuth), folderNodeRef, from, userGroupRole.getRole()); + } + } + } + } + } + + // commit the transaction + tx.commit(); + + UIContextService.getInstance(context).notifyBeans(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), e.getMessage()), e); + outcome = null; + } + + return outcome; + } + + /** + * Send an email notification to the specified User authority + * + * @param person Person node representing the user + * @param folder Folder node they are invited too + * @param from From text message + * @param roleText The role display label for the user invite notification + */ + private void notifyUser(NodeRef person, NodeRef folder, String from, String roleText) + { + String to = (String)this.nodeService.getProperty(person, ContentModel.PROP_EMAIL); + + if (to != null && to.length() != 0) + { + String msgRole = Application.getMessage(FacesContext.getCurrentInstance(), MSG_INVITED_ROLE); + String roleMessage = MessageFormat.format(msgRole, new Object[] {roleText}); + + // TODO: include External Authentication link to the invited space + //String args = folder.getStoreRef().getProtocol() + '/' + + // folder.getStoreRef().getIdentifier() + '/' + + // folder.getId(); + //String url = ExternalAccessServlet.generateExternalURL(LoginBean.OUTCOME_SPACEDETAILS, args); + + String body = this.internalSubject + "\r\n\r\n" + roleMessage + "\r\n\r\n";// + url + "\r\n\r\n"; + if (this.body != null && this.body.length() != 0) + { + body += this.body; + } + + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setTo(to); + simpleMailMessage.setSubject(subject); + simpleMailMessage.setText(body); + simpleMailMessage.setFrom(from); + + if (logger.isDebugEnabled()) + logger.debug("Sending notification email to: " + to + "\n...with subject:\n" + subject + "\n...with body:\n" + body); + + try + { + // Send the message + this.mailSender.send(simpleMailMessage); + } + catch (Throwable e) + { + // don't stop the action but let admins know email is not getting sent + logger.error("Failed to send email to " + to, e); + } + } + } + + /** + * Returns the properties for current user-roles JSF DataModel + * + * @return JSF DataModel representing the current user-roles + */ + public DataModel getUserRolesDataModel() + { + if (this.userRolesDataModel == null) + { + this.userRolesDataModel = new ListDataModel(); + } + + this.userRolesDataModel.setWrappedData(this.userGroupRoles); + + return this.userRolesDataModel; + } + + /** + * Query callback method executed by the Generic Picker component. + * This method is part of the contract to the Generic Picker, it is up to the backing bean + * to execute whatever query is appropriate and return the results. + * + * @param filterIndex Index of the filter drop-down selection + * @param contains Text from the contains textbox + * + * @return An array of SelectItem objects containing the results to display in the picker. + */ + public SelectItem[] pickerCallback(int filterIndex, String contains) + { + FacesContext context = FacesContext.getCurrentInstance(); + + SelectItem[] items; + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + if (filterIndex == 0) + { + // build xpath to match available User/Person objects + NodeRef peopleRef = personService.getPeopleContainer(); + // NOTE: see SearcherComponentTest + String xpath = "*[like(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "firstName, '%" + contains + "%', false)" + + " or " + "like(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "lastName, '%" + contains + "%', false)]"; + + List nodes = searchService.selectNodes( + peopleRef, + xpath, + null, + this.namespaceService, + false); + + items = new SelectItem[nodes.size()]; + for (int index=0; index groups = authorityService.getAllAuthorities(AuthorityType.GROUP); + groups.addAll(authorityService.getAllAuthorities(AuthorityType.EVERYONE)); + + List results = new ArrayList(groups.size()); + String containsLower = contains.toLowerCase(); + int offset = PermissionService.GROUP_PREFIX.length(); + for (String group : groups) + { + if (group.toLowerCase().indexOf(containsLower) != -1) + { + results.add(new SortableSelectItem(group, group.substring(offset), group)); + } + } + items = new SelectItem[results.size()]; + results.toArray(items); + } + + Arrays.sort(items); + + // commit the transaction + tx.commit(); + } + catch (Exception err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err ); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + + items = new SelectItem[0]; + } + + return items; + } + + /** + * Action handler called when the Add button is pressed to process the current selection + */ + public void addSelection(ActionEvent event) + { + UIGenericPicker picker = (UIGenericPicker)event.getComponent().findComponent("picker"); + UISelectOne rolePicker = (UISelectOne)event.getComponent().findComponent("roles"); + + String[] results = picker.getSelectedResults(); + if (results != null) + { + String role = (String)rolePicker.getValue(); + + if (role != null) + { + for (int i=0; i perms = getFolderPermissions(); + SelectItem[] roles = new SelectItem[perms.size()]; + int index = 0; + for (String permission : perms) + { + String displayLabel = bundle.getString(permission); + roles[index++] = new SelectItem(permission, displayLabel); + } + + return roles; + } + + /** + * @return Returns the notify listbox selection. + */ + public String getNotify() + { + return this.notify; + } + + /** + * @param notify The notify listbox selection to set. + */ + public void setNotify(String notify) + { + this.notify = notify; + } + + /** + * @return Returns the automaticText. + */ + public String getAutomaticText() + { + return this.automaticText; + } + + /** + * @param automaticText The automaticText to set. + */ + public void setAutomaticText(String automaticText) + { + this.automaticText = automaticText; + } + + /** + * @return Returns the email body text. + */ + public String getBody() + { + return this.body; + } + + /** + * @param body The email body text to set. + */ + public void setBody(String body) + { + this.body = body; + } + + /** + * @return Returns the email subject text. + */ + public String getSubject() + { + return this.subject; + } + + /** + * @param subject The email subject text to set. + */ + public void setSubject(String subject) + { + this.subject = subject; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + String stepDesc = null; + + switch (this.currentStep) + { + case 1: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_DESCRIPTION_ID); + break; + } + case 2: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_DESCRIPTION_ID); + break; + } + default: + { + stepDesc = ""; + } + } + + return stepDesc; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 2: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#next() + */ + public String next() + { + String outcome = super.next(); + + if (outcome.equals("notify")) + { + FacesContext context = FacesContext.getCurrentInstance(); + + // prepare automatic text for email and screen + StringBuilder buf = new StringBuilder(256); + + String personName = Application.getCurrentUser(context).getFullName(getNodeService()); + String msgInvite = Application.getMessage(context, MSG_INVITED_SPACE); + Node node = this.browseBean.getActionSpace(); + String path = this.nodeService.getPath(node.getNodeRef()).toDisplayPath(this.nodeService); + buf.append(MessageFormat.format(msgInvite, new Object[] { + path + '/' + node.getName(), + personName}) ); + + this.internalSubject = buf.toString(); + + buf.append("
"); + + String msgRole = Application.getMessage(context, MSG_INVITED_ROLE); + String roleText; + if (this.userGroupRoles.size() != 0) + { + String roleMsg = Application.getMessage(context, userGroupRoles.get(0).getRole()); + roleText = MessageFormat.format(msgRole, roleMsg); + } + else + { + roleText = MessageFormat.format(msgRole, "[role]"); + } + + buf.append(roleText); + + this.automaticText = buf.toString(); + } + + return outcome; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch(step) + { + case 1: + { + outcome = "invite"; + break; + } + case 2: + { + outcome = "notify"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } + + /** + * @return a cached list of available folder permissions + */ + private Set getFolderPermissions() + { + if (this.folderPermissions == null) + { + this.folderPermissions = this.permissionService.getSettablePermissions(ContentModel.TYPE_FOLDER); + } + + return this.folderPermissions; + } + + + /** + * Simple wrapper class to represent a user/group and a role combination + */ + public static class UserGroupRole + { + public UserGroupRole(String authority, String role, String label) + { + this.authority = authority; + this.role = role; + this.label = label; + } + + public String getAuthority() + { + return this.authority; + } + + public String getRole() + { + return this.role; + } + + public String getLabel() + { + return this.label; + } + + private String authority; + private String role; + private String label; + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewActionWizard.java b/source/java/org/alfresco/web/bean/wizard/NewActionWizard.java new file mode 100644 index 0000000000..7cc15ad259 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewActionWizard.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.transaction.UserTransaction; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Handler class used by the New Action Wizard + * + * @author Kevin Roast + */ +public class NewActionWizard extends BaseActionWizard +{ + private static Log logger = LogFactory.getLog(NewActionWizard.class); + + private static final String ERROR = "error_action"; + + // TODO: retrieve these from the config service + private static final String WIZARD_TITLE_ID = "create_action_title"; + private static final String WIZARD_DESC_ID = "create_action_desc"; + private static final String STEP1_TITLE_ID = "create_action_step1_title"; + private static final String STEP2_TITLE_ID = "create_action_step2_title"; + private static final String FINISH_INSTRUCTION_ID = "create_action_finish_instruction"; + + /** + * Deals with the finish button being pressed + * + * @return outcome + */ + public String finish() + { + String outcome = FINISH_OUTCOME; + + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // build the action params map based on the selected action instance + Map actionParams = buildActionParams(); + + // build the action to execute + Action action = this.actionService.createAction(getAction()); + action.setParameterValues(actionParams); + + // execute the action on the current document node + this.actionService.executeAction(action, this.browseBean.getDocument().getNodeRef()); + + if (logger.isDebugEnabled()) + { + logger.debug("Executed action '" + this.action + + "' with action params of " + + this.currentActionProperties); + } + + // reset the current document properties/aspects in case we have changed them + // during the execution of the custom action + this.browseBean.getDocument().reset(); + + // commit the transaction + tx.commit(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), ERROR), e.getMessage()), e); + outcome = null; + } + + return outcome; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + return ""; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + case 3: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 3: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + } + + /** + * @return Returns the summary data for the wizard. + */ + public String getSummary() + { + String summaryAction = this.actionService.getActionDefinition( + this.action).getTitle(); + + return buildSummary( + new String[] {"Action"}, + new String[] {summaryAction}); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch(step) + { + case 1: + { + outcome = "action"; + break; + } + case 2: + { + outcome = this.action; + break; + } + case 3: + { + outcome = "summary"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewForumWizard.java b/source/java/org/alfresco/web/bean/wizard/NewForumWizard.java new file mode 100644 index 0000000000..ee889b6e78 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewForumWizard.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.util.ArrayList; +import java.util.List; + +import javax.faces.context.FacesContext; + +import org.alfresco.model.ForumModel; +import org.alfresco.web.ui.common.component.UIListItem; + +/** + * Wizard bean used for creating and editing forum spaces + * + * @author gavinc + */ +public class NewForumWizard extends NewSpaceWizard +{ + public static final String FORUM_ICON_DEFAULT = "forum_large"; + + protected String forumStatus; + + protected List forumIcons; + + /** + * Returns the status of the forum + * + * @return The status of the forum + */ + public String getForumStatus() + { + return this.forumStatus; + } + + /** + * Sets the status of the forum + * + * @param forumStatus The status + */ + public void setForumStatus(String forumStatus) + { + this.forumStatus = forumStatus; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#init() + */ + public void init() + { + super.init(); + + this.spaceType = ForumModel.TYPE_FORUM.toString(); + this.icon = FORUM_ICON_DEFAULT; + this.forumStatus = "0"; + } + + /** + * Returns a list of icons to allow the user to select from. + * + * @return A list of icons + */ + @SuppressWarnings("unchecked") + public List getIcons() + { + // return the various forum icons + if (this.forumIcons == null) + { + this.forumIcons = new ArrayList(1); + + UIListItem item = new UIListItem(); + item.setValue(FORUM_ICON_DEFAULT); + item.getAttributes().put("image", "/images/icons/forum_large.gif"); + this.forumIcons.add(item); + } + + return this.forumIcons; + } + + /** + * @see org.alfresco.web.bean.wizard.NewSpaceWizard#performCustomProcessing(javax.faces.context.FacesContext) + */ + @Override + protected void performCustomProcessing(FacesContext context) + { + // add or update the ForumModel.PROP_STATUS property depending on the editMode + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewForumsWizard.java b/source/java/org/alfresco/web/bean/wizard/NewForumsWizard.java new file mode 100644 index 0000000000..08fdbfe817 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewForumsWizard.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.model.ForumModel; +import org.alfresco.web.ui.common.component.UIListItem; + +/** + * Wizard bean used for creating and editing forums spaces + * + * @author gavinc + */ +public class NewForumsWizard extends NewSpaceWizard +{ + public static final String FORUMS_ICON_DEFAULT = "forums_large"; + + protected List forumsIcons; + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#init() + */ + public void init() + { + super.init(); + + this.spaceType = ForumModel.TYPE_FORUMS.toString(); + this.icon = FORUMS_ICON_DEFAULT; + } + + /** + * Returns a list of icons to allow the user to select from. + * + * @return A list of icons + */ + @SuppressWarnings("unchecked") + public List getIcons() + { + // return the various forums icons + if (this.forumsIcons == null) + { + this.forumsIcons = new ArrayList(1); + + UIListItem item = new UIListItem(); + item.setValue(FORUMS_ICON_DEFAULT); + item.getAttributes().put("image", "/images/icons/forums_large.gif"); + this.forumsIcons.add(item); + } + + return this.forumsIcons; + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewPostWizard.java b/source/java/org/alfresco/web/bean/wizard/NewPostWizard.java new file mode 100644 index 0000000000..b0d9ff1f7f --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewPostWizard.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import javax.faces.context.FacesContext; + +import org.alfresco.model.ForumModel; +import org.alfresco.util.GUID; +import org.alfresco.web.bean.repository.Repository; + +/** + * Backing bean for posting forum articles. + * + * @author gavinc + */ +public class NewPostWizard extends CreateContentWizard +{ + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#init() + */ + @Override + public void init() + { + super.init(); + + // set up for creating a post instead of HTML + this.createType = CONTENT_TEXT; + this.objectType = ForumModel.TYPE_POST.toString(); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#finish() + */ + @Override + public String finish() + { + // create appropriate values for filename, title and content type + this.fileName = GUID.generate() + ".txt"; + this.contentType = Repository.getMimeTypeForFileName( + FacesContext.getCurrentInstance(), this.fileName); + this.title = this.fileName; + + return super.finish(); + } + + +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewReplyWizard.java b/source/java/org/alfresco/web/bean/wizard/NewReplyWizard.java new file mode 100644 index 0000000000..d7b150157d --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewReplyWizard.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import javax.faces.event.ActionEvent; + +import org.alfresco.model.ContentModel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Backing bean for posting replies to forum articles. + * + * @author gavinc + */ +public class NewReplyWizard extends NewPostWizard +{ + private static Log logger = LogFactory.getLog(NewReplyWizard.class); + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#startWizard(javax.faces.event.ActionEvent) + */ + @Override + public void startWizard(ActionEvent event) + { + super.startWizard(event); + + // also setup the content in the browse bean + this.browseBean.setupContentAction(event); + } + + /** + * @see org.alfresco.web.bean.wizard.BaseContentWizard#performCustomProcessing() + */ + @Override + protected void performCustomProcessing() + { + if (this.editMode == false) + { + // setup the referencing aspect with the references association + // between the new post and the one being replied to + this.nodeService.addAspect(this.createdNode, ContentModel.ASPECT_REFERENCING, null); + this.nodeService.createAssociation(this.createdNode, this.browseBean.getDocument().getNodeRef(), + ContentModel.ASSOC_REFERENCES); + + if (logger.isDebugEnabled()) + { + logger.debug("created new node: " + this.createdNode); + logger.debug("existing node: " + this.browseBean.getDocument().getNodeRef()); + } + } + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewRuleWizard.java b/source/java/org/alfresco/web/bean/wizard/NewRuleWizard.java new file mode 100644 index 0000000000..efb25bb039 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewRuleWizard.java @@ -0,0 +1,1490 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.model.DataModel; +import javax.faces.model.ListDataModel; +import javax.faces.model.SelectItem; +import javax.transaction.UserTransaction; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigService; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluator; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.evaluator.HasAspectEvaluator; +import org.alfresco.repo.action.evaluator.InCategoryEvaluator; +import org.alfresco.repo.action.evaluator.IsSubTypeEvaluator; +import org.alfresco.repo.action.executer.CheckInActionExecuter; +import org.alfresco.repo.action.executer.SimpleWorkflowActionExecuter; +import org.alfresco.repo.action.executer.SpecialiseTypeActionExecuter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionConditionDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.RulesBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Handler class used by the New Space Wizard + * + * @author gavinc + */ +public class NewRuleWizard extends BaseActionWizard +{ + // parameter names for actions + public static final String PROP_ACTION_NAME = "actionName"; + public static final String PROP_ACTION_SUMMARY = "actionSummary"; + + // parameter names for conditions + public static final String PROP_CONDITION_NAME = "conditionName"; + public static final String PROP_CONDITION_SUMMARY = "conditionSummary"; + public static final String PROP_CONDITION_NOT = "notcondition"; + public static final String PROP_PROPERTY = "property"; + public static final String PROP_CONTAINS_TEXT = "containstext"; + public static final String PROP_MODEL_TYPE = "modeltype"; + public static final String PROP_MIMETYPE = "mimetype"; + public static final String PROP_MODEL_ASPECT = "modelaspect"; + public static final String PROP_TYPE_OR_ASPECT = "typeoraspect"; + + private static Log logger = LogFactory.getLog(NewRuleWizard.class); + + private static final String ERROR = "error_rule"; + + // TODO: retrieve these from the config service + private static final String WIZARD_TITLE_ID = "new_rule_title"; + private static final String WIZARD_TITLE_EDIT_ID = "new_rule_title_edit"; + private static final String WIZARD_DESC_ID = "new_rule_desc"; + private static final String WIZARD_DESC_EDIT_ID = "new_rule_desc_edit"; + private static final String STEP1_TITLE_ID = "new_rule_step1_title"; + private static final String STEP2_TITLE_ID = "new_rule_step2_title"; + private static final String STEP3_TITLE_ID = "new_rule_step3_title"; + private static final String FINISH_INSTRUCTION_ID = "new_rule_finish_instruction"; + private static final String FINISH_INSTRUCTION_EDIT_ID = "new_rule_finish_instruction_edit"; + + // new rule wizard specific properties + private String title; + private String description; + private String type; + private String condition; + private boolean runInBackground; + private boolean applyToSubSpaces; + private boolean editingAction; + private boolean editingCondition; + + private RuleService ruleService; + private RulesBean rulesBean; + + private List modelTypes; + private List mimeTypes; + private List types; + private List conditions; + private List typesAndAspects; + private Map conditionDescriptions; + private Map currentConditionProperties; + + private List> allActionsProperties; + private List> allConditionsProperties; + + private DataModel allActionsDataModel; + private DataModel allConditionsDataModel; + + /** + * Deals with the finish button being pressed + * + * @return outcome + */ + public String finish() + { + String outcome = FINISH_OUTCOME; + + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // get hold of the space the rule will apply to and make sure + // it is actionable + Node currentSpace = browseBean.getActionSpace(); + + Rule rule = null; + + if (this.editMode) + { + // update the existing rule in the repository + rule = this.rulesBean.getCurrentRule(); + + // remove all the conditions and actions from the current rule + rule.removeAllActionConditions(); + rule.removeAllActions(); + } + else + { + rule = this.ruleService.createRule(this.getType()); + } + + // setup the rule and add it to the space + rule.setTitle(this.title); + rule.setDescription(this.description); + rule.applyToChildren(this.applyToSubSpaces); + rule.setExecuteAsynchronously(this.runInBackground); + + // add all the conditions to the rule + for (Map condParams : this.allConditionsProperties) + { + Map repoCondParams = buildConditionParams(condParams); + + // add the condition to the rule + ActionCondition condition = this.actionService.createActionCondition( + (String)condParams.get(PROP_CONDITION_NAME)); + condition.setParameterValues(repoCondParams); + + // specify whether the condition result should be inverted + Boolean not = (Boolean)condParams.get(PROP_CONDITION_NOT); + condition.setInvertCondition(((Boolean)not).booleanValue()); + + rule.addActionCondition(condition); + } + + // add all the actions to the rule + for (Map actionParams : this.allActionsProperties) + { + // use the base class version of buildActionParams(), but for this we need + // to setup the currentActionProperties and action variables + String actionName = (String)actionParams.get(PROP_ACTION_NAME); + this.action = actionName; + this.currentActionProperties = actionParams; + Map repoActionParams = buildActionParams(); + + // add the action to the rule + Action action = this.actionService.createAction(actionName); + action.setParameterValues(repoActionParams); + rule.addAction(action); + } + + // Save the rule + this.ruleService.saveRule(currentSpace.getNodeRef(), rule); + + if (logger.isDebugEnabled()) + { + logger.debug(this.editMode ? "Updated" : "Added" + " rule '" + this.title + "'"); + } + + // commit the transaction + tx.commit(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), ERROR), e.getMessage()), e); + outcome = null; + } + + return outcome; + } + + /** + * Returns the properties for all the conditions as a JSF DataModel + * + * @return JSF DataModel representing the condition properties + */ + public DataModel getAllConditionsDataModel() + { + if (this.allConditionsDataModel == null) + { + this.allConditionsDataModel = new ListDataModel(); + } + + this.allConditionsDataModel.setWrappedData(this.allConditionsProperties); + + return this.allConditionsDataModel; + } + + /** + * Displays the settings page for the current condition being added (if required) + * + * @return The outcome + */ + public String promptForConditionValues() + { + String outcome = null; + + // set the flag to show we are creating a new condition + this.editingCondition = false; + + if ("no-condition".equals(this.condition)) + { + HashMap condProps = new HashMap(3); + condProps.put(PROP_CONDITION_NAME, this.condition); + condProps.put(PROP_CONDITION_SUMMARY, Application.getMessage( + FacesContext.getCurrentInstance(), "condition_no_condition")); + condProps.put(PROP_CONDITION_NOT, Boolean.FALSE); + this.allConditionsProperties.add(condProps); + + // NOTE: we don't set an outcome to stay on the same page as there are + // no settings related to 'no-condition' + + if (logger.isDebugEnabled()) + logger.debug("Add 'no-condition' condition to list"); + } + else if (this.condition != null) + { + HashMap condProps = new HashMap(3); + condProps.put(PROP_CONDITION_NAME, this.condition); + this.currentConditionProperties = condProps; + outcome = this.condition; + + if (logger.isDebugEnabled()) + logger.debug("Added '" + this.condition + "' condition to list"); + } + + // reset the selected condition drop down + this.condition = null; + + return outcome; + } + + /** + * Sets up the context for editing existing condition values + * + * @return The outcome + */ + public String editCondition() + { + // use the built in JSF support for retrieving the object for the + // row that was clicked by the user + Map conditionToEdit = (Map)this.allConditionsDataModel.getRowData(); + this.condition = (String)conditionToEdit.get(PROP_CONDITION_NAME); + this.currentConditionProperties = conditionToEdit; + + // set the flag to show we are editing a condition + this.editingCondition = true; + + return this.condition; + } + + /** + * Adds the condition just setup by the user to the list of conditions for the rule + * + * @return The outcome + */ + public String addCondition() + { + String summary = buildConditionSummary(this.currentConditionProperties); + + if (summary != null) + { + this.currentConditionProperties.put(PROP_CONDITION_SUMMARY, summary); + } + + if (this.editingCondition) + { + this.condition = null; + } + else + { + this.allConditionsProperties.add(this.currentConditionProperties); + } + + // re-display the conditions step + return "condition"; + } + + /** + * Removes the requested condition from the list + * + * @return The outcome + */ + public String removeCondition() + { + // use the built in JSF support for retrieving the object for the + // row that was clicked by the user + Map conditionToRemove = (Map)this.allConditionsDataModel.getRowData(); + this.allConditionsProperties.remove(conditionToRemove); + + // reset the action drop down + this.condition = null; + + // return no outcome to refresh page + return null; + } + + /** + * Cancels the addition of the condition + * + * @return The outcome + */ + public String cancelAddCondition() + { + if (this.editingCondition) + { + this.condition = null; + } + else + { + this.currentConditionProperties.clear(); + } + + + return "condition"; + } + + /** + * Returns the properties for all the actions as a JSF DataModel + * + * @return JSF DataModel representing the action properties + */ + public DataModel getAllActionsDataModel() + { + if (this.allActionsDataModel == null) + { + this.allActionsDataModel = new ListDataModel(); + } + + this.allActionsDataModel.setWrappedData(this.allActionsProperties); + + return this.allActionsDataModel; + } + + /** + * Displays the settings page for the current action being added + * + * @return The outcome + */ + public String promptForActionValues() + { + // set the flag to show we are creating a new action + this.editingAction = false; + + HashMap actionProps = new HashMap(3); + actionProps.put(PROP_ACTION_NAME, this.action); + this.currentActionProperties = actionProps; + + String outcome = this.action; + + if (SimpleWorkflowActionExecuter.NAME.equals(this.action)) + { + this.currentActionProperties.put("approveAction", "move"); + this.currentActionProperties.put("rejectStepPresent", "yes"); + this.currentActionProperties.put("rejectAction", "move"); + + if (logger.isDebugEnabled()) + logger.debug("Added '" + SimpleWorkflowActionExecuter.NAME + + "' action to list"); + } + else if (CheckInActionExecuter.NAME.equals(this.action)) + { + this.currentActionProperties.put(PROP_CHECKIN_MINOR, new Boolean(true)); + + if (logger.isDebugEnabled()) + logger.debug("Added '" + CheckInActionExecuter.NAME + + "' action to list"); + } + else if ("extract-metadata".equals(this.action)) + { + // This one (currently) has no parameters, so just add it... + actionProps.put(PROP_ACTION_SUMMARY, buildActionSummary(actionProps)); + this.allActionsProperties.add(actionProps); + + outcome = null; + + if (logger.isDebugEnabled()) + logger.debug("Added 'extract-metadata' action to list"); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Added '" + this.action + "' action to list"); + } + + // reset the selected action drop down + this.action = null; + + return outcome; + } + + /** + * Sets up the context for editing existing action values + * + * @return The outcome + */ + public String editAction() + { + // use the built in JSF support for retrieving the object for the + // row that was clicked by the user + Map actionToEdit = (Map)this.allActionsDataModel.getRowData(); + this.action = (String)actionToEdit.get(PROP_ACTION_NAME); + this.currentActionProperties = actionToEdit; + + // set the flag to show we are editing an action + this.editingAction = true; + + return this.action; + } + + /** + * Adds the action just setup by the user to the list of actions for the rule + * + * @return The outcome + */ + public String addAction() + { + String summary = buildActionSummary(this.currentActionProperties); + + if (summary != null) + { + this.currentActionProperties.put(PROP_ACTION_SUMMARY, summary); + } + + if (this.editingAction) + { + this.action = null; + } + else + { + this.allActionsProperties.add(this.currentActionProperties); + } + + // re-display the actions step + return "action"; + } + + /** + * Removes the requested action from the list + * + * @return The outcome + */ + public String removeAction() + { + // use the built in JSF support for retrieving the object for the + // row that was clicked by the user + Map actionToRemove = (Map)this.allActionsDataModel.getRowData(); + this.allActionsProperties.remove(actionToRemove); + + // reset the action drop down + this.action = null; + + // return no outcome to refresh page + return null; + } + + /** + * Cancels the addition of the action + * + * @return The outcome + */ + public String cancelAddAction() + { + if (this.editingAction) + { + this.condition = null; + } + else + { + this.currentActionProperties.clear(); + } + + return "action"; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + if (this.editMode) + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_EDIT_ID); + } + else + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_ID); + } + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + if (this.editMode) + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_EDIT_ID); + } + else + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_ID); + } + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + String stepDesc = null; + + switch (this.currentStep) + { + case 4: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_DESCRIPTION_ID); + break; + } + default: + { + stepDesc = ""; + } + } + + return stepDesc; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + case 3: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP3_TITLE_ID); + break; + } + case 4: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 4: + { + if (this.editMode) + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_EDIT_ID); + } + else + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + } + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + this.title = null; + this.description = null; + this.type = "inbound"; + this.condition = null; + this.action = null; + this.applyToSubSpaces = false; + this.runInBackground = false; + this.conditions = null; + this.conditionDescriptions = null; + + this.allConditionsProperties = new ArrayList>(); + this.allActionsProperties = new ArrayList>(); + } + + /** + * Sets the context of the rule up before performing the + * standard wizard editing steps + * + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#startWizardForEdit(javax.faces.event.ActionEvent) + */ + public void startWizardForEdit(ActionEvent event) + { + // setup context for rule to be edited + this.rulesBean.setupRuleAction(event); + + // perform the usual edit processing + super.startWizardForEdit(event); + } + + /** + * Populates the values of the backing bean ready for editing the rule + * + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#populate() + */ + public void populate() + { + // get hold of the current rule details + Rule rule = this.rulesBean.getCurrentRule(); + + if (rule == null) + { + throw new AlfrescoRuntimeException("Failed to locate the current rule"); + } + + // populate the bean with current values + this.type = rule.getRuleTypeName(); + this.title = rule.getTitle(); + this.description = rule.getDescription(); + this.applyToSubSpaces = rule.isAppliedToChildren(); + this.runInBackground = rule.getExecuteAsychronously(); + + // populate the conditions list with maps of properties representing each condition + List conditions = rule.getActionConditions(); + for (ActionCondition condition : conditions) + { + Map params = populateCondition(condition); + this.allConditionsProperties.add(params); + } + + List actions = rule.getActions(); + for (Action action : actions) + { + // use the base class version of populateActionFromProperties(), + // but for this we need to setup the currentActionProperties and + // action variables + this.currentActionProperties = new HashMap(3); + this.action = action.getActionDefinitionName(); + populateActionFromProperties(action.getParameterValues()); + + // also add the name and summary + this.currentActionProperties.put(PROP_ACTION_NAME, this.action); + // generate the summary + this.currentActionProperties.put(PROP_ACTION_SUMMARY, + buildActionSummary(this.currentActionProperties)); + + // add the populated currentActionProperties to the list + this.allActionsProperties.add(this.currentActionProperties); + } + + // reset the current action + this.action = null; + } + + /** + * @return Returns the summary data for the wizard. + */ + public String getSummary() + { + // create the summary using all the conditions + StringBuilder conditionsSummary = new StringBuilder(); + for (Map props : this.allConditionsProperties) + { + conditionsSummary.append(props.get(PROP_CONDITION_SUMMARY)); + conditionsSummary.append("
"); + } + + // create the summary using all the actions + StringBuilder actionsSummary = new StringBuilder(); + for (Map props : this.allActionsProperties) + { + actionsSummary.append(props.get(PROP_ACTION_SUMMARY)); + actionsSummary.append("
"); + } + + ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); + + String backgroundYesNo = this.runInBackground ? bundle.getString("yes") : bundle.getString("no"); + String subSpacesYesNo = this.applyToSubSpaces ? bundle.getString("yes") : bundle.getString("no"); + + return buildSummary( + new String[] {bundle.getString("rule_type"), bundle.getString("name"), bundle.getString("description"), + bundle.getString("apply_to_sub_spaces"), bundle.getString("run_in_background"), + bundle.getString("conditions"), bundle.getString("actions")}, + new String[] {this.type, this.title, this.description, subSpacesYesNo, backgroundYesNo, + conditionsSummary.toString(), actionsSummary.toString()}); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#back() + */ + public String back() + { + // reset the drop downs when back is clicked + this.action = null; + this.condition = null; + + return super.back(); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#next() + */ + public String next() + { + // reset the drop downs when next is clicked + this.action = null; + this.condition = null; + + return super.next(); + } + + /** + * @return Returns the description. + */ + public String getDescription() + { + return description; + } + + /** + * @param description The description to set. + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * @return Returns the title. + */ + public String getTitle() + { + return title; + } + + /** + * @param title The title to set. + */ + public void setTitle(String title) + { + this.title = title; + } + + /** + * @return Returns whether the rule should run in the background + */ + public boolean getRunInBackground() + { + return this.runInBackground; + } + + /** + * @param runInBackground Sets whether the rule should run in the background + */ + public void setRunInBackground(boolean runInBackground) + { + this.runInBackground = runInBackground; + } + + /** + * @return Returns whether the rule should be applied to sub spaces i.e. if it gets inherited + */ + public boolean getApplyToSubSpaces() + { + return this.applyToSubSpaces; + } + + /** + * @param applyToSubSpaces Sets whether the rule will get applied to sub spaces + */ + public void setApplyToSubSpaces(boolean applyToSubSpaces) + { + this.applyToSubSpaces = applyToSubSpaces; + } + + /** + * @return Returns the type. + */ + public String getType() + { + return type; + } + + /** + * @param type The type to set + */ + public void setType(String type) + { + this.type = type; + } + + /** + * @return Returns the selected condition + */ + public String getCondition() + { + return this.condition; + } + + /** + * @param condition Sets the selected condition + */ + public void setCondition(String condition) + { + this.condition = condition; + } + + /** + * @param ruleService Sets the rule service to use + */ + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + + /** + * @param mimetypeService Sets the mimetype service to use + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * Sets the RulesBean instance to be used by the wizard in edit mode + * + * @param rulesBean The RulesBean + */ + public void setRulesBean(RulesBean rulesBean) + { + this.rulesBean = rulesBean; + } + + /** + * @return Returns the list of selectable actions + */ + public List getActions() + { + if (this.actions == null) + { + super.getActions(); + + // add the "Select an action" entry at the beginning of the list + this.actions.add(0, new SelectItem("null", + Application.getMessage(FacesContext.getCurrentInstance(), "select_an_action"))); + } + + return this.actions; + } + + /** + * Returns a list of the types available in the repository + * + * @return List of SelectItem objects + */ + public List getModelTypes() + { + if (this.modelTypes == null) + { + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Action Wizards"); + if (wizardCfg != null) + { + ConfigElement typesCfg = wizardCfg.getConfigElement("types"); + if (typesCfg != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + this.modelTypes = new ArrayList(); + for (ConfigElement child : typesCfg.getChildren()) + { + QName idQName = Repository.resolveToQName(child.getAttribute("name")); + + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + TypeDefinition typeDef = this.dictionaryService.getType(idQName); + if (typeDef != null) + { + label = typeDef.getTitle(); + } + else + { + label = idQName.getLocalName(); + } + } + + this.modelTypes.add(new SelectItem(idQName.toString(), label)); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.modelTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + else + { + logger.warn("Could not find types configuration element"); + } + } + else + { + logger.warn("Could not find Action Wizards configuration section"); + } + } + + return this.modelTypes; + } + + /** + * Returns a list of mime types in the system + * + * @return List of mime types + */ + public List getMimeTypes() + { + if (this.mimeTypes == null) + { + this.mimeTypes = new ArrayList(50); + + Map mimeTypes = mimetypeService.getDisplaysByMimetype(); + for (String mimeType : mimeTypes.keySet()) + { + this.mimeTypes.add(new SelectItem(mimeType, mimeTypes.get(mimeType))); + } + + // make sure the list is sorted by the values + QuickSort sorter = new QuickSort(this.mimeTypes, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + return this.mimeTypes; + } + + /** + * @return Returns the list of selectable conditions + */ + public List getConditions() + { + if (this.conditions == null) + { + List ruleConditions = this.actionService.getActionConditionDefinitions(); + this.conditions = new ArrayList(ruleConditions.size()); + for (ActionConditionDefinition ruleConditionDef : ruleConditions) + { + // add to SelectItem list + this.conditions.add(new SelectItem(ruleConditionDef.getName(), + ruleConditionDef.getTitle())); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.conditions, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + + // add the "Select a condition" entry at the beginning of the list + this.conditions.add(0, new SelectItem("null", + Application.getMessage(FacesContext.getCurrentInstance(), "select_a_condition"))); + } + + return this.conditions; + } + + /** + * @return Returns a map of all the condition descriptions + */ + public Map getConditionDescriptions() + { + if (this.conditionDescriptions == null) + { + List ruleConditions = this.actionService.getActionConditionDefinitions(); + this.conditionDescriptions = new HashMap(ruleConditions.size()); + for (ActionConditionDefinition ruleConditionDef : ruleConditions) + { + this.conditionDescriptions.put(ruleConditionDef.getName(), + ruleConditionDef.getDescription()); + } + } + + return this.conditionDescriptions; + } + + /** + * @return Returns the types of rules that can be defined + */ + public List getTypes() + { + if (this.types == null) + { + List ruleTypes = this.ruleService.getRuleTypes(); + this.types = new ArrayList(ruleTypes.size()); + for (RuleType ruleType : ruleTypes) + { + this.types.add(new SelectItem(ruleType.getName(), ruleType.getDisplayLabel())); + } + } + + return this.types; + } + + /** + * @return Gets the condition settings + */ + public Map getConditionProperties() + { + return this.currentConditionProperties; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch(step) + { + case 1: + { + outcome = "details"; + break; + } + case 2: + { + outcome = "condition"; + break; + } + case 3: + { + outcome = "action"; + break; + } + case 4: + { + outcome = "summary"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } + + /** + * Builds the Map of properties for the given condition in the format the repo is expecting + * + * @param params The Map of properties built from the UI + * @return The Map the repo is expecting + */ + protected Map buildConditionParams(Map params) + { + Map repoParams = new HashMap(params.size()); + + String condName = (String)params.get(PROP_CONDITION_NAME); + if (condName.equals(ComparePropertyValueEvaluator.NAME)) + { + repoParams.put(ComparePropertyValueEvaluator.PARAM_VALUE, params.get(PROP_CONTAINS_TEXT)); + } + else if (condName.equals(InCategoryEvaluator.NAME)) + { + // put the selected category in the condition params + repoParams.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, params.get(PROP_CATEGORY)); + + // add the classifiable aspect + repoParams.put(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, ContentModel.ASPECT_GEN_CLASSIFIABLE); + } + else if (condName.equals(IsSubTypeEvaluator.NAME)) + { + // add the model type + repoParams.put(IsSubTypeEvaluator.PARAM_TYPE, QName.createQName((String)params.get(PROP_MODEL_TYPE))); + } + else if (condName.equals(HasAspectEvaluator.NAME)) + { + // add the aspect + repoParams.put(HasAspectEvaluator.PARAM_ASPECT, QName.createQName((String)params.get(PROP_ASPECT))); + } + else if (condName.equals(CompareMimeTypeEvaluator.NAME)) + { + repoParams.put(CompareMimeTypeEvaluator.PARAM_VALUE, params.get(PROP_MIMETYPE)); + } + + return repoParams; + } + + /** + * Populates a Map of properties the wizard is expecting for the given condition + * + * @param condition The condition to build the map for + */ + protected Map populateCondition(ActionCondition condition) + { + // find out what the condition is called + Map condProps = new HashMap(3); + String name = condition.getActionConditionDefinitionName(); + condProps.put(PROP_CONDITION_NAME, name); + + // add the appropriate properties + Map repoCondProps = condition.getParameterValues(); + if (name.equals(ComparePropertyValueEvaluator.NAME)) + { + condProps.put(PROP_CONTAINS_TEXT, (String)repoCondProps.get(ComparePropertyValueEvaluator.PARAM_VALUE)); + } + else if (name.equals(InCategoryEvaluator.NAME)) + { + NodeRef catNodeRef = (NodeRef)repoCondProps.get(InCategoryEvaluator.PARAM_CATEGORY_VALUE); + condProps.put(PROP_CATEGORY, catNodeRef); + } + else if (name.equals(IsSubTypeEvaluator.NAME)) + { + condProps.put(PROP_MODEL_TYPE, ((QName)repoCondProps.get(IsSubTypeEvaluator.PARAM_TYPE)).toString()); + } + else if (name.equals(HasAspectEvaluator.NAME)) + { + condProps.put(PROP_ASPECT, ((QName)repoCondProps.get(HasAspectEvaluator.PARAM_ASPECT)).toString()); + } + else if (name.equals(CompareMimeTypeEvaluator.NAME)) + { + condProps.put(PROP_MIMETYPE, repoCondProps.get(CompareMimeTypeEvaluator.PARAM_VALUE)); + } + + // specify whether the condition result should be inverted + condProps.put(PROP_CONDITION_NOT, Boolean.valueOf(condition.getInvertCondition())); + + // generate the summary + condProps.put(PROP_CONDITION_SUMMARY, buildConditionSummary(condProps)); + + return condProps; + } + + /** + * Returns a summary string for the given condition parameters + * + * @return The summary or null if a summary could not be built + */ + protected String buildConditionSummary(Map props) + { + String summaryResult = null; + + String condName = (String)props.get(PROP_CONDITION_NAME); + if (condName != null) + { + StringBuilder summary = new StringBuilder(); + + String msgId = "condition_" + condName.replace('-', '_'); + + // JSF is putting the boolean into the map as a Boolean object so we + // need to handle that - adding a converter doesn't seem to help! + Boolean not = (Boolean)props.get(PROP_CONDITION_NOT); + if (not.booleanValue()) + { + msgId = msgId + "_not"; + } + + if (logger.isDebugEnabled()) + logger.debug("Looking up condition summary string: " + msgId); + + summary.append(Application.getMessage(FacesContext.getCurrentInstance(), msgId)); + summary.append(" "); + + // define a summary to be added for each condition + if ("in-category".equals(condName)) + { + String name = Repository.getNameForNode(this.nodeService, (NodeRef)props.get(PROP_CATEGORY)); + summary.append("'").append(name).append("'"); + } + else if ("compare-property-value".equals(condName)) + { + summary.append("'"); + summary.append(props.get(PROP_CONTAINS_TEXT)); + summary.append("'"); + } + else if ("is-subtype".equals(condName)) + { + // find the label used by looking through the SelectItem list + String typeName = (String)props.get(PROP_MODEL_TYPE); + for (SelectItem item : this.getModelTypes()) + { + if (item.getValue().equals(typeName)) + { + summary.append("'").append(item.getLabel()).append("'"); + break; + } + } + } + else if ("has-aspect".equals(condName)) + { + // find the label used by looking through the SelectItem list + String aspectName = (String)props.get(PROP_ASPECT); + for (SelectItem item : this.getAspects()) + { + if (item.getValue().equals(aspectName)) + { + summary.append("'").append(item.getLabel()).append("'"); + break; + } + } + } + else if (CompareMimeTypeEvaluator.NAME.equals(condName)) + { + String mimetype = (String)props.get(PROP_MIMETYPE); + for (SelectItem item : this.getMimeTypes()) + { + if (item.getValue().equals(mimetype)) + { + summary.append("'").append(item.getLabel()).append("'"); + break; + } + } + } + + summaryResult = summary.toString(); + } + + return summaryResult; + } + + /** + * Returns a summary string for the given action parameters + * + * @return The summary or null if a summary could not be built + */ + protected String buildActionSummary(Map props) + { + String summaryResult = null; + + String actionName = (String)this.currentActionProperties.get(PROP_ACTION_NAME); + if (actionName != null) + { + StringBuilder summary = new StringBuilder(); + summary.append(Application.getMessage(FacesContext.getCurrentInstance(), + "action_" + actionName.replace('-', '_'))); + summary.append(" "); + + // define a summary to be added for each action + if ("add-features".equals(actionName)) + { + String aspect = (String)this.currentActionProperties.get(PROP_ASPECT); + + // find the label used by looking through the SelectItem list + for (SelectItem item : this.getAspects()) + { + if (item.getValue().equals(aspect)) + { + summary.append("'").append(item.getLabel()).append("'"); + break; + } + } + } + else if ("simple-workflow".equals(actionName)) + { + // just leave the summary as the title for now + String approveStepName = (String)this.currentActionProperties.get(PROP_APPROVE_STEP_NAME); + String approveAction = (String)this.currentActionProperties.get(PROP_APPROVE_ACTION); + NodeRef approveFolder = (NodeRef)this.currentActionProperties.get(PROP_APPROVE_FOLDER); + String approveFolderName = Repository.getNameForNode(this.nodeService, approveFolder); + String approveMsg = MessageFormat.format(summary.toString(), + new Object[] {Application.getMessage(FacesContext.getCurrentInstance(), approveAction), + approveFolderName, approveStepName}); + + String rejectStep = (String)this.currentActionProperties.get(PROP_REJECT_STEP_PRESENT); + + String rejectMsg = null; + if (rejectStep != null && "yes".equals(rejectStep)) + { + String rejectStepName = (String)this.currentActionProperties.get(PROP_REJECT_STEP_NAME); + String rejectAction = (String)this.currentActionProperties.get(PROP_REJECT_ACTION); + NodeRef rejectFolder = (NodeRef)this.currentActionProperties.get(PROP_REJECT_FOLDER); + String rejectFolderName = Repository.getNameForNode(this.nodeService, rejectFolder); + rejectMsg = MessageFormat.format(summary.toString(), + new Object[] {Application.getMessage(FacesContext.getCurrentInstance(), rejectAction), + rejectFolderName, rejectStepName}); + } + + summary = new StringBuilder(approveMsg); + if (rejectMsg != null) + { + summary.append(" "); + summary.append(rejectMsg); + } + } + else if ("link-category".equals(actionName)) + { + NodeRef cat = (NodeRef)this.currentActionProperties.get(PROP_CATEGORY); + String name = Repository.getNameForNode(this.nodeService, cat); + summary.append("'").append(name).append("'"); + } + else if ("transform".equals(actionName)) + { + NodeRef space = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + String name = Repository.getNameForNode(this.nodeService, space); + String transformer = (String)this.currentActionProperties.get(PROP_TRANSFORMER); + + // find the label used by looking through the SelectItem list + for (SelectItem item : this.getTransformers()) + { + if (item.getValue().equals(transformer)) + { + transformer = item.getLabel(); + break; + } + } + + // recreate the summary object as it contains parameters + String msg = MessageFormat.format(summary.toString(), new Object[] {name, transformer}); + summary = new StringBuilder(msg); + } + else if ("transform-image".equals(actionName)) + { + NodeRef space = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + String name = Repository.getNameForNode(this.nodeService, space); + String transformer = (String)this.currentActionProperties.get(PROP_IMAGE_TRANSFORMER); + String option = (String)this.currentActionProperties.get(PROP_TRANSFORM_OPTIONS); + + // find the label used by looking through the SelectItem list + for (SelectItem item : this.getImageTransformers()) + { + if (item.getValue().equals(transformer)) + { + transformer = item.getLabel(); + break; + } + } + + // recreate the summary object as it contains parameters + String msg = MessageFormat.format(summary.toString(), new Object[] {name, transformer, option}); + summary = new StringBuilder(msg); + } + else if ("copy".equals(actionName) || "move".equals(actionName) || "check-out".equals(actionName)) + { + NodeRef space = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + String spaceName = Repository.getNameForNode(this.nodeService, space); + summary.append("'").append(spaceName).append("'"); + } + else if ("mail".equals(actionName)) + { + String address = (String)this.currentActionProperties.get(PROP_TO); + summary.append("'").append(address).append("'"); + } + else if ("check-in".equals(actionName)) + { + String comment = (String)this.currentActionProperties.get(PROP_CHECKIN_DESC); + Boolean minorChange = (Boolean)this.currentActionProperties.get(PROP_CHECKIN_MINOR); + String change = null; + if (minorChange != null && minorChange.booleanValue()) + { + change = Application.getMessage(FacesContext.getCurrentInstance(), "minor_change"); + } + else + { + change = Application.getMessage(FacesContext.getCurrentInstance(), "major_change"); + } + + // recreate the summary object as it contains parameters + String msg = MessageFormat.format(summary.toString(), new Object[] {change, comment}); + summary = new StringBuilder(msg); + } + else if ("import".equals(actionName)) + { + NodeRef space = (NodeRef)this.currentActionProperties.get(PROP_DESTINATION); + String spaceName = Repository.getNameForNode(this.nodeService, space); + summary.append("'").append(spaceName).append("'"); + } + else if (SpecialiseTypeActionExecuter.NAME.equals(actionName) == true) + { + String label = null; + String objectType = (String)this.currentActionProperties.get(PROP_OBJECT_TYPE); + for (SelectItem item : getObjectTypes()) + { + if (item.getValue().equals(objectType) == true) + { + label = item.getLabel(); + break; + } + } + + summary.append("'").append(label).append("'"); + } + + summaryResult = summary.toString(); + } + + return summaryResult; + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewSpaceWizard.java b/source/java/org/alfresco/web/bean/wizard/NewSpaceWizard.java new file mode 100644 index 0000000000..cdf122bd68 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewSpaceWizard.java @@ -0,0 +1,1002 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; +import javax.transaction.UserTransaction; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.data.QuickSort; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIListItem; +import org.alfresco.web.ui.common.component.description.UIDescription; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Handler class used by the New Space Wizard + * + * @author gavinc + */ +public class NewSpaceWizard extends AbstractWizardBean +{ + public static final String SPACE_ICON_DEFAULT = "space-icon-default"; + public static final String FORUMS_ICON_DEFAULT = "forums_large"; + + private static Log logger = LogFactory.getLog(NewSpaceWizard.class); + + // TODO: retrieve these from the config service + private static final String WIZARD_TITLE_ID = "new_space_title"; + private static final String WIZARD_DESC_ID = "new_space_desc"; + private static final String STEP1_TITLE_ID = "new_space_step1_title"; + private static final String STEP1_DESCRIPTION_ID = "new_space_step1_desc"; + private static final String STEP2_TITLE_ID = "new_space_step2_title"; + private static final String STEP2_DESCRIPTION_ID = "new_space_step2_desc"; + private static final String STEP3_TITLE_ID = "new_space_step3_title"; + private static final String STEP3_DESCRIPTION_ID = "new_space_step3_desc"; + private static final String FINISH_INSTRUCTION_ID = "new_space_finish_instruction"; + + private static final String ERROR = "error_space"; + private static final String DEFAULT_SPACE_TYPE_ICON = "/images/icons/space.gif"; + + // new space wizard specific properties + private SearchService searchService; + private DictionaryService dictionaryService; + + protected String spaceType; + protected String icon; + protected String createFrom; + protected NodeRef existingSpaceId; + protected String templateSpaceId; + protected String copyPolicy; + protected String name; + protected String description; + protected String templateName; + protected boolean saveAsTemplate; + protected List templates; + protected List folderTypes; + protected List genericIcons; + protected List forumsIcons; + protected List folderTypeDescriptions; + + // the NodeRef of the node created during finish + protected NodeRef createdNode; + + /** + * Deals with the finish button being pressed + * + * @return outcome + */ + public String finish() + { + String outcome = FINISH_OUTCOME; + + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (this.editMode) + { + // update the existing node in the repository + Node currentSpace = this.browseBean.getActionSpace(); + NodeRef nodeRef = currentSpace.getNodeRef(); + + // rename if necessary + fileFolderService.rename(nodeRef, this.name); + + // update the properties + Map properties = this.nodeService.getProperties(nodeRef); + properties.put(ContentModel.PROP_NAME, this.name); + properties.put(ContentModel.PROP_ICON, this.icon); + properties.put(ContentModel.PROP_DESCRIPTION, this.description); + + // apply properties + this.nodeService.setProperties(nodeRef, properties); + } + else + { + String newSpaceId = null; + + if (this.createFrom.equals("scratch")) + { + // create the space (just create a folder for now) + NodeRef parentNodeRef; + String nodeId = getNavigator().getCurrentNodeId(); + if (nodeId == null) + { + parentNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + } + else + { + parentNodeRef = new NodeRef(Repository.getStoreRef(), nodeId); + } + + FileInfo fileInfo = fileFolderService.create( + parentNodeRef, + this.name, + Repository.resolveToQName(this.spaceType)); + NodeRef nodeRef = fileInfo.getNodeRef(); + newSpaceId = nodeRef.getId(); + + if (logger.isDebugEnabled()) + logger.debug("Created folder node with name: " + this.name); + + // apply the uifacets aspect - icon, title and description props + Map uiFacetsProps = new HashMap(5); + uiFacetsProps.put(ContentModel.PROP_ICON, this.icon); + uiFacetsProps.put(ContentModel.PROP_TITLE, this.name); + uiFacetsProps.put(ContentModel.PROP_DESCRIPTION, this.description); + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_UIFACETS, uiFacetsProps); + + if (logger.isDebugEnabled()) + logger.debug("Added uifacets aspect with properties: " + uiFacetsProps); + + // remember the created node + this.createdNode = nodeRef; + } + else if (this.createFrom.equals("existing")) + { + // copy the selected space and update the name, description and icon + NodeRef sourceNode = this.existingSpaceId; + NodeRef parentSpace = new NodeRef(Repository.getStoreRef(), getNavigator().getCurrentNodeId()); + + // copy from existing + NodeRef copiedNode = this.fileFolderService.copy(sourceNode, parentSpace, this.name).getNodeRef(); + + // also need to set the new description and icon properties + this.nodeService.setProperty(copiedNode, ContentModel.PROP_DESCRIPTION, this.description); + this.nodeService.setProperty(copiedNode, ContentModel.PROP_ICON, this.icon); + + newSpaceId = copiedNode.getId(); + + if (logger.isDebugEnabled()) + logger.debug("Copied space with id of " + sourceNode.getId() + " to " + this.name); + + // remember the created node + this.createdNode = copiedNode; + } + else if (this.createFrom.equals("template")) + { + // copy the selected space and update the name, description and icon + NodeRef sourceNode = new NodeRef(Repository.getStoreRef(), this.templateSpaceId); + NodeRef parentSpace = new NodeRef(Repository.getStoreRef(), getNavigator().getCurrentNodeId()); + // copy from the template + NodeRef copiedNode = this.fileFolderService.copy(sourceNode, parentSpace, this.name).getNodeRef(); + // also need to set the new description and icon properties + this.nodeService.setProperty(copiedNode, ContentModel.PROP_DESCRIPTION, this.description); + this.nodeService.setProperty(copiedNode, ContentModel.PROP_ICON, this.icon); + + newSpaceId = copiedNode.getId(); + + if (logger.isDebugEnabled()) + logger.debug("Copied template space with id of " + sourceNode.getId() + " to " + this.name); + + // remember the created node + this.createdNode = copiedNode; + } + + // if the user selected to save the space as a template space copy the new + // space to the templates folder + if (this.saveAsTemplate) + { + // get hold of the Templates node + DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(null); + namespacePrefixResolver.registerNamespace(NamespaceService.APP_MODEL_PREFIX, NamespaceService.APP_MODEL_1_0_URI); + + String xpath = Application.getRootPath(FacesContext.getCurrentInstance()) + "/" + + Application.getGlossaryFolderName(FacesContext.getCurrentInstance()) + "/" + + Application.getSpaceTemplatesFolderName(FacesContext.getCurrentInstance()); + + NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + List templateNodeList = this.searchService.selectNodes( + rootNodeRef, + xpath, null, namespacePrefixResolver, false); + if (templateNodeList.size() == 1) + { + // get the first item in the list as we from test above there is only one! + NodeRef templateNode = templateNodeList.get(0); + NodeRef sourceNode = new NodeRef(Repository.getStoreRef(), newSpaceId); + // copy this to the template location + fileFolderService.copy(sourceNode, templateNode, this.templateName); + } + } + } + + // give subclasses a chance to perform custom processing before committing + performCustomProcessing(context); + + // commit the transaction + tx.commit(); + + // now we know the new details are in the repository, reset the + // client side node representation so the new details are retrieved + String statusMsg = null; + if (this.editMode) + { + this.browseBean.getActionSpace().reset(); + statusMsg = MessageFormat.format(Application.getMessage(context, "status_space_updated"), + new Object[]{this.name}); + } + else + { + // add a message to inform the user that the creation was OK + statusMsg = MessageFormat.format(Application.getMessage(context, "status_space_created"), + new Object[]{this.name}); + } + + // add the status message + Utils.addStatusMessage(FacesMessage.SEVERITY_INFO, statusMsg); + } + catch (FileExistsException e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + // print status message + String statusMsg = MessageFormat.format( + Application.getMessage( + FacesContext.getCurrentInstance(), "error_exists"), + e.getExisting().getName()); + Utils.addErrorMessage(statusMsg); + // no outcome + outcome = null; + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), ERROR), e.getMessage()), e); + outcome = null; + } + + return outcome; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_ID); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + String stepDesc = null; + + switch (this.currentStep) + { + case 1: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_DESCRIPTION_ID); + break; + } + case 2: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_DESCRIPTION_ID); + break; + } + case 3: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP3_DESCRIPTION_ID); + break; + } + case 4: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_DESCRIPTION_ID); + break; + } + default: + { + stepDesc = ""; + } + } + + return stepDesc; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + case 3: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP3_TITLE_ID); + break; + } + case 4: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 4: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + // clear the cached query results + if (this.templates != null) + { + this.templates.clear(); + this.templates = null; + } + + // reset all variables + this.createFrom = "scratch"; + this.spaceType = ContentModel.TYPE_FOLDER.toString(); + this.icon = SPACE_ICON_DEFAULT; + this.copyPolicy = "contents"; + this.existingSpaceId = null; + this.templateSpaceId = null; + this.name = null; + this.description = ""; + this.templateName = null; + this.saveAsTemplate = false; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#populate() + */ + public void populate() + { + // get hold of the current node and populate the appropriate values + Node currentSpace = browseBean.getActionSpace(); + Map props = currentSpace.getProperties(); + + this.name = (String)props.get("name"); + this.description = (String)props.get("description"); + this.icon = (String)props.get("app:icon"); + } + + /** + * @return Returns the summary data for the wizard. + */ + public String getSummary() + { + String summaryCreateType = null; + ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); + + if (this.createFrom.equals("scratch")) + { + summaryCreateType = bundle.getString("scratch"); + } + else if (this.createFrom.equals("existing")) + { + summaryCreateType = bundle.getString("an_existing_space"); + } + else if (this.createFrom.equals("template")) + { + summaryCreateType = bundle.getString("a_template"); + } + +// String summarySaveAsTemplate = this.saveAsTemplate ? bundle.getString("yes") : bundle.getString("no"); +// bundle.getString("save_as_template"), bundle.getString("template_name")}, +// summarySaveAsTemplate, this.templateName + + String spaceTypeLabel = null; + for (UIListItem item : this.getFolderTypes()) + { + if (item.getValue().equals(this.spaceType)) + { + spaceTypeLabel = item.getLabel(); + break; + } + } + + return buildSummary( + new String[] {bundle.getString("space_type"), bundle.getString("name"), + bundle.getString("description"), bundle.getString("creating_from")}, + new String[] {spaceTypeLabel, this.name, this.description, summaryCreateType}); + } + + /** + * @return Returns a list of template spaces currently in the system + */ + public List getTemplateSpaces() + { + if (this.templates == null) + { + this.templates = new ArrayList(); + + FacesContext context = FacesContext.getCurrentInstance(); + String xpath = Application.getRootPath(context) + "/" + Application.getGlossaryFolderName(context) + + "/" + Application.getSpaceTemplatesFolderName(context) + "/*"; + NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + NamespaceService resolver = Repository.getServiceRegistry(context).getNamespaceService(); + List results = this.searchService.selectNodes(rootNodeRef, xpath, null, resolver, false); + + if (results.size() > 0) + { + for (NodeRef assocRef : results) + { + Node childNode = new Node(assocRef); + this.templates.add(new SelectItem(childNode.getId(), childNode.getName())); + } + + // make sure the list is sorted by the label + QuickSort sorter = new QuickSort(this.templates, "label", true, IDataContainer.SORT_CASEINSENSITIVE); + sorter.sort(); + } + + // add an entry (at the start) to instruct the user to select a template + this.templates.add(0, new SelectItem("none", Application.getMessage(FacesContext.getCurrentInstance(), "select_a_template"))); + } + + return this.templates; + } + + /** + * Returns a list of UIListItem objects representing the folder types + * and also constructs the list of descriptions for each type + * + * @return List of UIListItem components + */ + @SuppressWarnings("unchecked") + public List getFolderTypes() + { + if (this.folderTypes == null) + { + FacesContext context = FacesContext.getCurrentInstance(); + this.folderTypes = new ArrayList(2); + this.folderTypeDescriptions = new ArrayList(2); + + // add the well known 'container space' type to start with + UIListItem defaultItem = new UIListItem(); + String defaultLabel = Application.getMessage(context, "container"); + defaultItem.setValue(ContentModel.TYPE_FOLDER.toString()); + defaultItem.setLabel(defaultLabel); + defaultItem.setTooltip(defaultLabel); + defaultItem.getAttributes().put("image", DEFAULT_SPACE_TYPE_ICON); + this.folderTypes.add(defaultItem); + + UIDescription defaultDesc = new UIDescription(); + defaultDesc.setControlValue(ContentModel.TYPE_FOLDER.toString()); + defaultDesc.setText(Application.getMessage(context, "container_desc")); + this.folderTypeDescriptions.add(defaultDesc); + + // add any configured content sub-types to the list + ConfigService svc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + Config wizardCfg = svc.getConfig("Custom Folder Types"); + if (wizardCfg != null) + { + ConfigElement typesCfg = wizardCfg.getConfigElement("folder-types"); + if (typesCfg != null) + { + for (ConfigElement child : typesCfg.getChildren()) + { + QName idQName = Repository.resolveToQName(child.getAttribute("name")); + TypeDefinition typeDef = this.dictionaryService.getType(idQName); + + if (typeDef != null && + this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_FOLDER)) + { + // look for a client localized string + String label = null; + String msgId = child.getAttribute("displayLabelId"); + if (msgId != null) + { + label = Application.getMessage(context, msgId); + } + + // if there wasn't an externalized string look for one in the config + if (label == null) + { + label = child.getAttribute("displayLabel"); + } + + // if there wasn't a client based label try and get it from the dictionary + if (label == null) + { + label = typeDef.getTitle(); + } + + // finally use the localname if we still haven't found a label + if (label == null) + { + label = idQName.getLocalName(); + } + + // resolve a description string for the type + String description = null; + msgId = child.getAttribute("descriptionMsgId"); + if (msgId != null) + { + description = Application.getMessage(context, msgId); + } + + if (description == null) + { + description = child.getAttribute("description"); + } + + // if we don't have a local description just use the label + if (description == null) + { + description = label; + } + + // extract the icon to use from the config + String icon = child.getAttribute("icon"); + if (icon == null || icon.length() == 0) + { + icon = DEFAULT_SPACE_TYPE_ICON; + } + + UIListItem item = new UIListItem(); + item.getAttributes().put("value", idQName.toString()); + item.getAttributes().put("label", label); + item.getAttributes().put("tooltip", label); + item.getAttributes().put("image", icon); + this.folderTypes.add(item); + + UIDescription desc = new UIDescription(); + desc.setControlValue(idQName.toString()); + desc.setText(description); + this.folderTypeDescriptions.add(desc); + } + } + } + else + { + logger.warn("Could not find 'folder-types' configuration element"); + } + } + else + { + logger.warn("Could not find 'Custom Folder Types' configuration section"); + } + + } + + return this.folderTypes; + } + + /** + * Returns a list of UIDescription objects for the folder types + * + * @return A list of UIDescription objects + */ + public List getFolderTypeDescriptions() + { + if (this.folderTypeDescriptions == null) + { + // call the getFolderType method to construct the list + getFolderTypes(); + } + + return this.folderTypeDescriptions; + } + + /** + * Returns a list of icons to allow the user to select from. + * The list can change according to the type of space being created. + * + * @return A list of icons + */ + @SuppressWarnings("unchecked") + public List getIcons() + { + // TODO: Drive the list of icons to show for each space type from the config + // this will then remove the dependency on forums from this generic + // class + + List icons = null; + + if (this.spaceType.equals(ForumModel.TYPE_FORUMS.toString())) + { + // return the various forum icons + if (this.forumsIcons == null) + { + this.forumsIcons = new ArrayList(2); + + // change default icon to be forums + this.icon = FORUMS_ICON_DEFAULT; + + UIListItem item = new UIListItem(); + item.setValue(FORUMS_ICON_DEFAULT); + item.getAttributes().put("image", "/images/icons/forums_large.gif"); + this.forumsIcons.add(item); + } + + icons = this.forumsIcons; + } + else + { + // return the generic space icons + if (this.genericIcons == null) + { + this.genericIcons = new ArrayList(6); + + // change default icon + this.icon = SPACE_ICON_DEFAULT; + + UIListItem item = new UIListItem(); + item.setValue("space-icon-default"); + item.getAttributes().put("image", "/images/icons/space-icon-default.gif"); + this.genericIcons.add(item); + + item = new UIListItem(); + item.setValue("space-icon-star"); + item.getAttributes().put("image", "/images/icons/space-icon-star.gif"); + this.genericIcons.add(item); + + item = new UIListItem(); + item.setValue("space-icon-doc"); + item.getAttributes().put("image", "/images/icons/space-icon-doc.gif"); + this.genericIcons.add(item); + + item = new UIListItem(); + item.setValue("space-icon-pen"); + item.getAttributes().put("image", "/images/icons/space-icon-pen.gif"); + this.genericIcons.add(item); + + item = new UIListItem(); + item.setValue("space-icon-cd"); + item.getAttributes().put("image", "/images/icons/space-icon-cd.gif"); + this.genericIcons.add(item); + + item = new UIListItem(); + item.setValue("space-icon-image"); + item.getAttributes().put("image", "/images/icons/space-icon-image.gif"); + this.genericIcons.add(item); + } + + icons = this.genericIcons; + } + + return icons; + } + + /** + * @return Returns the searchService. + */ + public SearchService getSearchService() + { + return searchService; + } + + /** + * @param searchService The searchService to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Sets the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @return Returns the copyPolicy. + */ + public String getCopyPolicy() + { + return copyPolicy; + } + + /** + * @param copyPolicy The copyPolicy to set. + */ + public void setCopyPolicy(String copyPolicy) + { + this.copyPolicy = copyPolicy; + } + + /** + * @return Returns the createFrom. + */ + public String getCreateFrom() + { + return createFrom; + } + + /** + * @param createFrom The createFrom to set. + */ + public void setCreateFrom(String createFrom) + { + this.createFrom = createFrom; + } + + /** + * @return Returns the description. + */ + public String getDescription() + { + return description; + } + + /** + * @param description The description to set. + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * @return Returns the existingSpaceId. + */ + public NodeRef getExistingSpaceId() + { + return existingSpaceId; + } + + /** + * @param existingSpaceId The existingSpaceId to set. + */ + public void setExistingSpaceId(NodeRef existingSpaceId) + { + this.existingSpaceId = existingSpaceId; + } + + /** + * @return Returns the icon. + */ + public String getIcon() + { + return icon; + } + + /** + * @param icon The icon to set. + */ + public void setIcon(String icon) + { + this.icon = icon; + } + + /** + * @return Returns the name. + */ + public String getName() + { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @return Returns the saveAsTemplate. + */ + public boolean isSaveAsTemplate() + { + return saveAsTemplate; + } + + /** + * @param saveAsTemplate The saveAsTemplate to set. + */ + public void setSaveAsTemplate(boolean saveAsTemplate) + { + this.saveAsTemplate = saveAsTemplate; + } + + /** + * @return Returns the spaceType. + */ + public String getSpaceType() + { + return spaceType; + } + + /** + * @param spaceType The spaceType to set. + */ + public void setSpaceType(String spaceType) + { + this.spaceType = spaceType; + } + + /** + * @return Returns the templateName. + */ + public String getTemplateName() + { + return templateName; + } + + /** + * @param templateName The templateName to set. + */ + public void setTemplateName(String templateName) + { + this.templateName = templateName; + } + + /** + * @return Returns the templateSpaceId. + */ + public String getTemplateSpaceId() + { + return templateSpaceId; + } + + /** + * @param templateSpaceId The templateSpaceId to set. + */ + public void setTemplateSpaceId(String templateSpaceId) + { + this.templateSpaceId = templateSpaceId; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch(step) + { + case 1: + { + outcome = "create-from"; + break; + } + case 2: + { + if (createFrom.equalsIgnoreCase("scratch")) + { + outcome = "from-scratch"; + } + else if (createFrom.equalsIgnoreCase("existing")) + { + outcome = "from-existing"; + } + else if (createFrom.equalsIgnoreCase("template")) + { + outcome = "from-template"; + } + + break; + } + case 3: + { + outcome = "details"; + break; + } + case 4: + { + outcome = "summary"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } + + /** + * Performs any processing sub classes may wish to do before commit is called + * + * @param context Faces context + */ + protected void performCustomProcessing(FacesContext context) + { + // used by subclasses if necessary + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewTopicWizard.java b/source/java/org/alfresco/web/bean/wizard/NewTopicWizard.java new file mode 100644 index 0000000000..562f8db323 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewTopicWizard.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.model.SelectItem; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.component.UIListItem; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Wizard bean used for creating and editing topic spaces + * + * @author gavinc + */ +public class NewTopicWizard extends NewSpaceWizard +{ + public static final String TOPIC_ICON_DEFAULT = "topic_large"; + + private static final Log logger = LogFactory.getLog(NewTopicWizard.class); + + protected String message; + protected String topicType; + protected List topicIcons; + protected List topicTypes; + + protected ContentService contentService; + + /** + * Returns a list of topic types for the user to select from + * + * @return The topic types + */ + public List getTopicTypes() + { + if (this.topicTypes == null) + { + this.topicTypes = new ArrayList(3); + + // TODO: change this to be based on categories + this.topicTypes.add(new SelectItem("1", "Announcement")); + this.topicTypes.add(new SelectItem("0", "Normal")); + this.topicTypes.add(new SelectItem("2", "Sticky")); + } + + return this.topicTypes; + } + + /** + * Returns the type of the topic + * + * @return The type of topic + */ + public String getTopicType() + { + return this.topicType; + } + + /** + * Sets the type of the topic + * + * @param topicType The type of the topic + */ + public void setTopicType(String topicType) + { + this.topicType = topicType; + } + + /** + * Returns the message entered by the user for the first post + * + * @return The message for the first post + */ + public String getMessage() + { + return this.message; + } + + /** + * Sets the message + * + * @param message The message + */ + public void setMessage(String message) + { + this.message = message; + } + + /** + * @param contentService The contentService to set. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#init() + */ + public void init() + { + super.init(); + + this.spaceType = ForumModel.TYPE_TOPIC.toString(); + this.icon = TOPIC_ICON_DEFAULT; + this.topicType = "0"; + this.message = null; + } + + /** + * Returns a list of icons to allow the user to select from. + * + * @return A list of icons + */ + @SuppressWarnings("unchecked") + public List getIcons() + { + // return the various forum icons + if (this.topicIcons == null) + { + this.topicIcons = new ArrayList(2); + + UIListItem item = new UIListItem(); + item.setValue(TOPIC_ICON_DEFAULT); + item.getAttributes().put("image", "/images/icons/topic_large.gif"); + this.topicIcons.add(item); + } + + return this.topicIcons; + } + + /** + * @see org.alfresco.web.bean.wizard.NewSpaceWizard#performCustomProcessing(javax.faces.context.FacesContext) + */ + @Override + protected void performCustomProcessing(FacesContext context) + { + if (this.editMode == false) + { + // ************************* + // TODO: Add or update the ForumModel.PROP_TYPE property depending on the editMode + // ************************* + + // get the node ref of the node that will contain the content + NodeRef containerNodeRef = this.createdNode; + + // create a unique file name for the message content + String fileName = GUID.generate() + ".txt"; + + // create properties for content type + Map contentProps = new HashMap(5, 1.0f); + contentProps.put(ContentModel.PROP_NAME, fileName); + + // create the node to represent the content + String assocName = QName.createValidLocalName(fileName); + ChildAssociationRef assocRef = this.nodeService.createNode( + containerNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, assocName), + Repository.resolveToQName(ForumModel.TYPE_POST.toString()), + contentProps); + + NodeRef postNodeRef = assocRef.getChildRef(); + + if (logger.isDebugEnabled()) + logger.debug("Created post node with filename: " + fileName); + + // apply the titled aspect - title and description + Map titledProps = new HashMap(3, 1.0f); + titledProps.put(ContentModel.PROP_TITLE, fileName); + this.nodeService.addAspect(postNodeRef, ContentModel.ASPECT_TITLED, titledProps); + + if (logger.isDebugEnabled()) + logger.debug("Added titled aspect with properties: " + titledProps); + + Map editProps = new HashMap(1, 1.0f); + editProps.put(ContentModel.PROP_EDITINLINE, true); + this.nodeService.addAspect(postNodeRef, ContentModel.ASPECT_INLINEEDITABLE, editProps); + + if (logger.isDebugEnabled()) + logger.debug("Added inlineeditable aspect with properties: " + editProps); + + // get a writer for the content and put the file + ContentWriter writer = contentService.getWriter(postNodeRef, ContentModel.PROP_CONTENT, true); + // set the mimetype and encoding + writer.setMimetype(Repository.getMimeTypeForFileName(context, fileName)); + writer.setEncoding("UTF-8"); + writer.putContent(this.message); + } + } +} diff --git a/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java b/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java new file mode 100644 index 0000000000..7f5043b5d4 --- /dev/null +++ b/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java @@ -0,0 +1,981 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean.wizard; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.application.FacesMessage; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.faces.validator.ValidatorException; +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigService; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.OwnableService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.ContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.users.UsersBean; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.apache.log4j.Logger; + +/** + * @author Kevin Roast + */ +public class NewUserWizard extends AbstractWizardBean +{ + private static Logger logger = Logger.getLogger(NewUserWizard.class); + + private static final String WIZARD_TITLE_NEW_ID = "new_user_title"; + private static final String WIZARD_DESC_NEW_ID = "new_user_desc"; + private static final String WIZARD_TITLE_EDIT_ID = "new_user_title_edit"; + private static final String WIZARD_DESC_EDIT_ID = "new_user_desc_edit"; + private static final String STEP1_TITLE_ID = "new_user_step1_title"; + private static final String STEP1_DESCRIPTION_ID = "new_user_step1_desc"; + private static final String STEP2_TITLE_ID = "new_user_step2_title"; + private static final String STEP2_DESCRIPTION_ID = "new_user_step2_desc"; + private static final String FINISH_INSTRUCTION_ID = "new_user_finish_instruction"; + private static final String ERROR = "error_person"; + + /** form variables */ + private String firstName = null; + private String lastName = null; + private String userName = null; + private String password = null; + private String confirm = null; + private String email = null; + private String companyId = null; + private String homeSpaceName = ""; + private NodeRef homeSpaceLocation = null; + + /** AuthenticationService bean reference */ + private AuthenticationService authenticationService; + + /** NamespaceService bean reference */ + private NamespaceService namespaceService; + + /** PermissionService bean reference */ + private PermissionService permissionService; + + /** PersonService bean reference */ + private PersonService personService; + + /** OwnableService bean reference */ + private OwnableService ownableService; + + /** ConfigService bean reference */ + private ConfigService configService; + + /** action context */ + private Node person = null; + + /** ref to system people folder */ + private NodeRef peopleRef = null; + + /** ref to the company home space folder */ + private NodeRef companyHomeSpaceRef = null; + + + /** + * @param authenticationService The AuthenticationService to set. + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + /** + * @param namespaceService The namespaceService to set. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param permissionService The PermissionService to set. + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param personService The person service. + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * @param ownableService The ownableService to set. + */ + public void setOwnableService(OwnableService ownableService) + { + this.ownableService = ownableService; + } + + /** + * @param configService The ConfigService to set. + */ + public void setConfigService(ConfigService configService) + { + this.configService = configService; + } + + /** + * Initialises the wizard + */ + public void init() + { + super.init(); + + // reset all variables + this.firstName = ""; + this.lastName = ""; + this.userName = ""; + this.password = ""; + this.confirm = ""; + this.email = ""; + this.companyId = ""; + this.homeSpaceName = ""; + this.homeSpaceLocation = getCompanyHomeSpace(); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#populate() + */ + public void populate() + { + // set values for edit mode + Map props = getPerson().getProperties(); + + this.firstName = (String) props.get("firstName"); + this.lastName = (String) props.get("lastName"); + this.userName = (String) props.get("userName"); + this.password = ""; + this.confirm = ""; + this.email = (String) props.get("email"); + this.companyId = (String) props.get("organizationId"); + + // calculate home space name and parent space Id from homeFolderId + this.homeSpaceLocation = null; // default to Company root space + this.homeSpaceName = ""; // default to none set below root + NodeRef homeFolderRef = (NodeRef) props.get("homeFolder"); + if (this.nodeService.exists(homeFolderRef) == true) + { + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(homeFolderRef); + NodeRef parentRef = childAssocRef.getParentRef(); + if (this.nodeService.getRootNode(Repository.getStoreRef()).equals(parentRef) == false) + { + this.homeSpaceLocation = parentRef; + this.homeSpaceName = Repository.getNameForNode(nodeService, homeFolderRef); + } + else + { + this.homeSpaceLocation = homeFolderRef; + } + } + + if (logger.isDebugEnabled()) + logger.debug("Edit user home space location: " + homeSpaceLocation + " home space name: " + homeSpaceName); + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardDescription() + */ + public String getWizardDescription() + { + if (this.editMode) + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_EDIT_ID); + } + else + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_DESC_NEW_ID); + } + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getWizardTitle() + */ + public String getWizardTitle() + { + if (this.editMode) + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_EDIT_ID); + } + else + { + return Application.getMessage(FacesContext.getCurrentInstance(), WIZARD_TITLE_NEW_ID); + } + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepTitle() + */ + public String getStepTitle() + { + String stepTitle = null; + + switch (this.currentStep) + { + case 1: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_TITLE_ID); + break; + } + case 2: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_TITLE_ID); + break; + } + case 3: + { + stepTitle = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_TITLE_ID); + break; + } + default: + { + stepTitle = ""; + } + } + + return stepTitle; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepDescription() + */ + public String getStepDescription() + { + String stepDesc = null; + + switch (this.currentStep) + { + case 1: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP1_DESCRIPTION_ID); + break; + } + case 2: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), STEP2_DESCRIPTION_ID); + break; + } + case 3: + { + stepDesc = Application.getMessage(FacesContext.getCurrentInstance(), SUMMARY_DESCRIPTION_ID); + break; + } + default: + { + stepDesc = ""; + } + } + + return stepDesc; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#getStepInstructions() + */ + public String getStepInstructions() + { + String stepInstruction = null; + + switch (this.currentStep) + { + case 3: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), FINISH_INSTRUCTION_ID); + break; + } + default: + { + stepInstruction = Application.getMessage(FacesContext.getCurrentInstance(), DEFAULT_INSTRUCTION_ID); + } + } + + return stepInstruction; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#determineOutcomeForStep(int) + */ + protected String determineOutcomeForStep(int step) + { + String outcome = null; + + switch (step) + { + case 1: + { + outcome = "person-properties"; + break; + } + case 2: + { + outcome = "user-properties"; + break; + } + case 3: + { + outcome = "summary"; + break; + } + default: + { + outcome = CANCEL_OUTCOME; + } + } + + return outcome; + } + + /** + * @see org.alfresco.web.bean.wizard.AbstractWizardBean#finish() + */ + public String finish() + { + String outcome = FINISH_OUTCOME; + + // TODO: implement create new Person object from specified details + UserTransaction tx = null; + + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + if (this.editMode) + { + // update the existing node in the repository + NodeRef nodeRef = getPerson().getNodeRef(); + + Map props = this.nodeService.getProperties(nodeRef); + props.put(ContentModel.PROP_USERNAME, this.userName); + props.put(ContentModel.PROP_FIRSTNAME, this.firstName); + props.put(ContentModel.PROP_LASTNAME, this.lastName); + + // calculate whether we need to move the old home space or create new + NodeRef newHomeFolderRef; + NodeRef oldHomeFolderRef = (NodeRef)this.nodeService.getProperty(nodeRef, ContentModel.PROP_HOMEFOLDER); + boolean moveHomeSpace = false; + boolean renameHomeSpace = false; + if (oldHomeFolderRef != null && this.nodeService.exists(oldHomeFolderRef) == true) + { + // the original home folder ref exists so may need moving if it has been changed + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(oldHomeFolderRef); + NodeRef currentHomeSpaceLocation = childAssocRef.getParentRef(); + if (this.homeSpaceName.length() != 0) + { + if (currentHomeSpaceLocation.equals(this.homeSpaceLocation) == false && + oldHomeFolderRef.equals(this.homeSpaceLocation) == false && + currentHomeSpaceLocation.equals(getCompanyHomeSpace()) == false) + { + moveHomeSpace = true; + } + + String oldHomeSpaceName = Repository.getNameForNode(nodeService, oldHomeFolderRef); + if (oldHomeSpaceName.equals(this.homeSpaceName) == false && + oldHomeFolderRef.equals(this.homeSpaceLocation) == false) + { + renameHomeSpace = true; + } + } + } + + if (logger.isDebugEnabled()) + logger.debug("Moving space: " + moveHomeSpace + " and renaming space: " + renameHomeSpace); + + if (moveHomeSpace == false && renameHomeSpace == false) + { + if (this.homeSpaceLocation != null && this.homeSpaceName.length() != 0) + { + newHomeFolderRef = createHomeSpace(this.homeSpaceLocation.getId(), this.homeSpaceName, false); + } + else if (this.homeSpaceLocation != null) + { + // location selected but no home space name entered, + // so the home ref should be set to the newly selected space + newHomeFolderRef = this.homeSpaceLocation; + + // set the permissions for this space so the user can access it + + } + else + { + // nothing selected - use Company Home by default + newHomeFolderRef = getCompanyHomeSpace(); + } + } + else + { + // either move, rename or both required + if (moveHomeSpace == true) + { + this.nodeService.moveNode( + oldHomeFolderRef, + this.homeSpaceLocation, + ContentModel.ASSOC_CONTAINS, + this.nodeService.getPrimaryParent(oldHomeFolderRef).getQName()); + } + newHomeFolderRef = oldHomeFolderRef; // ref ID doesn't change + + if (renameHomeSpace == true) + { + // change HomeSpace node name + this.nodeService.setProperty(newHomeFolderRef, ContentModel.PROP_NAME, this.homeSpaceName); + } + } + + props.put(ContentModel.PROP_HOMEFOLDER, newHomeFolderRef); + props.put(ContentModel.PROP_EMAIL, this.email); + props.put(ContentModel.PROP_ORGID, this.companyId); + this.nodeService.setProperties(nodeRef, props); + + // TODO: RESET HomeSpace Ref found in top-level navigation bar! + // NOTE: not need cos only admin can do this? + } + else + { + if (this.password.equals(this.confirm)) + { + if (!this.personService.getUserNamesAreCaseSensitive()) + { + this.userName = this.userName.toLowerCase(); + } + + // create properties for Person type from submitted Form data + Map props = new HashMap(7, 1.0f); + props.put(ContentModel.PROP_USERNAME, this.userName); + props.put(ContentModel.PROP_FIRSTNAME, this.firstName); + props.put(ContentModel.PROP_LASTNAME, this.lastName); + NodeRef homeSpaceNodeRef; + if (this.homeSpaceLocation != null && this.homeSpaceName.length() != 0) + { + // create new + homeSpaceNodeRef = createHomeSpace(this.homeSpaceLocation.getId(), this.homeSpaceName, true); + } + else if (this.homeSpaceLocation != null) + { + // set to existing + homeSpaceNodeRef = homeSpaceLocation; + setupHomeSpacePermissions(homeSpaceNodeRef); + } + else + { + // default to Company Home + homeSpaceNodeRef = getCompanyHomeSpace(); + } + props.put(ContentModel.PROP_HOMEFOLDER, homeSpaceNodeRef); + props.put(ContentModel.PROP_EMAIL, this.email); + props.put(ContentModel.PROP_ORGID, this.companyId); + + // create the node to represent the Person + String assocName = QName.createValidLocalName(this.userName); + NodeRef newPerson = this.personService.createPerson(props); + + // ensure the user can access their own Person object + this.permissionService.setPermission(newPerson, this.userName, permissionService.getAllPermission(), true); + + if (logger.isDebugEnabled()) logger.debug("Created Person node for username: " + this.userName); + + // create the ACEGI Authentication instance for the new user + this.authenticationService.createAuthentication(this.userName, this.password.toCharArray()); + + if (logger.isDebugEnabled()) logger.debug("Created User Authentication instance for username: " + this.userName); + } + else + { + outcome = null; + Utils.addErrorMessage(Application.getMessage(context, UsersBean.ERROR_PASSWORD_MATCH)); + } + } + + // commit the transaction + tx.commit(); + + // reset the richlist component so it rebinds to the users list + invalidateUserList(); + } + catch (Exception e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), ERROR), e + .getMessage()), e); + outcome = null; + } + + return outcome; + } + + /** + * @return Returns the summary data for the wizard. + */ + public String getSummary() + { + String homeSpaceLabel = this.homeSpaceName; + if (this.homeSpaceName.length() == 0 && this.homeSpaceLocation != null) + { + homeSpaceLabel = Repository.getNameForNode(this.nodeService, this.homeSpaceLocation); + } + + ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); + + return buildSummary(new String[] { bundle.getString("name"), bundle.getString("username"), + bundle.getString("password"), bundle.getString("homespace") }, new String[] { + this.firstName + " " + this.lastName, this.userName, "********", homeSpaceLabel }); + } + + /** + * Init the users screen + */ + public void setupUsers(ActionEvent event) + { + invalidateUserList(); + } + + /** + * Action listener called when the wizard is being launched for editing an + * existing node. + */ + public void startWizardForEdit(ActionEvent event) + { + UIActionLink link = (UIActionLink) event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // remember the Person node + setPerson(node); + + // set the wizard in edit mode + this.editMode = true; + + // populate the wizard's default values with the current value + // from the node being edited + init(); + populate(); + + // clear the UI state in preparation for finishing the action + // and returning to the main page + invalidateUserList(); + + if (logger.isDebugEnabled()) logger.debug("Started wizard : " + getWizardTitle() + " for editing"); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), + Repository.ERROR_NODEREF), new Object[] { id })); + } + } + else + { + setPerson(null); + } + } + + /** + * @return Returns the companyId. + */ + public String getCompanyId() + { + return this.companyId; + } + + /** + * @param companyId + * The companyId to set. + */ + public void setCompanyId(String companyId) + { + this.companyId = companyId; + } + + /** + * @return Returns the email. + */ + public String getEmail() + { + return this.email; + } + + /** + * @param email + * The email to set. + */ + public void setEmail(String email) + { + this.email = email; + } + + /** + * @return Returns the firstName. + */ + public String getFirstName() + { + return this.firstName; + } + + /** + * @param firstName The firstName to set. + */ + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + /** + * @return Returns the homeSpaceLocation. + */ + public NodeRef getHomeSpaceLocation() + { + return this.homeSpaceLocation; + } + + /** + * @param homeSpaceLocation The homeSpaceLocation to set. + */ + public void setHomeSpaceLocation(NodeRef homeSpaceLocation) + { + this.homeSpaceLocation = homeSpaceLocation; + } + + /** + * @return Returns the homeSpaceName. + */ + public String getHomeSpaceName() + { + return this.homeSpaceName; + } + + /** + * @param homeSpaceName The homeSpaceName to set. + */ + public void setHomeSpaceName(String homeSpaceName) + { + this.homeSpaceName = homeSpaceName; + } + + /** + * @return Returns the lastName. + */ + public String getLastName() + { + return this.lastName; + } + + /** + * @param lastName The lastName to set. + */ + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + /** + * @return Returns the userName. + */ + public String getUserName() + { + return this.userName; + } + + /** + * @param userName The userName to set. + */ + public void setUserName(String userName) + { + this.userName = userName; + } + + /** + * @return Returns the password. + */ + public String getPassword() + { + return this.password; + } + + /** + * @param password The password to set. + */ + public void setPassword(String password) + { + this.password = password; + } + + /** + * @return Returns the confirm password. + */ + public String getConfirm() + { + return this.confirm; + } + + /** + * @param confirm The confirm password to set. + */ + public void setConfirm(String confirm) + { + this.confirm = confirm; + } + + /** + * @return Returns the person context. + */ + public Node getPerson() + { + return this.person; + } + + /** + * @param person The person context to set. + */ + public void setPerson(Node person) + { + this.person = person; + } + + public boolean getEditMode() + { + return this.editMode; + } + + + // ------------------------------------------------------------------------------ + // Validator methods + + /** + * Validate password field data is acceptable + */ + public void validatePassword(FacesContext context, UIComponent component, Object value) throws ValidatorException + { + String pass = (String) value; + if (pass.length() < 5 || pass.length() > 12) + { + String err = "Password must be between 5 and 12 characters in length."; + throw new ValidatorException(new FacesMessage(err)); + } + + for (int i = 0; i < pass.length(); i++) + { + if (Character.isLetterOrDigit(pass.charAt(i)) == false) + { + String err = "Password can only contain characters or digits."; + throw new ValidatorException(new FacesMessage(err)); + } + } + } + + /** + * Validate Username field data is acceptable + */ + public void validateUsername(FacesContext context, UIComponent component, Object value) throws ValidatorException + { + String pass = (String) value; + if (pass.length() < 5 || pass.length() > 12) + { + String err = "Username must be between 5 and 12 characters in length."; + throw new ValidatorException(new FacesMessage(err)); + } + + for (int i = 0; i < pass.length(); i++) + { + if (Character.isLetterOrDigit(pass.charAt(i)) == false) + { + String err = "Username can only contain characters or digits."; + throw new ValidatorException(new FacesMessage(err)); + } + } + } + + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Helper to return the company home space + * + * @return company home space NodeRef + */ + private NodeRef getCompanyHomeSpace() + { + if (this.companyHomeSpaceRef == null) + { + String companyXPath = Application.getRootPath(FacesContext.getCurrentInstance()); + + NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); + List nodes = this.searchService.selectNodes(rootNodeRef, companyXPath, null, this.namespaceService, + false); + + if (nodes.size() == 0) + { + throw new IllegalStateException("Unable to find company home space path: " + companyXPath); + } + + this.companyHomeSpaceRef = nodes.get(0); + } + + return this.companyHomeSpaceRef; + } + + /** + * Create the specified home space if it does not exist, and return the ID + * + * @param locationId + * Parent location + * @param spaceName + * Home space to create, can be null to simply return the parent + * @param error + * True to throw an error if the space already exists, else + * ignore and return + * + * @return ID of the home space + */ + private NodeRef createHomeSpace(String locationId, String spaceName, boolean error) + { + String homeSpaceId = locationId; + NodeRef homeSpaceNodeRef = null; + if (spaceName != null && spaceName.length() != 0) + { + NodeRef parentRef = new NodeRef(Repository.getStoreRef(), locationId); + + // check for existance of home space with same name - return immediately + // if it exists or throw an exception an give user chance to enter another name + // TODO: this might be better replaced with an XPath query! + List children = this.nodeService.getChildAssocs(parentRef); + for (ChildAssociationRef ref : children) + { + String childNodeName = (String) this.nodeService.getProperty(ref.getChildRef(), ContentModel.PROP_NAME); + if (spaceName.equals(childNodeName)) + { + if (error) + { + throw new AlfrescoRuntimeException("A Home Space with the same name already exists."); + } + else + { + return ref.getChildRef(); + } + } + } + + // space does not exist already, create a new Space under it with + // the specified name + String qname = QName.createValidLocalName(spaceName); + ChildAssociationRef assocRef = this.nodeService.createNode(parentRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, qname), ContentModel.TYPE_FOLDER); + + NodeRef nodeRef = assocRef.getChildRef(); + + // set the name property on the node + this.nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, spaceName); + + if (logger.isDebugEnabled()) logger.debug("Created Home Space for with name: " + spaceName); + + // apply the uifacets aspect - icon, title and description props + Map uiFacetsProps = new HashMap(3); + uiFacetsProps.put(ContentModel.PROP_ICON, NewSpaceWizard.SPACE_ICON_DEFAULT); + uiFacetsProps.put(ContentModel.PROP_TITLE, spaceName); + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_UIFACETS, uiFacetsProps); + + setupHomeSpacePermissions(nodeRef); + + // return the ID of the created space + homeSpaceNodeRef = nodeRef; + homeSpaceId = nodeRef.getId(); + } + + return homeSpaceNodeRef; + } + + /** + * Setup the default permissions for this and other users on the Home Space + * + * @param homeSpaceRef Home Space reference + */ + private void setupHomeSpacePermissions(NodeRef homeSpaceRef) + { + // Admin Authority has full permissions by default (automatic - set in the permission config) + // give full permissions to the new user + this.permissionService.setPermission(homeSpaceRef, this.userName, permissionService.getAllPermission(), true); + + // by default other users will only have GUEST access to the space contents + // or whatever is configured as the default in the web-client-xml config + String permission = getDefaultPermission(); + if (permission != null && permission.length() != 0) + { + this.permissionService.setPermission(homeSpaceRef, permissionService.getAllAuthorities(), permission, true); + } + + // the new user is the OWNER of their own space and always has full permissions + this.ownableService.setOwner(homeSpaceRef, this.userName); + this.permissionService.setPermission(homeSpaceRef, permissionService.getOwnerAuthority(), permissionService.getAllPermission(), true); + + // now detach (if we did this first we could not set any permissions!) + this.permissionService.setInheritParentPermissions(homeSpaceRef, false); + } + + /** + * @return default permission string to set for other users for a new Home Space + */ + private String getDefaultPermission() + { + ClientConfigElement config = (ClientConfigElement)this.configService.getGlobalConfig().getConfigElement( + ClientConfigElement.CONFIG_ELEMENT_ID); + + return config.getHomeSpacePermission(); + } + + private void invalidateUserList() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } +} diff --git a/source/java/org/alfresco/web/config/AspectEvaluator.java b/source/java/org/alfresco/web/config/AspectEvaluator.java new file mode 100644 index 0000000000..2c52f24bf7 --- /dev/null +++ b/source/java/org/alfresco/web/config/AspectEvaluator.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.Set; + +import org.alfresco.config.evaluator.Evaluator; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; + +/** + * Evaluator that determines whether a given object has a particular aspect applied + * + * @author gavinc + */ +public class AspectEvaluator implements Evaluator +{ + /** + * Determines whether the given aspect is applied to the given object + * + * @see org.alfresco.config.evaluator.Evaluator#applies(java.lang.Object, java.lang.String) + */ + public boolean applies(Object obj, String condition) + { + boolean result = false; + + if (obj instanceof Node) + { + Set aspects = ((Node)obj).getAspects(); + if (aspects != null) + { + QName spaceQName = Repository.resolveToQName(condition); + result = aspects.contains(spaceQName); + } + } + + return result; + } + +} diff --git a/source/java/org/alfresco/web/config/ClientConfigElement.java b/source/java/org/alfresco/web/config/ClientConfigElement.java new file mode 100644 index 0000000000..0b19d9661c --- /dev/null +++ b/source/java/org/alfresco/web/config/ClientConfigElement.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.element.ConfigElementAdapter; + +/** + * Custom config element that represents config values for the client + * + * @author Kevin Roast + */ +public class ClientConfigElement extends ConfigElementAdapter +{ + public static final String CONFIG_ELEMENT_ID = "client"; + public static final String VIEW_DETAILS = "details"; + public static final String VIEW_ICONS = "icons"; + public static final String VIEW_LIST = "list"; + public static final String VIEW_BUBBLE = "bubble"; + + private static final String SEPARATOR = ":"; + + // defaults for any config values not supplied + private int defaultPageSize = 10; + private String defaultView = "details"; + private String defaultSortColumn = "name"; + private String defaultSortOrder = "ascending"; + + // list to store all the configured views + private List views = new ArrayList(4); + + // map to store all the default views + private Map defaultViews = new HashMap(4); + + // map to store all default pages sizes for configured client views + private Map pagesSizes = new HashMap(10); + + // map to store default sort columns for configured views + private Map sortColumns = new HashMap(4); + + // list of pages that have been configured to have ascending sorts + private List descendingSorts = new ArrayList(1); + + private int recentSpacesItems = 6; + private int searchMinimum = 3; + private String helpUrl = null; + private String editLinkType = null; + private Map localeMap = new HashMap(); + private List languages = new ArrayList(8); + private String homeSpacePermission = null; + private List contentTypes = null; + private List customProps = null; + + /** + * Default Constructor + */ + public ClientConfigElement() + { + super(CONFIG_ELEMENT_ID); + + // add the default page sizes to the map + this.pagesSizes.put(VIEW_DETAILS, defaultPageSize); + this.pagesSizes.put(VIEW_LIST, defaultPageSize); + this.pagesSizes.put(VIEW_ICONS, 9); + this.pagesSizes.put(VIEW_BUBBLE, 5); + } + + /** + * Constructor + * + * @param name Name of the element this config element represents + */ + public ClientConfigElement(String name) + { + super(name); + } + + /** + * @see org.alfresco.config.element.ConfigElementAdapter#combine(org.alfresco.config.ConfigElement) + */ + public ConfigElement combine(ConfigElement configElement) + { + return null; + } + + /** + * Adds a configured view + * + * @param renderer The implementation class of the view (the renderer) + */ + public void addView(String renderer) + { + this.views.add(renderer); + } + + /** + * Returns a map of configured views for the client + * + * @return List of the implementation classes for the configured views + */ + public List getViews() + { + return this.views; + } + + /** + * Adds a default view setting + * + * @param page The page to set the default view for + * @param view The view name that will be the default + */ + public void addDefaultView(String page, String view) + { + this.defaultViews.put(page, view); + } + + /** + * Returns the default view for the given page + * + * @param page The page to get the default view for + * @return The defualt view, if there isn't a configured default for the + * given page 'details' will be returned + */ + public String getDefaultView(String page) + { + String view = this.defaultViews.get(page); + + if (view == null) + { + view = this.defaultView; + } + + return view; + } + + /** + * Adds a configured page size to the internal store + * + * @param page The name of the page i.e. browse, forums etc. + * @param view The name of the view the size is for i.e. details, icons etc. + * @param size The size of the page + */ + public void addDefaultPageSize(String page, String view, int size) + { + this.pagesSizes.put(page + SEPARATOR + view, new Integer(size)); + } + + /** + * Returns the page size for the given page and view combination + * + * @param page The name of the page i.e. browse, forums etc. + * @param view The name of the view the size is for i.e. details, icons etc. + * @return The size of the requested page, if the combination doesn't exist + * the default for the view will be used, if the view doesn't exist either + * 10 will be returned. + */ + public int getDefaultPageSize(String page, String view) + { + Integer pageSize = this.pagesSizes.get(page + SEPARATOR + view); + + // try just the view if the combination isn't present + if (pageSize == null) + { + pageSize = this.pagesSizes.get(view); + + // if the view is not present either default to 10 + if (pageSize == null) + { + pageSize = new Integer(10); + } + } + + return pageSize.intValue(); + } + + /** + * Adds a default sorting column for the given page + * + * @param page The name of the page i.e. browse, forums etc. + * @param column The name of the column to initially sort by + */ + public void addDefaultSortColumn(String page, String column) + { + this.sortColumns.put(page, column); + } + + /** + * Returns the default sort column for the given page + * + * @param page The name of the page i.e. browse, forums etc. + * @return The name of the column to sort by, name is returned if + * the page is not found + */ + public String getDefaultSortColumn(String page) + { + String column = this.sortColumns.get(page); + + if (column == null) + { + column = this.defaultSortColumn; + } + + return column; + } + + /** + * Sets the given page as using descending sorts + * + * @param page The name of the page i.e. browse, forums etc. + */ + public void addDescendingSort(String page) + { + this.descendingSorts.add(page); + } + + /** + * Determines whether the given page has been + * configured to use descending sorting by default + * + * @param page The name of the page i.e. browse, forums etc. + * @return true if the page should use descending sorts + */ + public boolean hasDescendingSort(String page) + { + return this.descendingSorts.contains(page); + } + + /** + * @return Returns the recentSpacesItems. + */ + public int getRecentSpacesItems() + { + return this.recentSpacesItems; + } + + /** + * @param recentSpacesItems The recentSpacesItems to set. + */ + /*package*/ void setRecentSpacesItems(int recentSpacesItems) + { + this.recentSpacesItems = recentSpacesItems; + } + + /** + * Add a language locale and display label to the list. + * + * @param locale Locale code + * @param label Display label + */ + /*package*/ void addLanguage(String locale, String label) + { + this.localeMap.put(locale, label); + this.languages.add(locale); + } + + /** + * @return List of supported language locale strings in config file order + */ + public List getLanguages() + { + return this.languages; + } + + /** + * @param locale The locale string to lookup language label for + * + * @return the language label for specified locale string, or null if not found + */ + public String getLabelForLanguage(String locale) + { + return this.localeMap.get(locale); + } + + /** + * @return Returns the help Url. + */ + public String getHelpUrl() + { + return this.helpUrl; + } + + /** + * @param helpUrl The help Url to set. + */ + /*package*/ void setHelpUrl(String helpUrl) + { + this.helpUrl = helpUrl; + } + + /** + * @return Returns the edit link type. + */ + public String getEditLinkType() + { + return this.editLinkType; + } + + /** + * @param editLinkType The edit link type to set. + */ + /*package*/ void setEditLinkType(String editLinkType) + { + this.editLinkType = editLinkType; + } + + /** + * @return Returns the search minimum number of characters. + */ + public int getSearchMinimum() + { + return this.searchMinimum; + } + + /** + * @param searchMinimum The searchMinimum to set. + */ + /*package*/ void setSearchMinimum(int searchMinimum) + { + this.searchMinimum = searchMinimum; + } + + /** + * @return Returns the default Home Space permissions. + */ + public String getHomeSpacePermission() + { + return this.homeSpacePermission; + } + + /** + * @param homeSpacePermission The default Home Space permission to set. + */ + /*package*/ void setHomeSpacePermission(String homeSpacePermission) + { + this.homeSpacePermission = homeSpacePermission; + } + + /** + * @return Returns the contentTypes. + */ + public List getContentTypes() + { + return this.contentTypes; + } + + /** + * @param contentTypes The contentTypes to set. + */ + /*package*/ void setContentTypes(List contentTypes) + { + this.contentTypes = contentTypes; + } + + /** + * @return Returns the customProps. + */ + public List getCustomProperties() + { + return this.customProps; + } + + /** + * @param customProps The customProps to set. + */ + /*package*/ void setCustomProperties(List customProps) + { + this.customProps = customProps; + } + + + public static class CustomProperty + { + CustomProperty(String type, String aspect, String property) + { + Type = type; + Aspect = aspect; + Property = property; + } + + public String Type; + public String Aspect; + public String Property; + } +} diff --git a/source/java/org/alfresco/web/config/ClientElementReader.java b/source/java/org/alfresco/web/config/ClientElementReader.java new file mode 100644 index 0000000000..3d4a50494e --- /dev/null +++ b/source/java/org/alfresco/web/config/ClientElementReader.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.alfresco.web.config.ClientConfigElement.CustomProperty; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Element; + +/** + * Custom element reader to parse config for client config values + * + * @author Kevin Roast + */ +public class ClientElementReader implements ConfigElementReader +{ + public static final String ELEMENT_VIEWS = "views"; + public static final String ELEMENT_VIEW = "view"; + public static final String ELEMENT_VIEWDEFAULTS = "view-defaults"; + public static final String ELEMENT_PAGESIZE = "page-size"; + public static final String ELEMENT_SORTCOLUMN = "sort-column"; + public static final String ELEMENT_SORTDESCENDING = "sort-descending"; + public static final String ELEMENT_RECENTSPACESITEMS = "recent-spaces-items"; + public static final String ELEMENT_LANGUAGES = "languages"; + public static final String ELEMENT_LANGUAGE = "language"; + public static final String ATTRIBUTE_LOCALE = "locale"; + public static final String ATTRIBUTE_NAME = "name"; + public static final String ELEMENT_HELPURL = "help-url"; + public static final String ELEMENT_EDITLINKTYPE = "edit-link-type"; + public static final String ELEMENT_SEARCHMINIMUM = "search-minimum"; + public static final String ELEMENT_HOMESPACEPERMISSION = "home-space-permission"; + public static final String ELEMENT_ADVANCEDSEARCH = "advanced-search"; + public static final String ELEMENT_CONTENTTYPES = "content-types"; + public static final String ELEMENT_TYPE = "type"; + public static final String ELEMENT_CUSTOMPROPS = "custom-properties"; + public static final String ELEMENT_METADATA = "meta-data"; + public static final String ATTRIBUTE_TYPE = "type"; + public static final String ATTRIBUTE_PROPERTY = "property"; + public static final String ATTRIBUTE_ASPECT = "aspect"; + + private static Log logger = LogFactory.getLog(ClientElementReader.class); + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + @SuppressWarnings("unchecked") + public ConfigElement parse(Element element) + { + ClientConfigElement configElement = null; + + if (element != null) + { + String name = element.getName(); + if (name.equals(ClientConfigElement.CONFIG_ELEMENT_ID) == false) + { + throw new ConfigException("ClientElementReader can only parse " + + ClientConfigElement.CONFIG_ELEMENT_ID + "elements, the element passed was '" + + name + "'"); + } + + configElement = new ClientConfigElement(); + + // get the configured views + Element views = element.element(ELEMENT_VIEWS); + if (views != null) + { + Iterator renderers = views.elementIterator(ELEMENT_VIEW); + while (renderers.hasNext()) + { + Element renderer = renderers.next(); + configElement.addView(renderer.getTextTrim()); + } + } + + // get all the view related default settings + Element viewDefaults = element.element(ELEMENT_VIEWDEFAULTS); + if (viewDefaults != null) + { + Iterator pages = viewDefaults.elementIterator(); + while (pages.hasNext()) + { + Element page = pages.next(); + String pageName = page.getName(); + + // get the default view mode for the page + Element defaultView = page.element(ELEMENT_VIEW); + if (defaultView != null) + { + String viewName = defaultView.getTextTrim(); + configElement.addDefaultView(pageName, viewName); + } + + // get the initial sort column + Element sortColumn = page.element(ELEMENT_SORTCOLUMN); + if (sortColumn != null) + { + String column = sortColumn.getTextTrim(); + configElement.addDefaultSortColumn(pageName, column); + } + + // get the sort descending option + Element sortDesc = page.element(ELEMENT_SORTDESCENDING); + if (sortDesc != null) + { + Boolean descending = new Boolean(sortDesc.getTextTrim()); + if (descending.booleanValue() == true) + { + configElement.addDescendingSort(pageName); + } + } + + // process the page-size element + processPageSizeElement(page.element(ELEMENT_PAGESIZE), + pageName, configElement); + } + } + + // get the languages sub-element + Element languages = element.element(ELEMENT_LANGUAGES); + if (languages != null) + { + Iterator langsItr = languages.elementIterator(ELEMENT_LANGUAGE); + while (langsItr.hasNext()) + { + Element language = langsItr.next(); + String localeCode = language.attributeValue(ATTRIBUTE_LOCALE); + String label = language.getTextTrim(); + + if (localeCode != null && localeCode.length() != 0 && + label != null && label.length() != 0) + { + // store the language code against the display label + configElement.addLanguage(localeCode, label); + } + } + } + + // get the recent space max items + Element recentSpaces = element.element(ELEMENT_RECENTSPACESITEMS); + if (recentSpaces != null) + { + configElement.setRecentSpacesItems(Integer.parseInt(recentSpaces.getTextTrim())); + } + + // get the Help url + Element helpUrl = element.element(ELEMENT_HELPURL); + if (helpUrl != null) + { + configElement.setHelpUrl(helpUrl.getTextTrim()); + } + + // get the edit link type + Element editLinkType = element.element(ELEMENT_EDITLINKTYPE); + if (editLinkType != null) + { + configElement.setEditLinkType(editLinkType.getTextTrim()); + } + + // get the minimum number of characters for valid search string + Element searchMin = element.element(ELEMENT_SEARCHMINIMUM); + if (searchMin != null) + { + configElement.setSearchMinimum(Integer.parseInt(searchMin.getTextTrim())); + } + + // get the default permission for newly created users Home Spaces + Element permission = element.element(ELEMENT_HOMESPACEPERMISSION); + if (permission != null) + { + configElement.setHomeSpacePermission(permission.getTextTrim()); + } + + // get the Advanced Search config block + Element advsearch = element.element(ELEMENT_ADVANCEDSEARCH); + if (advsearch != null) + { + // get the list of content types + Element contentTypes = advsearch.element(ELEMENT_CONTENTTYPES); + Iterator typesItr = contentTypes.elementIterator(ELEMENT_TYPE); + List types = new ArrayList(5); + while (typesItr.hasNext()) + { + Element contentType = typesItr.next(); + String type = contentType.attributeValue(ATTRIBUTE_NAME); + if (type != null) + { + types.add(type); + } + } + configElement.setContentTypes(types); + + // get the list of custom properties to display + Element customProps = advsearch.element(ELEMENT_CUSTOMPROPS); + Iterator propsItr = customProps.elementIterator(ELEMENT_METADATA); + List props = new ArrayList(5); + while (propsItr.hasNext()) + { + Element propElement = propsItr.next(); + String type = propElement.attributeValue(ATTRIBUTE_TYPE); + String aspect = propElement.attributeValue(ATTRIBUTE_ASPECT); + String prop = propElement.attributeValue(ATTRIBUTE_PROPERTY); + props.add(new ClientConfigElement.CustomProperty(type, aspect, prop)); + } + configElement.setCustomProperties(props); + } + } + + return configElement; + } + + /** + * Processes a page-size element + * + * @param pageSizeElement The element to process + * @param page The page the page-size element belongs to + * @param configElement The config element being populated + */ + @SuppressWarnings("unchecked") + private void processPageSizeElement(Element pageSizeElement, String page, + ClientConfigElement configElement) + { + if (pageSizeElement != null) + { + Iterator views = pageSizeElement.elementIterator(); + while (views.hasNext()) + { + Element view = views.next(); + String viewName = view.getName(); + String pageSize = view.getTextTrim(); + try + { + configElement.addDefaultPageSize(page, viewName, Integer.parseInt(pageSize)); + } + catch (NumberFormatException nfe) + { + if (logger.isWarnEnabled()) + { + logger.warn("Failed to set page size for view '" + viewName + + "' in page '" + page + "' as '" + pageSize + + "' is an invalid number!"); + } + } + } + } + } +} diff --git a/source/java/org/alfresco/web/config/MimeTypeConfigElement.java b/source/java/org/alfresco/web/config/MimeTypeConfigElement.java new file mode 100644 index 0000000000..eb7cf14f37 --- /dev/null +++ b/source/java/org/alfresco/web/config/MimeTypeConfigElement.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.element.ConfigElementAdapter; + +/** + * @author Kevin Roast + */ +public final class MimeTypeConfigElement extends ConfigElementAdapter +{ + /** + * Default Constructor + */ + public MimeTypeConfigElement() + { + super(MimeTypesElementReader.ELEMENT_MIMETYPES); + } + + /** + * Constructor + * + * @param mappings Map of mimetype elements to use + */ + public MimeTypeConfigElement(Map mappings) + { + super(MimeTypesElementReader.ELEMENT_MIMETYPES); + this.mimetypes = mappings; + } + + /** + * @see org.alfresco.config.element.ConfigElementAdapter#combine(org.alfresco.config.ConfigElement) + */ + public ConfigElement combine(ConfigElement configElement) + { + MimeTypeConfigElement combined = new MimeTypeConfigElement(this.mimetypes); + + if (configElement instanceof MimeTypeConfigElement) + { + combined.mimetypes.putAll( ((MimeTypeConfigElement)configElement).mimetypes ); + } + + return combined; + } + + /** + * Add a mimetype extension mapping to the config element + * + * @param ext extension to map against + * @param mimetype mimetype content type for the specified extension + */ + public void addMapping(String ext, String mimetype) + { + this.mimetypes.put(ext, mimetype); + } + + /** + * Return the mimetype for the specified extension + * + * @param ext File + * + * @return mimetype content type or null if not found + */ + public String getMimeType(String ext) + { + return this.mimetypes.get(ext); + } + + private Map mimetypes = new HashMap(89, 1.0f); +} diff --git a/source/java/org/alfresco/web/config/MimeTypesElementReader.java b/source/java/org/alfresco/web/config/MimeTypesElementReader.java new file mode 100644 index 0000000000..5607781b82 --- /dev/null +++ b/source/java/org/alfresco/web/config/MimeTypesElementReader.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.Iterator; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.dom4j.Element; + +/** + * @author Kevin Roast + */ +public class MimeTypesElementReader implements ConfigElementReader +{ + public final static String ELEMENT_MIMETYPES = "mimetypes"; + public final static String ELEMENT_MIMEMAPPING = "mime-mapping"; + public final static String ELEMENT_EXTENSION = "extension"; + public final static String ELEMENT_MIMETYPE = "mime-type"; + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + public ConfigElement parse(Element element) + { + MimeTypeConfigElement configElement = null; + + if (element != null) + { + String name = element.getName(); + if (name.equals(ELEMENT_MIMETYPES) == false) + { + throw new ConfigException("MimeTypesElementReader can only parse " + + ELEMENT_MIMETYPES + "elements, the element passed was '" + + name + "'"); + } + + configElement = new MimeTypeConfigElement(); + + // walk the mime-mapping elements + Iterator mappings = element.elementIterator(ELEMENT_MIMEMAPPING); + while (mappings.hasNext()) + { + Element mapping = mappings.next(); + Element extensionElement = mapping.element(ELEMENT_EXTENSION); + Element mimetypeElement = mapping.element(ELEMENT_MIMETYPE); + + if (extensionElement == null || mimetypeElement == null) + { + throw new ConfigException("mime-mapping element must specify 'extension' and 'mime-type'"); + } + + String extension = extensionElement.getTextTrim(); + String mimetype = mimetypeElement.getTextTrim(); + + if (extension == null || extension.length() == 0) + { + throw new ConfigException("mime-mapping extension element value must be specified"); + } + if (mimetype == null || mimetype.length() == 0) + { + throw new ConfigException("mime-mapping mimetype element value must be specified"); + } + + // add the mimetype extension to the config element + configElement.addMapping(extension, mimetype); + } + } + + return configElement; + } +} diff --git a/source/java/org/alfresco/web/config/NavigationConfigElement.java b/source/java/org/alfresco/web/config/NavigationConfigElement.java new file mode 100644 index 0000000000..91d3fff88d --- /dev/null +++ b/source/java/org/alfresco/web/config/NavigationConfigElement.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.HashMap; +import java.util.List; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.element.ConfigElementAdapter; +import org.alfresco.config.element.GenericConfigElement; + +/** + * Custom config element that represents the config data for navigation + * + * @author gavinc + */ +public class NavigationConfigElement extends ConfigElementAdapter +{ + private HashMap viewIds = new HashMap(); + private HashMap outcomes = new HashMap(); + + private boolean kidsPopulated = false; + + /** + * Default constructor + */ + public NavigationConfigElement() + { + super("navigation"); + } + + /** + * Constructor + * + * @param name Name of the element this config element represents + */ + public NavigationConfigElement(String name) + { + super(name); + } + + /** + * @see org.alfresco.config.ConfigElement#getChildren() + */ + public List getChildren() + { + // lazily build the list of generic config elements representing + // the navigation overrides as the caller may not even call this method + + List kids = null; + + if (this.viewIds.size() > 0 || this.outcomes.size() > 0) + { + if (this.kidsPopulated == false) + { + // create generic config elements for the from-view-id items + for (String fromViewId : this.viewIds.keySet()) + { + GenericConfigElement ce = new GenericConfigElement(NavigationElementReader.ELEMENT_OVERRIDE); + ce.addAttribute(NavigationElementReader.ATTR_FROM_VIEWID, fromViewId); + + NavigationResult navRes = this.viewIds.get(fromViewId); + String result = navRes.getResult(); + if (navRes.isOutcome()) + { + ce.addAttribute(NavigationElementReader.ATTR_TO_OUTCOME, result); + } + else + { + ce.addAttribute(NavigationElementReader.ATTR_TO_VIEWID, result); + } + + // add the element + this.children.add(ce); + } + + // create generic config elements for the from-outcome items + for (String fromOutcome : this.outcomes.keySet()) + { + GenericConfigElement ce = new GenericConfigElement(NavigationElementReader.ELEMENT_OVERRIDE); + ce.addAttribute(NavigationElementReader.ATTR_FROM_OUTCOME, fromOutcome); + + NavigationResult navRes = this.outcomes.get(fromOutcome); + String result = navRes.getResult(); + if (navRes.isOutcome()) + { + ce.addAttribute(NavigationElementReader.ATTR_TO_OUTCOME, result); + } + else + { + ce.addAttribute(NavigationElementReader.ATTR_TO_VIEWID, result); + } + + // add the element + this.children.add(ce); + } + + this.kidsPopulated = true; + } + + kids = super.getChildren(); + } + + return kids; + } + + /** + * @see org.alfresco.config.ConfigElement#combine(org.alfresco.config.ConfigElement) + */ + public ConfigElement combine(ConfigElement configElement) + { + NavigationConfigElement combined = new NavigationConfigElement(); + + // add all the existing from view id overrides + for (String fromViewId : this.viewIds.keySet()) + { + combined.addOverride(fromViewId, null, this.viewIds.get(fromViewId)); + } + + // add all the existing from outcome overrides + for (String fromOutcome : this.outcomes.keySet()) + { + combined.addOverride(null, fromOutcome, this.outcomes.get(fromOutcome)); + } + + // add all the from view id overrides from the given element + NavigationConfigElement navCfg = (NavigationConfigElement)configElement; + HashMap viewIds = navCfg.getViewIds(); + for (String fromViewId : viewIds.keySet()) + { + combined.addOverride(fromViewId, null, viewIds.get(fromViewId)); + } + + // add all the from outcome overrides from the given element + HashMap outcomes = navCfg.getOutcomes(); + for (String fromOutcome : outcomes.keySet()) + { + combined.addOverride(null, fromOutcome, outcomes.get(fromOutcome)); + } + + return combined; + } + + /** + * Returns the list of view ids that have overrides defined + * + * @return Map of view ids and navigation results + */ + public HashMap getViewIds() + { + return this.viewIds; + } + + /** + * Returns the list of outcomes that have overrides defined + * + * @return Map of outcomes and navigation results + */ + public HashMap getOutcomes() + { + return this.outcomes; + } + + /** + * Adds an override configuration item + * + * @param fromViewId The from-view-id value from the config + * @param fromOutcome The from-outcome value from the config + * @param toViewId The to-view-id value from the config + * @param toOutcome The to-outcome value from the config + */ + public void addOverride(String fromViewId, String fromOutcome, + String toViewId, String toOutcome) + { + // NOTE: the constructor will check the validity of the to* parameters + NavigationResult result = new NavigationResult(toViewId, toOutcome); + addOverride(fromViewId, fromOutcome, result); + } + + /** + * Adds an override configuration item + * + * @param fromViewId The from-view-id value from the config + * @param fromOutcome The from-outcome value from the config + * @param result The navigation result object to add + */ + public void addOverride(String fromViewId, String fromOutcome, + NavigationResult result) + { + if (fromViewId != null && fromOutcome != null) + { + throw new IllegalStateException("You can not have both a from-view-id and from-outcome"); + } + + if (fromViewId != null) + { + this.viewIds.put(fromViewId, result); + } + else if (fromOutcome != null) + { + this.outcomes.put(fromOutcome, result); + } + } + + /** + * Returns the best match navigation override configured for the given + * current view id and/or outcome. + * + * If an outcome is passed it takes precedence, the view id will not be + * used. + * + * @param fromViewId The current view id + * @param fromOutcome The current outcome + * @return The navigation result + */ + public NavigationResult getOverride(String fromViewId, String fromOutcome) + { + NavigationResult result = null; + + // look for a match for the outcome if one was provided + if (fromOutcome != null) + { + result = this.outcomes.get(fromOutcome); + } + else if (fromViewId != null) + { + result = this.viewIds.get(fromViewId); + } + + return result; + } +} diff --git a/source/java/org/alfresco/web/config/NavigationElementReader.java b/source/java/org/alfresco/web/config/NavigationElementReader.java new file mode 100644 index 0000000000..279aea5df7 --- /dev/null +++ b/source/java/org/alfresco/web/config/NavigationElementReader.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.Iterator; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.dom4j.Element; + +/** + * Custom element reader to parse config for navigation overrides + * + * @author gavinc + */ +public class NavigationElementReader implements ConfigElementReader +{ + public static final String ELEMENT_NAVIGATION = "navigation"; + public static final String ELEMENT_OVERRIDE = "override"; + public static final String ATTR_FROM_VIEWID = "from-view-id"; + public static final String ATTR_FROM_OUTCOME = "from-outcome"; + public static final String ATTR_TO_VIEWID = "to-view-id"; + public static final String ATTR_TO_OUTCOME = "to-outcome"; + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + public ConfigElement parse(Element element) + { + NavigationConfigElement configElement = null; + + if (element != null) + { + String name = element.getName(); + if (ELEMENT_NAVIGATION.equals(name) == false) + { + throw new ConfigException("NavigationElementReader can only parse " + + ELEMENT_NAVIGATION + "elements, " + "the element passed was '" + + name + "'"); + } + + configElement = new NavigationConfigElement(); + + // go through the items to show + Iterator items = element.elementIterator(); + while (items.hasNext()) + { + Element item = items.next(); + + // only process the override elements + if (ELEMENT_OVERRIDE.equals(item.getName())) + { + String fromViewId = item.attributeValue(ATTR_FROM_VIEWID); + String fromOutcome = item.attributeValue(ATTR_FROM_OUTCOME); + String toViewId = item.attributeValue(ATTR_TO_VIEWID); + String toOutcome = item.attributeValue(ATTR_TO_OUTCOME); + + configElement.addOverride(fromViewId, fromOutcome, toViewId, toOutcome); + } + } + } + + return configElement; + } +} diff --git a/source/java/org/alfresco/web/config/NavigationResult.java b/source/java/org/alfresco/web/config/NavigationResult.java new file mode 100644 index 0000000000..1d09a7d3aa --- /dev/null +++ b/source/java/org/alfresco/web/config/NavigationResult.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +/** + * Represents the result of a navigation config result. + * + * This object holds the string result which can either represent an outcome + * or a view id. + * + * @author gavinc + */ +public class NavigationResult +{ + private String result; + private boolean isOutcome = true; + + /** + * Default constructor + * + * @param viewId The to-view-id value + * @param outcome The to-outcome value + */ + public NavigationResult(String viewId, String outcome) + { + if (viewId != null && outcome != null) + { + throw new IllegalStateException("You can not have both a to-view-id and to-outcome"); + } + + if (outcome != null) + { + this.result = outcome; + } + else if (viewId != null) + { + this.result = viewId; + this.isOutcome = false; + } + } + + /** + * Returns the result + * + * @return The result + */ + public String getResult() + { + return this.result; + } + + /** + * Determines whether the result is an outcome + * + * @return true if the result represents an outcome, + * false if it represents a view id + */ + public boolean isOutcome() + { + return this.isOutcome; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" (result=").append(this.result); + buffer.append(" isOutcome=").append(this.isOutcome).append(")"); + return buffer.toString(); + } +} diff --git a/source/java/org/alfresco/web/config/NodeTypeEvaluator.java b/source/java/org/alfresco/web/config/NodeTypeEvaluator.java new file mode 100644 index 0000000000..154a1d42ad --- /dev/null +++ b/source/java/org/alfresco/web/config/NodeTypeEvaluator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import org.alfresco.config.evaluator.Evaluator; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; + +/** + * Evaluator that determines whether a given object has a particular node type + * + * @author gavinc + */ +public class NodeTypeEvaluator implements Evaluator +{ + /** + * Determines whether the given node type matches the path of the given object + * + * @see org.alfresco.config.evaluator.Evaluator#applies(java.lang.Object, java.lang.String) + */ + public boolean applies(Object obj, String condition) + { + boolean result = false; + + if (obj instanceof Node) + { + QName type = ((Node)obj).getType(); + if (type != null) + { + result = type.equals(Repository.resolveToQName(condition)); + } + } + + return result; + } + +} diff --git a/source/java/org/alfresco/web/config/PathEvaluator.java b/source/java/org/alfresco/web/config/PathEvaluator.java new file mode 100644 index 0000000000..1db2aa13bd --- /dev/null +++ b/source/java/org/alfresco/web/config/PathEvaluator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import org.alfresco.config.evaluator.Evaluator; +import org.alfresco.web.bean.repository.Node; + +/** + * Evaluator that determines whether a given object has a particular path + * + * @author gavinc + */ +public class PathEvaluator implements Evaluator +{ + /** + * Determines whether the given path matches the path of the given object + * + * @see org.alfresco.config.evaluator.Evaluator#applies(java.lang.Object, java.lang.String) + */ + public boolean applies(Object obj, String condition) + { + boolean result = false; + + // TODO: Also deal with NodeRef object's being passed in + + if (obj instanceof Node) + { + String path = (String)((Node)obj).getPath(); + if (path != null) + { + result = path.equalsIgnoreCase(condition); + } + } + + return result; + } + +} diff --git a/source/java/org/alfresco/web/config/PropertySheetConfigElement.java b/source/java/org/alfresco/web/config/PropertySheetConfigElement.java new file mode 100644 index 0000000000..3dafdd0fd6 --- /dev/null +++ b/source/java/org/alfresco/web/config/PropertySheetConfigElement.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.element.ConfigElementAdapter; +import org.alfresco.config.element.GenericConfigElement; + +/** + * Custom config element that represents the config data for a property sheet + * + * @author gavinc + */ +public class PropertySheetConfigElement extends ConfigElementAdapter +{ + // TODO: Currently this object just deals with properties and associations to show, + // in the future it will also deal with properties and associations to hide. + + private List items = new ArrayList(); + private Map itemsMap = new HashMap(); + private List itemNames = new ArrayList(); + private boolean kidsPopulated = false; + + /** + * Default constructor + */ + public PropertySheetConfigElement() + { + super("property-sheet"); + } + + /** + * Constructor + * + * @param name Name of the element this config element represents + */ + public PropertySheetConfigElement(String name) + { + super(name); + } + + /** + * @see org.alfresco.config.ConfigElement#getChildren() + */ + public List getChildren() + { + // lazily build the list of generic config elements representing + // the properties as the caller may not even call this method + + List kids = null; + + if (this.items.size() > 0) + { + if (this.kidsPopulated == false) + { + Iterator items = this.items.iterator(); + while (items.hasNext()) + { + ItemConfig pc = items.next(); + GenericConfigElement ce = null; + if (pc instanceof PropertyConfig) + { + ce = new GenericConfigElement(PropertySheetElementReader.ELEMENT_SHOW_PROPERTY); + } + else if (pc instanceof AssociationConfig) + { + ce = new GenericConfigElement(PropertySheetElementReader.ELEMENT_SHOW_ASSOC); + } + else + { + ce = new GenericConfigElement(PropertySheetElementReader.ELEMENT_SHOW_CHILD_ASSOC); + } + + ce.addAttribute(PropertySheetElementReader.ATTR_NAME, pc.getName()); + ce.addAttribute(PropertySheetElementReader.ATTR_DISPLAY_LABEL, pc.getDisplayLabel()); + ce.addAttribute(PropertySheetElementReader.ATTR_DISPLAY_LABEL_ID, pc.getDisplayLabelId()); + ce.addAttribute(PropertySheetElementReader.ATTR_READ_ONLY, Boolean.toString(pc.isReadOnly())); + ce.addAttribute(PropertySheetElementReader.ATTR_CONVERTER, pc.getConverter()); + this.children.add(ce); + } + + this.kidsPopulated = true; + } + + kids = super.getChildren(); + } + + return kids; + } + + /** + * @see org.alfresco.config.ConfigElement#combine(org.alfresco.config.ConfigElement) + */ + public ConfigElement combine(ConfigElement configElement) + { + PropertySheetConfigElement combined = new PropertySheetConfigElement(); + + // add all the existing properties + Iterator items = this.getItemsToShow().iterator(); + while (items.hasNext()) + { + combined.addItem(items.next()); + } + + // add all the properties from the given element + items = ((PropertySheetConfigElement)configElement).getItemsToShow().iterator(); + while (items.hasNext()) + { + combined.addItem(items.next()); + } + + return combined; + } + + /** + * Adds an item to show + * + * @param itemConfig A pre-configured property or association config object + */ + public void addItem(ItemConfig itemConfig) + { + if (this.itemsMap.containsKey(itemConfig.getName()) == false) + { + this.items.add(itemConfig); + this.itemsMap.put(itemConfig.getName(), itemConfig); + this.itemNames.add(itemConfig.getName()); + } + } + + /** + * Adds a property to show + * + * @param name The name of the property + * @param displayLabel Display label to use for the property + * @param displayLabelId Display label message id to use for the property + * @param readOnly Sets whether the property should be rendered as read only + * @param converter The name of a converter to apply to the property control + */ + public void addProperty(String name, String displayLabel, String displayLabelId, String readOnly, String converter) + { + addItem(new PropertyConfig(name, displayLabel, displayLabelId, Boolean.parseBoolean(readOnly), converter)); + } + + /** + * Adds an association to show + * + * @param name The name of the association + * @param displayLabel Display label to use for the property + * @param displayLabelId Display label message id to use for the property + * @param readOnly Sets whether the association should be rendered as read only + * @param converter The name of a converter to apply to the association control + */ + public void addAssociation(String name, String displayLabel, String displayLabelId, String readOnly, String converter) + { + addItem(new AssociationConfig(name, displayLabel, displayLabelId, Boolean.parseBoolean(readOnly), converter)); + } + + /** + * Adds a child association to show + * + * @param name The name of the child association + * @param displayLabel Display label to use for the property + * @param displayLabelId Display label message id to use for the property + * @param readOnly Sets whether the association should be rendered as read only + * @param converter The name of a converter to apply to the association control + */ + public void addChildAssociation(String name, String displayLabel, String displayLabelId, String readOnly, String converter) + { + addItem(new ChildAssociationConfig(name, displayLabel, displayLabelId, Boolean.parseBoolean(readOnly), converter)); + } + + /** + * @return Returns a list of item names to display + */ + public List getItemNamesToShow() + { + return this.itemNames; + } + + /** + * @return Returns the list of item config objects that represent those to display + */ + public List getItemsToShow() + { + return this.items; + } + + /** + * @return Returns a map of the item names to show + */ + public Map getItemsMapToShow() + { + return this.itemsMap; + } + + /** + * Inner class to represent a configured property sheet item + */ + public abstract class ItemConfig + { + private String name; + private String displayLabel; + private String displayLabelId; + private String converter; + private boolean readOnly; + + public ItemConfig(String name, String displayLabel, String displayLabelId, + boolean readOnly, String converter) + { + this.name = name; + this.displayLabel = displayLabel; + this.displayLabelId = displayLabelId; + this.readOnly = readOnly; + this.converter = converter; + } + + /** + * @return The display label + */ + public String getDisplayLabel() + { + return this.displayLabel; + } + + /** + * @return The display label message id + */ + public String getDisplayLabelId() + { + return this.displayLabelId; + } + + /** + * @return The property name + */ + public String getName() + { + return this.name; + } + + /** + * @return Determines whether the property is configured as read only + */ + public boolean isReadOnly() + { + return this.readOnly; + } + + public String getConverter() + { + return this.converter; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + StringBuilder buffer = new StringBuilder(super.toString()); + buffer.append(" (name=").append(this.name); + buffer.append(" displaylabel=").append(this.displayLabel); + buffer.append(" displaylabelId=").append(this.displayLabelId); + buffer.append(" converter=").append(this.converter); + buffer.append(" readonly=").append(this.readOnly).append(")"); + return buffer.toString(); + } + } + + /** + * Inner class to represent a configured property + */ + public class PropertyConfig extends ItemConfig + { + public PropertyConfig(String name, String displayLabel, String displayLabelId, + boolean readOnly, String converter) + { + super(name, displayLabel, displayLabelId, readOnly, converter); + } + } + + /** + * Inner class to represent a configured association + */ + public class AssociationConfig extends ItemConfig + { + public AssociationConfig(String name, String displayLabel, String displayLabelId, + boolean readOnly, String converter) + { + super(name, displayLabel, displayLabelId, readOnly, converter); + } + } + + /** + * Inner class to represent a configured child association + */ + public class ChildAssociationConfig extends ItemConfig + { + public ChildAssociationConfig(String name, String displayLabel, String displayLabelId, + boolean readOnly, String converter) + { + super(name, displayLabel, displayLabelId, readOnly, converter); + } + } +} diff --git a/source/java/org/alfresco/web/config/PropertySheetElementReader.java b/source/java/org/alfresco/web/config/PropertySheetElementReader.java new file mode 100644 index 0000000000..3832c5ed07 --- /dev/null +++ b/source/java/org/alfresco/web/config/PropertySheetElementReader.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.Iterator; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.dom4j.Element; + +/** + * Custom element reader to parse config for property sheets + * + * @author gavinc + */ +public class PropertySheetElementReader implements ConfigElementReader +{ + public static final String ELEMENT_PROPERTY_SHEET = "property-sheet"; + public static final String ELEMENT_SHOW_PROPERTY = "show-property"; + public static final String ELEMENT_SHOW_ASSOC = "show-association"; + public static final String ELEMENT_SHOW_CHILD_ASSOC = "show-child-association"; + public static final String ATTR_NAME = "name"; + public static final String ATTR_DISPLAY_LABEL = "displayLabel"; + public static final String ATTR_DISPLAY_LABEL_ID = "displayLabelId"; + public static final String ATTR_READ_ONLY = "readOnly"; + public static final String ATTR_CONVERTER = "converter"; + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + public ConfigElement parse(Element element) + { + PropertySheetConfigElement configElement = null; + + if (element != null) + { + String name = element.getName(); + if (name.equals(ELEMENT_PROPERTY_SHEET) == false) + { + throw new ConfigException("PropertySheetElementReader can only parse " + + ELEMENT_PROPERTY_SHEET + "elements, " + "the element passed was '" + + name + "'"); + } + + configElement = new PropertySheetConfigElement(); + + // go through the items to show + Iterator items = element.elementIterator(); + while (items.hasNext()) + { + Element item = items.next(); + String propName = item.attributeValue(ATTR_NAME); + String label = item.attributeValue(ATTR_DISPLAY_LABEL); + String labelId = item.attributeValue(ATTR_DISPLAY_LABEL_ID); + String readOnly = item.attributeValue(ATTR_READ_ONLY); + String converter = item.attributeValue(ATTR_CONVERTER); + + if (ELEMENT_SHOW_PROPERTY.equals(item.getName())) + { + // add the property to show to the custom config element + configElement.addProperty(propName, label, labelId, readOnly, converter); + } + else if (ELEMENT_SHOW_ASSOC.equals(item.getName())) + { + configElement.addAssociation(propName, label, labelId, readOnly, converter); + } + else if (ELEMENT_SHOW_CHILD_ASSOC.equals(item.getName())) + { + configElement.addChildAssociation(propName, label, labelId, readOnly, converter); + } + } + } + + return configElement; + } + +} diff --git a/source/java/org/alfresco/web/config/ServerConfigElement.java b/source/java/org/alfresco/web/config/ServerConfigElement.java new file mode 100644 index 0000000000..956ab5b302 --- /dev/null +++ b/source/java/org/alfresco/web/config/ServerConfigElement.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.element.ConfigElementAdapter; + +/** + * Custom config element that represents the config data for the server + * + * @author gavinc + */ +public class ServerConfigElement extends ConfigElementAdapter +{ + public static final String CONFIG_ELEMENT_ID = "server"; + + private String errorPage; + private String loginPage; + + /** + * Default constructor + */ + public ServerConfigElement() + { + super(CONFIG_ELEMENT_ID); + } + + /** + * Constructor + * + * @param name Name of the element this config element represents + */ + public ServerConfigElement(String name) + { + super(name); + } + + public ConfigElement combine(ConfigElement configElement) + { + // NOTE: combining these would simply override the values so we just need + // to return a new instance of the given config element + + ServerConfigElement combined = new ServerConfigElement(); + combined.setErrorPage(((ServerConfigElement)configElement).getErrorPage()); + combined.setLoginPage(((ServerConfigElement)configElement).getLoginPage()); + return combined; + } + + /** + * @return The error page the application should use + */ + public String getErrorPage() + { + return this.errorPage; + } + + /** + * @param errorPage Sets the error page + */ + public void setErrorPage(String errorPage) + { + this.errorPage = errorPage; + } + + /** + * @return Returns the login Page. + */ + public String getLoginPage() + { + return this.loginPage; + } + + /** + * @param loginPage The login Page to set. + */ + public void setLoginPage(String loginPage) + { + this.loginPage = loginPage; + } +} diff --git a/source/java/org/alfresco/web/config/ServerElementReader.java b/source/java/org/alfresco/web/config/ServerElementReader.java new file mode 100644 index 0000000000..2ff2415235 --- /dev/null +++ b/source/java/org/alfresco/web/config/ServerElementReader.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigException; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.dom4j.Element; + +/** + * Custom element reader to parse config for server details + * + * @author gavinc + */ +public class ServerElementReader implements ConfigElementReader +{ + public static final String ELEMENT_ERROR_PAGE = "error-page"; + public static final String ELEMENT_LOGIN_PAGE = "login-page"; + + /** + * @see org.alfresco.config.xml.elementreader.ConfigElementReader#parse(org.dom4j.Element) + */ + public ConfigElement parse(Element element) + { + ServerConfigElement configElement = null; + + if (element != null) + { + String name = element.getName(); + if (name.equals(ServerConfigElement.CONFIG_ELEMENT_ID) == false) + { + throw new ConfigException("ServerElementReader can only parse " + + ServerConfigElement.CONFIG_ELEMENT_ID + "elements, " + + "the element passed was '" + name + "'"); + } + + configElement = new ServerConfigElement(); + + // get the error page + Element errorPage = element.element(ELEMENT_ERROR_PAGE); + if (errorPage != null) + { + configElement.setErrorPage(errorPage.getTextTrim()); + } + + // get the login page + Element loginPage = element.element(ELEMENT_LOGIN_PAGE); + if (loginPage != null) + { + configElement.setLoginPage(loginPage.getTextTrim()); + } + } + + return configElement; + } +} diff --git a/source/java/org/alfresco/web/config/WebClientConfigTest.java b/source/java/org/alfresco/web/config/WebClientConfigTest.java new file mode 100644 index 0000000000..5dd44a89b1 --- /dev/null +++ b/source/java/org/alfresco/web/config/WebClientConfigTest.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigElement; +import org.alfresco.config.source.FileConfigSource; +import org.alfresco.config.xml.XMLConfigService; +import org.alfresco.util.BaseTest; +import org.alfresco.web.config.PropertySheetConfigElement.ItemConfig; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * JUnit tests to exercise the capabilities added to the web client config + * service + * + * @author gavinc + */ +public class WebClientConfigTest extends BaseTest +{ + private static Log logger = LogFactory.getLog(WebClientConfigTest.class); + + /** + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + + logger.info("******************************************************"); + } + + /** + * Tests the property sheet configuration classes + */ + public void testPropertySheetConfig() + { + // setup the config service + String configFile = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFile)); + svc.init(); + + // get hold of the property sheet config from the global section + Config global = svc.getGlobalConfig(); + ConfigElement globalPropSheet = global.getConfigElement("property-sheet"); + assertNotNull("global property sheet element should not be null", globalPropSheet); + assertTrue("config element should be an instance of PropertySheetConfigElement", + (globalPropSheet instanceof PropertySheetConfigElement)); + + // get the property names from the global section and make sure it is the + // name property + List propNames = ((PropertySheetConfigElement) globalPropSheet).getItemNamesToShow(); + logger.info("propNames = " + propNames); + assertTrue("There should only be one property in the list", propNames.size() == 1); + assertTrue("The property name should be 'name'", propNames.get(0).equals("name")); + + // get the config section representing a space aspect and make sure we get + // 5 properties + Config spaceAspectConfig = svc.getConfig("space-aspect"); + assertNotNull("Space aspect config should not be null", spaceAspectConfig); + PropertySheetConfigElement spacePropConfig = (PropertySheetConfigElement) spaceAspectConfig + .getConfigElement("property-sheet"); + assertNotNull("Space aspect property config should not be null", spacePropConfig); + propNames = spacePropConfig.getItemNamesToShow(); + logger.info("propNames = " + propNames); + assertTrue("There should be 5 properties in the list", propNames.size() == 5); + + // make sure the property sheet config has come back with the correct data + Map props = spacePropConfig.getItemsMapToShow(); + ItemConfig descProp = props.get("description"); + assertNotNull("description property config should not be null", descProp); + assertEquals("display label for description should be 'Description'", descProp.getDisplayLabel(), + "Description"); + assertFalse("read only for description should be 'false'", descProp.isReadOnly()); + + ItemConfig createdDataProp = props.get("createddate"); + assertNotNull("createddate property config should not be null", createdDataProp); + assertEquals("display label for createddate should be null", null, createdDataProp.getDisplayLabel()); + assertTrue("read only for createddate should be 'true'", createdDataProp.isReadOnly()); + + ItemConfig iconProp = props.get("icon"); + assertNotNull("icon property config should not be null", iconProp); + assertEquals("display label for icon should be null", null, iconProp.getDisplayLabel()); + assertFalse("read only for icon should be 'false'", iconProp.isReadOnly()); + } + + /** + * Tests the config service by retrieving property sheet configuration using + * the generic interfaces + */ + public void testGenericConfigElement() + { + // setup the config service + String configFiles = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFiles)); + svc.init(); + + // get the space aspect configuration + Config configProps = svc.getConfig("space-aspect"); + ConfigElement propsToDisplay = configProps.getConfigElement("property-sheet"); + assertNotNull("property sheet config should not be null", propsToDisplay); + + // get all the property names using the ConfigElement interface methods + List kids = propsToDisplay.getChildren(); + List propNames = new ArrayList(); + for (ConfigElement propElement : propsToDisplay.getChildren()) + { + String value = propElement.getValue(); + assertNull("property value should be null", value); + String propName = propElement.getAttribute("name"); + propNames.add(propName); + } + + logger.info("propNames = " + propNames); + assertTrue("There should be 5 properties", propNames.size() == 5); + assertFalse("The id attribute should not be present", propsToDisplay.hasAttribute("id")); + } + + /** + * Tests the config service by retrieving property sheet configuration using + * the custom config objects + */ + public void testGetProperties() + { + // setup the config service + String configFiles = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFiles)); + svc.init(); + + // get the space aspect configuration + Config configProps = svc.getConfig("space-aspect"); + PropertySheetConfigElement propsToDisplay = (PropertySheetConfigElement)configProps. + getConfigElement("property-sheet"); + assertNotNull("property sheet config should not be null", propsToDisplay); + + // get all the property names using the PropertySheetConfigElement implementation + List propNames = propsToDisplay.getItemNamesToShow(); + + // make sure the generic interfaces are also returning the correct data + List kids = propsToDisplay.getChildren(); + assertNotNull("kids should not be null", kids); + assertTrue("There should be more than one child", kids.size() > 1); + + logger.info("propNames = " + propNames); + assertEquals("There should be 5 properties", propNames.size() == 5, true); + assertFalse("The id attribute should not be present", propsToDisplay.hasAttribute("id")); + } + + /** + * Tests the custom server configuration objects + */ + public void testServerConfig() + { + // setup the config service + String configFiles = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFiles)); + svc.init(); + + // get the global config and from that the server config + ServerConfigElement serverConfig = (ServerConfigElement)svc.getGlobalConfig(). + getConfigElement(ServerConfigElement.CONFIG_ELEMENT_ID); + assertNotNull("server config should not be null", serverConfig); + + String errorPage = serverConfig.getErrorPage(); + assertTrue("error page should be '/jsp/error.jsp'", errorPage.equals("/jsp/error.jsp")); + } + + /** + * Tests the custom client configuration objects + */ + public void testClientConfig() + { + // setup the config service + String configFiles = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFiles)); + svc.init(); + + // get the global config and from that the client config + ClientConfigElement clientConfig = (ClientConfigElement)svc.getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + assertNotNull("client config should not be null", clientConfig); + + List views = clientConfig.getViews(); + assertEquals("There should be 2 configured views", 2, views.size()); + String renderer = views.get(1); + assertEquals("Renderer for the icons view should be 'org.alfresco.web.ui.common.renderer.data.RichListRenderer.IconViewRenderer'", + "org.alfresco.web.ui.common.renderer.data.RichListRenderer.IconViewRenderer", renderer); + + String defaultView = clientConfig.getDefaultView("topic"); + assertEquals("Default view for topic should be 'bubble'", "bubble", defaultView); + + // get the defualt view for something that doesn't exist + defaultView = clientConfig.getDefaultView("not-there"); + assertEquals("Default view for missing view should be 'details'", "details", defaultView); + + // get the default page size for the forum details view + int pageSize = clientConfig.getDefaultPageSize("forum", "details"); + assertEquals("Page size for forum details should be 20", 20, pageSize); + + // get the defualt page size for a non existent view + pageSize = clientConfig.getDefaultPageSize("not", "there"); + assertEquals("Page size for forum details should be 10", 10, pageSize); + + // get the default page size for a non existent screen and valid view + pageSize = clientConfig.getDefaultPageSize("not-there", "icons"); + assertEquals("Page size for icons view should be 9", 9, pageSize); + + // test the sort column + String column = clientConfig.getDefaultSortColumn("browse"); + assertEquals("Sort column for browse should be 'name'", "name", column); + + column = clientConfig.getDefaultSortColumn("topic"); + assertEquals("Sort column for topic should be 'created'", "created", column); + + // test the sorting direction + boolean sortDescending = clientConfig.hasDescendingSort("browse"); + assertFalse("browse screen should use an ascending sort", sortDescending); + + sortDescending = clientConfig.hasDescendingSort("topic"); + assertTrue("topic screen should use a descending sort", sortDescending); + } + + /** + * Tests the navigation config i.e. the custom element reader and config element + */ + public void testNavigation() + { + // setup the config service + String configFiles = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFiles)); + svc.init(); + + // *** Test the returning of a view id override + Config testCfg = svc.getConfig("viewid-navigation-result"); + assertNotNull("viewid-navigation-result config should not be null", testCfg); + + NavigationConfigElement navCfg = (NavigationConfigElement)testCfg.getConfigElement("navigation"); + assertNotNull("navigation config should not be null", navCfg); + + // get the result for the browse view id + NavigationResult navResult = navCfg.getOverride("/jsp/browse/browse.jsp", null); + assertEquals("result should be '/jsp/forums/forums.jsp'", "/jsp/forums/forums.jsp", + navResult.getResult()); + assertFalse("isOutcome test should be false", navResult.isOutcome()); + + // get the result for the browse outcome + navResult = navCfg.getOverride(null, "browse"); + assertEquals("result should be '/jsp/forums/topics.jsp'", "/jsp/forums/topics.jsp", + navResult.getResult()); + assertFalse("isOutcome test should be false", navResult.isOutcome()); + + // get the result when passing both the browse view id and outcome, make + // sure we get the result for the outcome as it should take precedence + navResult = navCfg.getOverride("/jsp/browse/browse.jsp", "browse"); + assertEquals("result should be '/jsp/forums/topics.jsp'", "/jsp/forums/topics.jsp", + navResult.getResult()); + assertFalse("isOutcome test should be false", navResult.isOutcome()); + + // *** Test the returning of an outcome override + testCfg = svc.getConfig("outcome-navigation-result"); + assertNotNull("outcome-navigation-result config should not be null", testCfg); + + navCfg = (NavigationConfigElement)testCfg.getConfigElement("navigation"); + assertNotNull("navigation config should not be null", navCfg); + + // get the result for the browse view id + navResult = navCfg.getOverride("/jsp/browse/browse.jsp", null); + assertEquals("result should be 'showSomethingElse'", "showSomethingElse", + navResult.getResult()); + assertTrue("isOutcome test should be true", navResult.isOutcome()); + + // get the result for the browse outcome + navResult = navCfg.getOverride(null, "browse"); + assertEquals("result should be 'showSomethingElse'", "showSomethingElse", + navResult.getResult()); + assertTrue("isOutcome test should be true", navResult.isOutcome()); + + // get the result when passing both the browse view id and outcome, make + // sure we get the result for the outcome as it should take precedence + navResult = navCfg.getOverride("/jsp/browse/browse.jsp", "browse"); + assertEquals("result should be 'showSomethingElse'", "showSomethingElse", + navResult.getResult()); + assertTrue("isOutcome test should be true", navResult.isOutcome()); + + // *** Test the duplicate result config + testCfg = svc.getConfig("duplicate-navigation-overrides"); + assertNotNull("duplicate-navigation-overrides config should not be null", testCfg); + + navCfg = (NavigationConfigElement)testCfg.getConfigElement("navigation"); + assertNotNull("navigation config should not be null", navCfg); + + // make sure the outcome result is 'newOutcome' + navResult = navCfg.getOverride(null, "browse"); + assertEquals("result should be 'newOutcome'", "newOutcome", + navResult.getResult()); + assertTrue("isOutcome test should be true", navResult.isOutcome()); + + // call getOverride passing a valid view id but an invalid outcome + // and make sure the result is null + navResult = navCfg.getOverride("/jsp/browse/browse.jsp", "nonExistentOutcome"); + assertNull("result should be null", navResult); + } + + public void testNavigationGenericConfig() + { + // setup the config service + String configFiles = getResourcesDir() + "test-config.xml"; + XMLConfigService svc = new XMLConfigService(new FileConfigSource(configFiles)); + svc.init(); + + // do a lookup using the generic config elements and make sure the correct + // info comes out + Config testCfg = svc.getConfig("duplicate-navigation-overrides"); + assertNotNull("duplicate-navigation-overrides config should not be null", testCfg); + + ConfigElement ce = testCfg.getConfigElement("navigation"); + assertNotNull("navigation config should not be null", ce); + + List children = ce.getChildren(); + assertNotNull(children); + + // make sure there are 2 children + assertEquals("There should be 2 children", 2, children.size()); + + // get the first child and make sure the attributes are correct, + // from-view-id should be '/jsp/browse/browse.jsp' and to-view-id + // should be '/jsp/forums/forums.jsp' + ConfigElement child = children.get(0); + String fromViewId = child.getAttribute("from-view-id"); + String fromOutcome = child.getAttribute("from-outcome"); + String toViewId = child.getAttribute("to-view-id"); + String toOutcome = child.getAttribute("to-outcome"); + + logger.info("fromViewId = " + fromViewId); + logger.info("fromOutcome = " + fromOutcome); + logger.info("toViewId = " + toViewId); + logger.info("toOutcome = " + toOutcome); + + assertNull(fromOutcome); + assertNull(toOutcome); + + assertEquals("/jsp/browse/browse.jsp", fromViewId); + assertEquals("/jsp/forums/forums.jsp", toViewId); + + // get the second child and make sure the attributes are correct, + // from-outcome should be 'browse' and to-outcome should be 'newOutcome' + child = children.get(1); + fromViewId = child.getAttribute("from-view-id"); + fromOutcome = child.getAttribute("from-outcome"); + toViewId = child.getAttribute("to-view-id"); + toOutcome = child.getAttribute("to-outcome"); + + logger.info("fromViewId = " + fromViewId); + logger.info("fromOutcome = " + fromOutcome); + logger.info("toViewId = " + toViewId); + logger.info("toOutcome = " + toOutcome); + + assertNull(fromViewId); + assertNull(toViewId); + + assertEquals("browse", fromOutcome); + assertEquals("newOutcome", toOutcome); + } +} diff --git a/source/java/org/alfresco/web/data/IDataContainer.java b/source/java/org/alfresco/web/data/IDataContainer.java new file mode 100644 index 0000000000..9ba03ecbe5 --- /dev/null +++ b/source/java/org/alfresco/web/data/IDataContainer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.data; + +/** + * @author kevinr + */ +public interface IDataContainer +{ + /** + * Return the currently sorted column if any + * + * @return current sorted column if any + */ + public String getCurrentSortColumn(); + + /** + * Returns the current sort direction. Only valid if a sort column is set. + * True is returned for descending sort, false for accending sort. + * + * @return true for descending sort, false for accending sort + */ + public boolean isCurrentSortDescending(); + + /** + * Returns the current page size used for this list, or -1 for no paging. + */ + public int getPageSize(); + + /** + * Return the current page the list is displaying + * + * @return Current page with zero based index + */ + public int getCurrentPage(); + + /** + * Set the current page to display. + * + * @param index Zero based page index to display + */ + public void setCurrentPage(int index); + + /** + * Return the count of max available pages + * + * @return count of max available pages + */ + public int getPageCount(); + + /** + * Returns true if a row of data is available + * + * @return true if data is available, false otherwise + */ + public boolean isDataAvailable(); + + /** + * Returns the next row of data from the data model + * + * @return next row of data as a Bean object + */ + public Object nextRow(); + + /** + * Sort the dataset using the specified sort parameters + * + * @param column Column to sort + * @param descending True for descending sort, false for ascending + * @param mode Sort mode to use (see IDataContainer constants) + */ + public void sort(String column, boolean descending, String mode); + + public final static String SORT_CASEINSENSITIVE = "case-insensitive"; + public final static String SORT_CASESENSITIVE = "case-sensitive"; +} diff --git a/source/java/org/alfresco/web/data/MergeSort.java b/source/java/org/alfresco/web/data/MergeSort.java new file mode 100644 index 0000000000..6bb4b6eaff --- /dev/null +++ b/source/java/org/alfresco/web/data/MergeSort.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.data; + +import java.util.List; + +/** + * @author kevinr + */ +public final class MergeSort extends Sort +{ + /** + * Constructor + * + * @param data a the List of String[] data to sort + * @param column the column getter method to use on the row to sort + * @param bForward true for a forward sort, false for a reverse sort + * @param mode sort mode to use (see IDataContainer constants) + */ + public MergeSort(List data, String column, boolean bForward, String mode) + { + super(data, column, bForward, mode); + } + + + // ------------------------------------------------------------------------------ + // Sort Implementation + + /** + * Runs the Quick Sort routine on the current dataset + */ + public void sort() + { + if (this.data.size() != 0) + { + // TODO: finish this! + //mergesort(this.data, 0, this.data.size() - 1); + + /*a = this.data; + + int n = a.length; + + b = new int[(n+1) >> 1]; + mergesort(0, n-1);*/ + } + } + + + // ------------------------------------------------------------------------------ + // Private methods + + /*private static Object[] a, b; + + private static void mergesort(int lo, int hi) + { + if (lo> 1; + mergesort(lo, m); + mergesort(m+1, hi); + merge(lo, m, hi); + } + } + + private static void merge(int lo, int m, int hi) + { + int i, j, k; + + i=0; + j=lo; + + // copy first half of array a to auxiliary array b + while (j <= m) + { + b[i++] = a[j++]; + } + + i=0; + k=lo; + + // copy back next-greatest element at each time + while (k < j && j <= hi) + { + if (b[i] <= a[j]) + { + a[k++]=b[i++]; + } + else + { + a[k++]=a[j++]; + } + } + + // copy back remaining elements of first half (if any) + while (k < j) + { + a[k++] = b[i++]; + } + }*/ +} diff --git a/source/java/org/alfresco/web/data/QuickSort.java b/source/java/org/alfresco/web/data/QuickSort.java new file mode 100644 index 0000000000..98cc13c1d7 --- /dev/null +++ b/source/java/org/alfresco/web/data/QuickSort.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.data; + +import java.util.List; + +/** + * QuickSort + * + * Implementation of a locale sensitive Quick Sort algorithm. The sorting supports + * locale specific case sensitive, case in-sensitive and numeric data sorting. The + * numeric sorting handles integer, floating point and scientific formats, with + * short-circuit value parsing. + * + * @author Kevin Roast + */ +public final class QuickSort extends Sort +{ + /** + * Constructor + * + * @param data a the List of String[] data to sort + * @param column the column getter method to use on the row to sort + * @param bForward true for a forward sort, false for a reverse sort + * @param mode sort mode to use (see IDataContainer constants) + */ + public QuickSort(List data, String column, boolean bForward, String mode) + { + super(data, column, bForward, mode); + } + + + // ------------------------------------------------------------------------------ + // Sort Implementation + + /** + * Runs the Quick Sort routine on the current dataset + */ + public void sort() + { + if (this.data.size() != 0) + { + qsort(this.data, 0, this.data.size() - 1); + } + } + + + // ------------------------------------------------------------------------------ + // Private methods + + /** + * recursive Quicksort function. + * + * @param v the array out of which to take a slice. + * @param lower the lower bound of this slice. + * @param upper the upper bound of this slice. + */ + private void qsort(final List v, final int lower, final int upper) + { + int sliceLength = upper - lower + 1 ; + if (sliceLength > 1) + { + if (sliceLength < 7) + { + // Insertion sort on smallest datasets + for (int i=lower; i<=upper; i++) + { + if (this.bForward == true) + { + for (int j=i; j > lower && getComparator().compare(this.keys.get(j - 1), this.keys.get(j)) > 0; j--) + { + // swap both the keys and the actual row data + swap(this.keys, j - 1, j); + swap(v, j - 1, j); + } + } + else + { + for (int j=i; j > lower && getComparator().compare(this.keys.get(j - 1), this.keys.get(j)) < 0; j--) + { + // swap both the keys and the actual row data + swap(this.keys, j - 1, j); + swap(v, j - 1, j); + } + } + } + } + else + { + int pivotIndex = partition(v, lower, upper); + qsort(v, lower, pivotIndex); + qsort(v, pivotIndex + 1, upper); + } + } + } + + /** + * Partition an array in two using the pivot value that is at the + * centre of the array being partitioned. + * + * This partition implementation based on that in Winder, R + * (1993) "Developing C++ Software", Wiley, p.395. NB. This + * implementation (unlike most others) does not guarantee that + * the split point contains the pivot value. Unlike other + * implementations, it requires only < (or >) relation and not + * both < and <= (or > and >=). Also, it seems easier to program + * and to understand. + * + * @param v the List out of which to take a slice. + * @param lower the lower bound of this slice. + * @param upper the upper bound of this slice. + */ + private int partition(final List v, int lower, int upper) + { + List keys = this.keys; + Object pivotValue = keys.get((upper + lower + 1) >> 1) ; + + int size = keys.size(); + + while (lower <= upper) + { + if (this.bForward == true) + { + while (getComparator().compare(keys.get(lower), pivotValue) < 0) + { + lower++; + } + while (getComparator().compare(pivotValue, keys.get(upper)) < 0) + { + upper--; + } + } + else + { + while (getComparator().compare(keys.get(lower), pivotValue) > 0) + { + lower++; + } + while (getComparator().compare(pivotValue, keys.get(upper)) > 0) + { + upper--; + } + } + if (lower <= upper) + { + if (lower < upper) + { + swap(keys, lower, upper); + swap(v, lower, upper); + } + lower++; + upper--; + } + } + + return upper; + } + +} // end class QuickSort diff --git a/source/java/org/alfresco/web/data/Sort.java b/source/java/org/alfresco/web/data/Sort.java new file mode 100644 index 0000000000..67bc8fac81 --- /dev/null +++ b/source/java/org/alfresco/web/data/Sort.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.data; + +import java.lang.reflect.Method; +import java.text.CollationKey; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.log4j.Logger; + +/** + * Sort + * + * Base sorting helper supports locale specific case sensitive, case in-sensitive and + * numeric data sorting. + * + * @author Kevin Roast + */ +public abstract class Sort +{ + // ------------------------------------------------------------------------------ + // Construction + + /** + * Constructor + * + * @param data a the List of String[] data to sort + * @param column the column getter method to use on the row to sort + * @param bForward true for a forward sort, false for a reverse sort + * @param mode sort mode to use (see IDataContainer constants) + */ + public Sort(List data, String column, boolean bForward, String mode) + { + this.data = data; + this.column = column; + this.bForward = bForward; + this.sortMode = mode; + + if (this.data.size() != 0) + { + // setup the Collator for our Locale + Collator collator = Collator.getInstance(Locale.getDefault()); + + // set the strength according to the sort mode + if (mode.equals(IDataContainer.SORT_CASEINSENSITIVE)) + { + collator.setStrength(Collator.SECONDARY); + } + else + { + collator.setStrength(Collator.IDENTICAL); + } + + this.keys = buildCollationKeys(collator); + } + } + + + // ------------------------------------------------------------------------------ + // Abstract Methods + + /** + * Runs the Sort routine on the current dataset + */ + public abstract void sort(); + + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Build a list of collation keys for comparing locale sensitive strings or build + * the appropriate objects for comparison for other standard data types. + * + * @param collator the Collator object to use to build String keys + */ + protected List buildCollationKeys(Collator collator) + { + List data = this.data; + int iSize = data.size(); + List keys = new ArrayList(iSize); + + try + { + // create the Bean getter method invoker to retrieve the value for a colunm + String methodName = getGetterMethodName(this.column); + Class returnType = null;; + Method getter = null; + // there will always be at least one item to sort if we get to this method + Object bean = this.data.get(0); + try + { + getter = bean.getClass().getMethod(methodName, (Class [])null); + returnType = getter.getReturnType(); + } + catch (NoSuchMethodException nsmerr) + { + // no bean getter method found - try Map implementation + if (bean instanceof Map) + { + Object obj = ((Map)bean).get(this.column); + if (obj != null) + { + returnType = obj.getClass(); + } + else + { + if (s_logger.isInfoEnabled()) + { + s_logger.info("Unable to get return type class for RichList column: " + column + + ". Suggest set java type directly in sort component tag."); + } + returnType = Object.class; + } + } + else + { + throw new IllegalStateException("Unable to find bean getter or Map impl for column name: " + this.column); + } + } + + // create appropriate comparator instance based on data type + // using the strategy pattern so sub-classes of Sort simply invoke the + // compare() method on the comparator interface - no type info required + boolean bknownType = true; + if (returnType.equals(String.class)) + { + if (strongStringCompare == true) + { + this.comparator = new StringComparator(); + } + else + { + this.comparator = new SimpleStringComparator(); + } + } + else if (returnType.equals(Date.class)) + { + this.comparator = new DateComparator(); + } + else if (returnType.equals(boolean.class) || returnType.equals(Boolean.class)) + { + this.comparator = new BooleanComparator(); + } + else if (returnType.equals(int.class) || returnType.equals(Integer.class)) + { + this.comparator = new IntegerComparator(); + } + else if (returnType.equals(long.class) || returnType.equals(Long.class)) + { + this.comparator = new LongComparator(); + } + else if (returnType.equals(float.class) || returnType.equals(Float.class)) + { + this.comparator = new FloatComparator(); + } + else + { + s_logger.warn("Unsupported sort data type: " + returnType + " defaulting to .toString()"); + this.comparator = new SimpleComparator(); + bknownType = false; + } + + // create a collation key for each required column item in the dataset + for (int iIndex=0; iIndex"); + + out.write(""); + + out.write(""); + + out.write(""); + } + + public static void generatePanelEnd(Writer out, String contextPath, String panel) + throws IOException + { + out.write(""); + + out.write(""); + + out.write(""); + + out.write(""); + } + + public static void generateTitledPanelMiddle(Writer out, String contextPath, String titlePanel, + String contentPanel, String contentBgColor) throws IOException + { + // generate the expanded part, just under the title + out.write(""); + + out.write(""); + + out.write(""); + + out.write(""); + + out.write(""); + } + + public final static String BGCOLOR_WHITE = "#FFFFFF"; +} diff --git a/source/java/org/alfresco/web/ui/common/SortableSelectItem.java b/source/java/org/alfresco/web/ui/common/SortableSelectItem.java new file mode 100644 index 0000000000..a5b3536fe3 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/SortableSelectItem.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common; + +import javax.faces.model.SelectItem; + +/** + * Wrapper class to facilitate case-insensitive sorting functionality against our SelectItem objects + * + * @author Kevin Roast + */ +public final class SortableSelectItem extends SelectItem implements Comparable +{ + public SortableSelectItem(String value, String label, String sort) + { + super(value, label); + this.sort = sort; + } + + public int compareTo(Object obj2) + { + if (this.sort == null && obj2 == null) return 0; + if (this.sort == null) return -1; + if (obj2 == null) return 1; + return this.sort.compareToIgnoreCase( ((SortableSelectItem)obj2).sort ); + } + + private String sort; +} diff --git a/source/java/org/alfresco/web/ui/common/Utils.java b/source/java/org/alfresco/web/ui/common/Utils.java new file mode 100644 index 0000000000..eb11553ecc --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/Utils.java @@ -0,0 +1,996 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common; + +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.application.FacesMessage; +import javax.faces.component.NamingContainer; +import javax.faces.component.UIComponent; +import javax.faces.component.UIForm; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.EvaluationException; +import javax.faces.el.MethodBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.CIFSServer; +import org.alfresco.filesys.server.filesys.DiskSharedDevice; +import org.alfresco.filesys.smb.server.repo.ContentContext; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.webdav.WebDAVServlet; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.DownloadContentServlet; +import org.alfresco.web.app.servlet.ExternalAccessServlet; +import org.alfresco.web.bean.NavigationBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.ui.common.component.UIStatusMessage; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.myfaces.renderkit.html.HtmlFormRendererBase; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Class containing misc helper methods used by the JSF components. + * + * @author Kevin Roast + */ +public final class Utils +{ + private static final String MSG_TIME_PATTERN = "time_pattern"; + private static final String MSG_DATE_PATTERN = "date_pattern"; + private static final String MSG_DATE_TIME_PATTERN = "date_time_pattern"; + + private static final String IMAGE_PREFIX16 = "/images/filetypes/"; + private static final String IMAGE_PREFIX32 = "/images/filetypes32/"; + private static final String IMAGE_POSTFIX = ".gif"; + private static final String DEFAULT_FILE_IMAGE16 = IMAGE_PREFIX16 + "_default" + IMAGE_POSTFIX; + private static final String DEFAULT_FILE_IMAGE32 = IMAGE_PREFIX32 + "_default" + IMAGE_POSTFIX; + + private static final Map s_fileExtensionMap = new HashMap(89, 1.0f); + + private static Log logger = LogFactory.getLog(Utils.class); + + /** + * Private constructor + */ + private Utils() + { + } + + /** + * Encodes the given string, so that it can be used within an HTML page. + * + * @param string the String to convert + */ + public static String encode(String string) + { + if (string == null) + { + return ""; + } + + StringBuilder sb = null; //create on demand + String enc; + char c; + for (int i = 0; i < string.length(); i++) + { + enc = null; + c = string.charAt(i); + switch (c) + { + case '"': enc = """; break; //" + case '&': enc = "&"; break; //& + case '<': enc = "<"; break; //< + case '>': enc = ">"; break; //> + + //german umlauts + case '\u00E4' : enc = "ä"; break; + case '\u00C4' : enc = "Ä"; break; + case '\u00F6' : enc = "ö"; break; + case '\u00D6' : enc = "Ö"; break; + case '\u00FC' : enc = "ü"; break; + case '\u00DC' : enc = "Ü"; break; + case '\u00DF' : enc = "ß"; break; + + //misc + //case 0x80: enc = "€"; break; sometimes euro symbol is ascii 128, should we suport it? + case '\u20AC': enc = "€"; break; + case '\u00AB': enc = "«"; break; + case '\u00BB': enc = "»"; break; + case '\u00A0': enc = " "; break; + + default: + if (((int)c) >= 0x80) + { + //encode all non basic latin characters + enc = "&#" + ((int)c) + ";"; + } + break; + } + + if (enc != null) + { + if (sb == null) + { + String soFar = string.substring(0, i); + sb = new StringBuilder(i + 8); + sb.append(soFar); + } + sb.append(enc); + } + else + { + if (sb != null) + { + sb.append(c); + } + } + } + + if (sb == null) + { + return string; + } + else + { + return sb.toString(); + } + } + + /** + * Crop a label within a SPAN element, using ellipses '...' at the end of label and + * and encode the result for HTML output. A SPAN will only be generated if the label + * is beyond the default setting of 32 characters in length. + * + * @param text to crop and encode + * + * @return encoded and cropped resulting label HTML + */ + public static String cropEncode(String text) + { + return cropEncode(text, 32); + } + + /** + * Crop a label within a SPAN element, using ellipses '...' at the end of label and + * and encode the result for HTML output. A SPAN will only be generated if the label + * is beyond the specified number of characters in length. + * + * @param text to crop and encode + * @param length length of string to crop too + * + * @return encoded and cropped resulting label HTML + */ + public static String cropEncode(String text, int length) + { + if (text.length() > length) + { + String label = text.substring(0, length - 3) + "..."; + StringBuilder buf = new StringBuilder(length + 32 + text.length()); + buf.append("") + .append(Utils.encode(label)) + .append(""); + return buf.toString(); + } + else + { + return Utils.encode(text); + } + } + + /** + * Replace one string instance with another within the specified string + * + * @param str + * @param repl + * @param with + * + * @return replaced string + */ + public static String replace(String str, String repl, String with) + { + int lastindex = 0; + int pos = str.indexOf(repl); + + // If no replacement needed, return the original string + // and save StringBuffer allocation/char copying + if (pos < 0) + { + return str; + } + + int len = repl.length(); + int lendiff = with.length() - repl.length(); + StringBuilder out = new StringBuilder((lendiff <= 0) ? str.length() : (str.length() + (lendiff << 3))); + for (; pos >= 0; pos = str.indexOf(repl, lastindex = pos + len)) + { + out.append(str.substring(lastindex, pos)).append(with); + } + + return out.append(str.substring(lastindex, str.length())).toString(); + } + + /** + * Remove all occurances of a String from a String + * + * @param str String to remove occurances from + * @param match The string to remove + * + * @return new String with occurances of the match removed + */ + public static String remove(String str, String match) + { + int lastindex = 0; + int pos = str.indexOf(match); + + // If no replacement needed, return the original string + // and save StringBuffer allocation/char copying + if (pos < 0) + { + return str; + } + + int len = match.length(); + StringBuilder out = new StringBuilder(str.length()); + for (; pos >= 0; pos = str.indexOf(match, lastindex = pos + len)) + { + out.append(str.substring(lastindex, pos)); + } + + return out.append(str.substring(lastindex, str.length())).toString(); + } + + /** + * Helper to output an attribute to the output stream + * + * @param out ResponseWriter + * @param attr attribute value object (cannot be null) + * @param mapping mapping to output as e.g. style="..." + * + * @throws IOException + */ + public static void outputAttribute(ResponseWriter out, Object attr, String mapping) + throws IOException + { + if (attr != null) + { + out.write(' '); + out.write(mapping); + out.write("=\""); + out.write(attr.toString()); + out.write('"'); + } + } + + /** + * Get the hidden field name for any action component. + * + * All components that wish to simply encode a form value with their client ID can reuse the same + * hidden field within the parent form. NOTE: components which use this method must only encode + * their client ID as the value and nothing else! + * + * Build a shared field name from the parent form name and the string "act". + * + * @return hidden field name shared by all action components within the Form. + */ + public static String getActionHiddenFieldName(FacesContext context, UIComponent component) + { + return Utils.getParentForm(context, component).getClientId(context) + NamingContainer.SEPARATOR_CHAR + "act"; + } + + /** + * Helper to recursively render a component and it's child components + * + * @param context FacesContext + * @param component UIComponent + * + * @throws IOException + */ + public static void encodeRecursive(FacesContext context, UIComponent component) + throws IOException + { + if (component.isRendered() == true) + { + component.encodeBegin(context); + + // follow the spec for components that render their children + if (component.getRendersChildren() == true) + { + component.encodeChildren(context); + } + else + { + if (component.getChildCount() != 0) + { + for (Iterator i=component.getChildren().iterator(); i.hasNext(); /**/) + { + encodeRecursive(context, (UIComponent)i.next()); + } + } + } + + component.encodeEnd(context); + } + } + + /** + * Generate the JavaScript to submit set the specified hidden Form field to the + * supplied value and submit the parent Form. + * + * NOTE: the supplied hidden field name is added to the Form Renderer map for output. + * + * @param context FacesContext + * @param component UIComponent to generate JavaScript for + * @param fieldId Hidden field id to set value for + * @param fieldValue Hidden field value to set hidden field too on submit + * + * @return JavaScript event code + */ + public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue) + { + return generateFormSubmit(context, component, fieldId, fieldValue, null); + } + + /** + * Generate the JavaScript to submit set the specified hidden Form field to the + * supplied value and submit the parent Form. + * + * NOTE: the supplied hidden field name is added to the Form Renderer map for output. + * + * @param context FacesContext + * @param component UIComponent to generate JavaScript for + * @param fieldId Hidden field id to set value for + * @param fieldValue Hidden field value to set hidden field too on submit + * @param params Optional map of param name/values to output + * + * @return JavaScript event code + */ + public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue, Map params) + { + UIForm form = Utils.getParentForm(context, component); + if (form == null) + { + throw new IllegalStateException("Must nest components inside UIForm to generate form submit!"); + } + + String formClientId = form.getClientId(context); + + StringBuilder buf = new StringBuilder(200); + buf.append("document.forms["); + buf.append("'"); + buf.append(formClientId); + buf.append("'"); + buf.append("]['"); + buf.append(fieldId); + buf.append("'].value='"); + buf.append(fieldValue); + buf.append("';"); + + if (params != null) + { + for (String name : params.keySet()) + { + buf.append("document.forms["); + buf.append("'"); + buf.append(formClientId); + buf.append("'"); + buf.append("]['"); + buf.append(name); + buf.append("'].value='"); + buf.append(params.get(name)); + buf.append("';"); + + // weak, but this seems to be the way Sun RI do it... + //FormRenderer.addNeededHiddenField(context, name); + HtmlFormRendererBase.addHiddenCommandParameter(form, name); + } + } + + buf.append("document.forms["); + buf.append("'"); + buf.append(formClientId); + buf.append("'"); + buf.append("].submit()"); + + buf.append(";return false;"); + + // weak, but this seems to be the way Sun RI do it... + //FormRenderer.addNeededHiddenField(context, fieldId); + HtmlFormRendererBase.addHiddenCommandParameter(form, fieldId); + + return buf.toString(); + } + + /** + * Generate the JavaScript to submit the parent Form. + * + * @param context FacesContext + * @param component UIComponent to generate JavaScript for + * + * @return JavaScript event code + */ + public static String generateFormSubmit(FacesContext context, UIComponent component) + { + UIForm form = Utils.getParentForm(context, component); + if (form == null) + { + throw new IllegalStateException("Must nest components inside UIForm to generate form submit!"); + } + + String formClientId = form.getClientId(context); + + StringBuilder buf = new StringBuilder(48); + + buf.append("document.forms["); + buf.append("'"); + buf.append(formClientId); + buf.append("'"); + buf.append("].submit()"); + + buf.append(";return false;"); + + return buf.toString(); + } + + /** + * Enum representing the client URL type to generate + */ + public enum URLMode {HTTP_DOWNLOAD, HTTP_INLINE, WEBDAV, CIFS, SHOW_DETAILS, FTP} + + /** + * Generates a URL for the given usage for the given node. + * + * The supported values for the usage parameter are of URLMode enum type + * @see URLMode + * + * @param context Faces context + * @param node The node to generate the URL for + * @param usage What the URL is going to be used for + * @return The URL for the requested usage without the context path + */ + public static String generateURL(FacesContext context, Node node, URLMode usage) + { + String url = null; + + switch (usage) + { + case WEBDAV: + { + // calculate a WebDAV URL for the given node + FileFolderService fileFolderService = Repository.getServiceRegistry( + context).getFileFolderService(); + try + { + List paths = fileFolderService.getNamePath(null, node.getNodeRef()); + User user = Application.getCurrentUser(context); + + // build up the webdav url + StringBuilder path = new StringBuilder("/").append(WebDAVServlet.WEBDAV_PREFIX); + + // build up the path skipping the first path as it is the root folder + boolean homeSpaceFound = false; + for (int x = 1; x < paths.size(); x++) + { + path.append("/").append(paths.get(x).getName()); + } + + url = path.toString(); + } + catch (Exception e) + { + if (logger.isWarnEnabled()) + logger.warn("Failed to calculate webdav url", e); + } + break; + } + + case CIFS: + { + // calculate a CIFS path for the given node + + // get hold of the node service, cifsServer and navigation bean + NodeService nodeService = Repository.getServiceRegistry(context).getNodeService(); + NavigationBean navBean = (NavigationBean)context.getExternalContext(). + getSessionMap().get("NavigationBean"); + CIFSServer cifsServer = (CIFSServer)FacesContextUtils.getRequiredWebApplicationContext( + context).getBean("cifsServer"); + + if (nodeService != null && navBean != null && cifsServer != null) + { + DiskSharedDevice diskShare = cifsServer.getConfiguration().getPrimaryFilesystem(); + + if (diskShare != null) + { + ContentContext contentCtx = (ContentContext) diskShare.getContext(); + NodeRef rootNode = contentCtx.getRootNode(); + Path path = nodeService.getPath(node.getNodeRef()); + url = Repository.getNamePath(nodeService, path, rootNode, "\\", + "file:///" + navBean.getCIFSServerPath(diskShare)); + } + } + break; + } + + case HTTP_DOWNLOAD: + { + url = DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName()); + break; + } + + case HTTP_INLINE: + { + url = DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName()); + break; + } + + case SHOW_DETAILS: + { + DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService(); + + // default to showing details of content + String outcome = "showDocDetails"; + + // if the node is a type of folder then make the outcome to show space details + if (dd.isSubClass(node.getType(), ContentModel.TYPE_FOLDER)) + { + outcome = "showSpaceDetails"; + } + + // build the url + url = ExternalAccessServlet.generateExternalURL(outcome, + Repository.getStoreRef().getProtocol() + "/" + + Repository.getStoreRef().getIdentifier() + "/" + node.getId()); + break; + } + + case FTP: + { + // not implemented yet! + break; + } + } + + return url; + } + + /** + * Build a context path safe image tag for the supplied image path. + * Image path should be supplied with a leading slash '/'. + * + * @param context FacesContext + * @param image The local image path from the web folder with leading slash '/' + * @param width Width in pixels + * @param height Height in pixels + * @param alt Optional alt/title text + * @param onclick JavaScript onclick event handler code + * + * @return Populated img tag + */ + public static String buildImageTag(FacesContext context, String image, int width, int height, String alt, String onclick) + { + return buildImageTag(context, image, width, height, alt, onclick, null); + } + + /** + * Build a context path safe image tag for the supplied image path. + * Image path should be supplied with a leading slash '/'. + * + * @param context FacesContext + * @param image The local image path from the web folder with leading slash '/' + * @param width Width in pixels + * @param height Height in pixels + * @param alt Optional alt/title text + * @param onclick JavaScript onclick event handler code + * @param align Optional HTML alignment value + * + * @return Populated img tag + */ + public static String buildImageTag(FacesContext context, String image, int width, int height, String alt, String onclick, String align) + { + StringBuilder buf = new StringBuilder(200); + + buf.append("Utils.encode(alt);'); + + return buf.toString(); + } + + /** + * Build a context path safe image tag for the supplied image path. + * Image path should be supplied with a leading slash '/'. + * + * @param context FacesContext + * @param image The local image path from the web folder with leading slash '/' + * @param width Width in pixels + * @param height Height in pixels + * @param alt Optional alt/title text + * + * @return Populated img tag + */ + public static String buildImageTag(FacesContext context, String image, int width, int height, String alt) + { + return buildImageTag(context, image, width, height, alt, null); + } + + /** + * Build a context path safe image tag for the supplied image path. + * Image path should be supplied with a leading slash '/'. + * + * @param context FacesContext + * @param image The local image path from the web folder with leading slash '/' + * @param alt Optional alt/title text + * + * @return Populated img tag + */ + public static String buildImageTag(FacesContext context, String image, String alt) + { + return buildImageTag(context, image, alt, null); + } + + /** + * Build a context path safe image tag for the supplied image path. + * Image path should be supplied with a leading slash '/'. + * + * @param context FacesContext + * @param image The local image path from the web folder with leading slash '/' + * @param alt Optional alt/title text + * @param align Optional HTML alignment value + * + * @return Populated img tag + */ + public static String buildImageTag(FacesContext context, String image, String alt, String align) + { + StringBuilder buf = new StringBuilder(128); + + buf.append("Utils.encode(alt); extIndex + 1) + { + String ext = name.substring(extIndex + 1).toLowerCase(); + String key = ext + ' ' + (small ? "16" : "32"); + + // found file extension for appropriate size image + synchronized (s_fileExtensionMap) + { + image = s_fileExtensionMap.get(key); + if (image == null) + { + // not found create for first time + image = (small ? IMAGE_PREFIX16 : IMAGE_PREFIX32) + ext + IMAGE_POSTFIX; + + // does this image exist on the web-server? + if (FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(image) != null) + { + // found the image for this extension - save it for later + s_fileExtensionMap.put(key, image); + } + else + { + // not found, save the default image for this extension instead + image = (small ? DEFAULT_FILE_IMAGE16 : DEFAULT_FILE_IMAGE32); + s_fileExtensionMap.put(key, image); + } + } + } + } + + return image; + } +} diff --git a/source/java/org/alfresco/web/ui/common/WebResources.java b/source/java/org/alfresco/web/ui/common/WebResources.java new file mode 100644 index 0000000000..7cf4318b66 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/WebResources.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common; + +/** + * Class containing well known web resource paths for images etc. + * + * @author Kevin Roast + */ +public class WebResources +{ + // Image paths + public static final String IMAGE_PREVIOUSPAGE_NONE = "/images/icons/PreviousPage_unavailable.gif"; + public static final String IMAGE_PREVIOUSPAGE = "/images/icons/PreviousPage.gif"; + public static final String IMAGE_FIRSTPAGE_NONE = "/images/icons/FirstPage_unavailable.gif"; + public static final String IMAGE_FIRSTPAGE = "/images/icons/FirstPage.gif"; + public static final String IMAGE_NEXTPAGE_NONE = "/images/icons/NextPage_unavailable.gif"; + public static final String IMAGE_NEXTPAGE = "/images/icons/NextPage.gif"; + public static final String IMAGE_LASTPAGE_NONE = "/images/icons/LastPage_unavailable.gif"; + public static final String IMAGE_LASTPAGE = "/images/icons/LastPage.gif"; + + public static final String IMAGE_SORTUP = "/images/icons/sort_up.gif"; + public static final String IMAGE_SORTDOWN = "/images/icons/sort_down.gif"; + public static final String IMAGE_SORTNONE = "/images/icons/sort_flat.gif"; + + public static final String IMAGE_EXPANDED = "/images/icons/expanded.gif"; + public static final String IMAGE_COLLAPSED = "/images/icons/collapsed.gif"; + + public static final String IMAGE_MOVELEFT = "/images/icons/move_left.gif"; + public static final String IMAGE_MOVERIGHT = "/images/icons/move_right.gif"; + public static final String IMAGE_GO_UP = "/images/icons/up.gif"; + + public static final String IMAGE_INFO = "/images/icons/info_icon.gif"; +} diff --git a/source/java/org/alfresco/web/ui/common/component/IBreadcrumbHandler.java b/source/java/org/alfresco/web/ui/common/component/IBreadcrumbHandler.java new file mode 100644 index 0000000000..deceeb7c36 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/IBreadcrumbHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.Serializable; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author Kevin Roast + */ +public interface IBreadcrumbHandler extends Serializable +{ + /** + * Override Object.toString() + * + * @return the element display label for this handler instance. + */ + public String toString(); + + /** + * Perform appropriate processing logic and then return a JSF navigation outcome. + * This method will be called by the framework when the handler instance is selected by the user. + * + * @param breadcrumb The UIBreadcrumb component that caused the navigation + * + * @return JSF navigation outcome + */ + public String navigationOutcome(UIBreadcrumb breadcrumb); +} diff --git a/source/java/org/alfresco/web/ui/common/component/SelfRenderingComponent.java b/source/java/org/alfresco/web/ui/common/component/SelfRenderingComponent.java new file mode 100644 index 0000000000..22864b00c4 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/SelfRenderingComponent.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.IOException; + +import javax.faces.component.UIComponentBase; +import javax.faces.context.ResponseWriter; + + +/** + * @author kevinr + */ +public abstract class SelfRenderingComponent extends UIComponentBase +{ + /** + * Default Constructor + */ + public SelfRenderingComponent() + { + // specifically set the renderer type to null to indicate to the framework + // that this component renders itself - there is no abstract renderer class + setRendererType(null); + } + + /** + * Helper to output an attribute to the output stream + * + * @param out ResponseWriter + * @param attr attribute value object (cannot be null) + * @param mapping mapping to output as e.g. style="..." + * + * @throws IOException + */ + protected static void outputAttribute(ResponseWriter out, Object attr, String mapping) + throws IOException + { + if (attr != null) + { + out.write(' '); + out.write(mapping); + out.write("=\""); + out.write(attr.toString()); + out.write('"'); + } + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIActionLink.java b/source/java/org/alfresco/web/ui/common/component/UIActionLink.java new file mode 100644 index 0000000000..0bb6889ff2 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIActionLink.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.util.HashMap; +import java.util.Map; + +import javax.faces.component.UICommand; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +/** + * @author kevinr + */ +public class UIActionLink extends UICommand +{ + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public UIActionLink() + { + setRendererType("org.alfresco.faces.ActionLinkRenderer"); + } + + + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Controls"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.padding = (Integer)values[1]; + this.image = (String)values[2]; + this.showLink = (Boolean)values[3]; + this.params = (Map)values[4]; + this.href = (String)values[5]; + this.tooltip = (String)values[6]; + this.target = (String)values[7]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[8]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.padding; + values[2] = this.image; + values[3] = this.showLink; + values[4] = this.params; + values[5] = this.href; + values[6] = this.tooltip; + values[7] = this.target; + return (values); + } + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Return the current child parameter map for this action link instance. + * This map is filled with name/value pairs from any child UIParameter components. + * + * @return Map of name/value pairs + */ + public Map getParameterMap() + { + if (this.params == null) + { + this.params = new HashMap(3, 1.0f); + } + return this.params; + } + + /** + * Get whether to show the link as well as the image if specified + * + * @return true to show the link as well as the image if specified + */ + public boolean getShowLink() + { + ValueBinding vb = getValueBinding("showLink"); + if (vb != null) + { + this.showLink = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.showLink != null) + { + return this.showLink.booleanValue(); + } + else + { + // return default + return true; + } + } + + /** + * Set whether to show the link as well as the image if specified + * + * @param showLink Whether to show the link as well as the image if specified + */ + public void setShowLink(boolean showLink) + { + this.showLink = Boolean.valueOf(showLink); + } + + /** + * Get the padding value for rendering this component in a table. + * + * @return the padding in pixels, if set != 0 then a table will be rendering around the items + */ + public int getPadding() + { + ValueBinding vb = getValueBinding("padding"); + if (vb != null) + { + this.padding = (Integer)vb.getValue(getFacesContext()); + } + + if (this.padding != null) + { + return this.padding.intValue(); + } + else + { + // return default + return 0; + } + } + + /** + * Set the padding value for rendering this component in a table. + * + * @param padding value in pixels, if set != 0 then a table will be rendering around the items + */ + public void setPadding(int padding) + { + this.padding = padding; + } + + /** + * Return the Image path to use for this actionlink. + * If an image is specified, it is shown in additon to the value text unless + * the 'showLink' property is set to 'false'. + * + * @return the image path to display + */ + public String getImage() + { + ValueBinding vb = getValueBinding("image"); + if (vb != null) + { + this.image = (String)vb.getValue(getFacesContext()); + } + + return this.image; + } + + /** + * Set the Image path to use for this actionlink. + * If an image is specified, it is shown in additon to the value text unless + * the 'showLink' property is set to 'false'. + * + * @param image Image path to display + */ + public void setImage(String image) + { + this.image = image; + } + + /** + * @return Returns the href. + */ + public String getHref() + { + ValueBinding vb = getValueBinding("href"); + if (vb != null) + { + this.href = (String)vb.getValue(getFacesContext()); + } + + return this.href; + } + + /** + * @param href The href to set. + */ + public void setHref(String href) + { + this.href = href; + } + + /** + * Get the tooltip title text + * + * @return the tooltip + */ + public String getTooltip() + { + ValueBinding vb = getValueBinding("tooltip"); + if (vb != null) + { + this.tooltip = (String)vb.getValue(getFacesContext()); + } + + return this.tooltip; + } + + /** + * Set the tooltip title text + * + * @param tooltip the tooltip + */ + public void setTooltip(String tooltip) + { + this.tooltip = tooltip; + } + + /** + * Get the target + * + * @return the target + */ + public String getTarget() + { + ValueBinding vb = getValueBinding("target"); + if (vb != null) + { + this.target = (String)vb.getValue(getFacesContext()); + } + + return this.target; + } + + /** + * Set the target + * + * @param target the target + */ + public void setTarget(String target) + { + this.target = target; + } + + /** + * Returns the onclick handler + * + * @return The onclick handler + */ + public String getOnclick() + { + ValueBinding vb = getValueBinding("onclick"); + if (vb != null) + { + this.onclick = (String)vb.getValue(getFacesContext()); + } + + return this.onclick; + } + + /** + * Sets the onclick handler + * + * @param onclick The onclick handler + */ + public void setOnclick(String onclick) + { + this.onclick = onclick; + } + + // ------------------------------------------------------------------------------ + // Private data + + /** the padding value in pixels, if set != 0 then a table will be rendered around the items */ + private Integer padding = null; + + /** True to show the link as well as the image if specified */ + private Boolean showLink = null; + + /** If an image is specified, it is shown in additon to the value text */ + private String image = null; + + /** static href to use instead of an action/actionlistener */ + private String href = null; + + /** tooltip title text to display on the action link */ + private String tooltip = null; + + /** the target reference */ + private String target = null; + + /** the onclick handler */ + private String onclick = null; + + /** Map of child param name/values pairs */ + private Map params = null; +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIBreadcrumb.java b/source/java/org/alfresco/web/ui/common/component/UIBreadcrumb.java new file mode 100644 index 0000000000..c3c3df75b5 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIBreadcrumb.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +/** + * @author kevinr + */ +public class UIBreadcrumb extends UICommand +{ + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public UIBreadcrumb() + { + setRendererType("org.alfresco.faces.BreadcrumbRenderer"); + } + + + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Controls"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.separator = (String)values[1]; + this.showRoot = (Boolean)values[2]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[3]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.separator; + values[2] = this.showRoot; + return (values); + } + + /** + * @see javax.faces.component.UICommand#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof BreadcrumbEvent) + { + setSelectedPathIndex( ((BreadcrumbEvent)event).SelectedIndex ); + } + + // default ActionEvent processing for a UICommand + super.broadcast(event); + } + + /** + * Set the selected path index. This modifies the current path value. + * + * @return last selected path index + */ + public void setSelectedPathIndex(int index) + { + // getValue() will return a List of IBreadcrumbHandler (see impl below) + List elements = (List)getValue(); + + if (elements.size() >= index) + { + // copy path elements up to the selected index to a new List + List path = new ArrayList(index + 1); + path.addAll(elements.subList(0, index + 1)); + + // set the new List as our new path value + setValue(path); + + // call the app logic for the element handler and perform any required navigation + String outcome = path.get(index).navigationOutcome(this); + if (outcome != null) + { + String viewId = getFacesContext().getViewRoot().getViewId(); + getFacesContext().getApplication().getNavigationHandler().handleNavigation( + getFacesContext(), viewId, outcome); + } + } + } + + /** + * Override getValue() to deal with converting a String path into a valid List of IBreadcrumbHandler + */ + public Object getValue() + { + List elements = null; + + Object value = super.getValue(); + if (value instanceof String) + { + elements = new ArrayList(8); + // found a String based path - convert to List of IBreadcrumbHandler instances + StringTokenizer t = new StringTokenizer((String)value, SEPARATOR); + while (t.hasMoreTokens() == true) + { + IBreadcrumbHandler handler = new DefaultPathHandler(t.nextToken()); + elements.add(handler); + } + + // save result so we don't need to repeat the conversion + setValue(elements); + } + else if (value instanceof List) + { + elements = (List)value; + } + else if (value != null) + { + throw new IllegalArgumentException("UIBreadcrumb value must be a String path or List of IBreadcrumbHandler!"); + } + else + { + elements = new ArrayList(8); + } + + return elements; + } + + /** + * Append a handler object to the current breadcrumb structure + * + * @param handler The IBreadcrumbHandler to append + */ + public void appendHandler(IBreadcrumbHandler handler) + { + if (handler == null) + { + throw new NullPointerException("IBreadcrumbHandler instance cannot be null!"); + } + + List elements = (List)getValue(); + elements.add(handler); + } + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Get the separator string to output between each breadcrumb element + * + * @return separator string + */ + public String getSeparator() + { + ValueBinding vb = getValueBinding("separator"); + if (vb != null) + { + this.separator = (String)vb.getValue(getFacesContext()); + } + + return this.separator; + } + + /** + * Set separator + * + * @param separator the separator string to output between each breadcrumb element + */ + public void setSeparator(String separator) + { + this.separator = separator; + } + + /** + * Get whether to show the root of the path + * + * @return true to show the root of the path, false to hide it + */ + public boolean getShowRoot() + { + ValueBinding vb = getValueBinding("showRoot"); + if (vb != null) + { + this.showRoot = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.showRoot != null) + { + return this.showRoot.booleanValue(); + } + else + { + // return default + return true; + } + } + + /** + * Set whether to show the root of the path + * + * @param showRoot Whether to show the root of the path + */ + public void setShowRoot(boolean showRoot) + { + this.showRoot = Boolean.valueOf(showRoot); + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the clicking of a breadcrumb element. + */ + public static class BreadcrumbEvent extends ActionEvent + { + public BreadcrumbEvent(UIComponent component, int selectedIndex) + { + super(component); + SelectedIndex = selectedIndex; + } + + public int SelectedIndex = 0; + } + + /** + * Class representing a handler for the default String path based breadcrumb + */ + private static class DefaultPathHandler implements IBreadcrumbHandler + { + /** + * Constructor + * + * @param label The element display label + */ + public DefaultPathHandler(String label) + { + this.label = label; + } + + /** + * Return the element display label + */ + public String toString() + { + return this.label; + } + + /** + * @see org.alfresco.web.ui.common.component.IBreadcrumbHandler#navigationOutcome(org.alfresco.web.ui.common.component.UIBreadcrumb) + */ + public String navigationOutcome(UIBreadcrumb breadcrumb) + { + // no outcome for the default handler - return to current page + return null; + } + + private String label; + } + + + // ------------------------------------------------------------------------------ + // Private data + + /** visible separator value */ + private String separator = null; + + /** true to show the root of the breadcrumb path, false otherwise */ + private Boolean showRoot = null; + + /** the separator for a breadcrumb path value */ + public final static String SEPARATOR = "/"; +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIGenericPicker.java b/source/java/org/alfresco/web/ui/common/component/UIGenericPicker.java new file mode 100644 index 0000000000..7abf042253 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIGenericPicker.java @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.MethodBinding; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; +import javax.faces.model.SelectItem; + +import org.alfresco.web.app.Application; +import org.alfresco.web.ui.common.Utils; + +/** + * @author Kevin Roast + */ +public class UIGenericPicker extends UICommand +{ + /** action ids */ + private final static int ACTION_NONE = -1; + private final static int ACTION_SEARCH = 0; + private final static int ACTION_CLEAR = 1; + private final static int ACTION_FILTER = 2; + private final static int ACTION_ADD = 3; + + /** form field postfixes */ + private final static String FIELD_FILTER = "_filter"; + private final static String FIELD_CONTAINS = "_contains"; + private final static String FIELD_RESULTS = "_results"; + + /** I18N message strings */ + private final static String MSG_SEARCH = "search"; + private final static String MSG_CLEAR = "clear"; + private final static String MSG_ADD = "add"; + private final static String MSG_RESULTS1 = "results_contains"; + private final static String MSG_RESULTS2 = "results_contains_filter"; + + private final static int DEFAULT_HEIGHT = 100; + private final static int DEFAULT_WIDTH = 250; + + private MethodBinding queryCallback = null; + private Boolean showFilter = null; + private Boolean showContains = null; + private Boolean showAddButton = null; + private Boolean filterRefresh = null; + private String addButtonLabel; + private Integer width = null; + private Integer height = null; + + private SelectItem[] filters = null; + private int filterIndex = 0; + private String contains = ""; + private String[] selectedResults = null; + private SelectItem[] currentResults = null; + + + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.GenericPicker"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + showFilter = (Boolean)values[1]; + showContains = (Boolean)values[2]; + showAddButton = (Boolean)values[3]; + addButtonLabel = (String)values[4]; + width = (Integer)values[5]; + height = (Integer)values[6]; + filterIndex = (Integer)values[7]; + contains = (String)values[8]; + queryCallback = (MethodBinding)values[9]; + selectedResults = (String[])values[10]; + currentResults = (SelectItem[])values[11]; + filters = (SelectItem[])values[12]; + filterRefresh = (Boolean)values[13]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[14]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = showFilter; + values[2] = showContains; + values[3] = showAddButton; + values[4] = addButtonLabel; + values[5] = width; + values[6] = height; + values[7] = filterIndex; + values[8] = contains; + values[9] = queryCallback; + values[10] = selectedResults; + values[11] = currentResults; + values[12] = filters; + values[13] = filterRefresh; + return (values); + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + Map valuesMap = context.getExternalContext().getRequestParameterValuesMap(); + String fieldId = getHiddenFieldName(); + String value = (String)requestMap.get(fieldId); + + int action = ACTION_NONE; + if (value != null && value.length() != 0) + { + // decode the values - we are expecting an action identifier + action = Integer.parseInt(value); + } + + // we always process these values to keep the component up-to-date + + // now find the Filter drop-down value + int filterIndex = 0; + String strFilterIndex = (String)requestMap.get(fieldId + FIELD_FILTER); + if (strFilterIndex != null && strFilterIndex.length() != 0) + { + filterIndex = Integer.parseInt(strFilterIndex); + } + + // and the Contains text box value + String contains = (String)requestMap.get(fieldId + FIELD_CONTAINS); + + // and the Results selections + String[] results = (String[])valuesMap.get(fieldId + FIELD_RESULTS); + + // queue an event + PickerEvent event = new PickerEvent(this, action, filterIndex, contains, results); + queueEvent(event); + } + + /** + * @see javax.faces.component.UIComponentBase#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof PickerEvent) + { + PickerEvent pickerEvent = (PickerEvent)event; + + // set component state from event properties + this.filterIndex = pickerEvent.FilterIndex; + this.contains = pickerEvent.Contains; + this.selectedResults = pickerEvent.Results; + + // delegate to appropriate action logic + switch (pickerEvent.Action) + { + case ACTION_ADD: + // call super for actionlistener execution + // it's up to the handler to get the results from the getSelectedResults() method + super.broadcast(event); + break; + + case ACTION_CLEAR: + this.contains = ""; + this.filterIndex = 0; + this.selectedResults = null; + this.currentResults = null; + break; + + case ACTION_FILTER: + // filter changed then query with new settings + case ACTION_SEARCH: + // query with current settings + MethodBinding callback = getQueryCallback(); + if (callback != null) + { + // use reflection to execute the query callback method and retrieve results + Object result = callback.invoke(getFacesContext(), new Object[] { + this.filterIndex, this.contains}); + + if (result instanceof SelectItem[]) + { + this.currentResults = (SelectItem[])result; + } + else + { + this.currentResults = null; + } + } + break; + } + } + else + { + super.broadcast(event); + } + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + ResourceBundle bundle = Application.getBundle(context); + + String clientId = getClientId(context); + + // start outer table + out.write(""); + + // top row + out.write(""); + + // information row + if (this.currentResults != null && getShowContains() == true) + { + out.write(""); + } + + // results list row + out.write(""); + + // bottom row - add button + if (getShowAddButton() == true) + { + out.write(""); + } + + // end outer table + out.write("
"); + + // filter drop-down + if (getShowFilter() == true) + { + out.write(""); + } + out.write(""); + + // Contains textbox + if (getShowContains() == true) + { + out.write(" "); + } + + // Search button + out.write(""); + out.write("
"); + String resultsMsg; + if (getShowFilter() == false) + { + resultsMsg = MessageFormat.format(bundle.getString(MSG_RESULTS1), new Object[] {this.contains}); + } + else + { + String filterMsg = this.filters[this.filterIndex].getLabel(); + resultsMsg = MessageFormat.format(bundle.getString(MSG_RESULTS2), new Object[] {this.contains, filterMsg}); + } + out.write(resultsMsg); + out.write(" "); + out.write(""); + out.write(Utils.encode(bundle.getString(MSG_CLEAR))); + out.write("
"); + out.write(""); + out.write("
"); + out.write(""); + out.write("
"); + } + + /** + * @return the filter options + */ + public SelectItem[] getFilterOptions() + { + if (this.filters == null) + { + ValueBinding vb = (ValueBinding)getValueBinding("filters"); + if (vb != null) + { + this.filters = (SelectItem[])vb.getValue(getFacesContext()); + } + } + + return this.filters; + } + + /** + * @return current filter drop-down selected index value + */ + public int getFilterIndex() + { + return this.filterIndex; + } + + /** + * @return Returns the addButtonLabel. + */ + public String getAddButtonLabel() + { + ValueBinding vb = getValueBinding("addButtonLabel"); + if (vb != null) + { + this.addButtonLabel = (String)vb.getValue(getFacesContext()); + } + + return this.addButtonLabel; + } + + /** + * @param addButtonLabel The addButtonLabel to set. + */ + public void setAddButtonLabel(String addButtonLabel) + { + this.addButtonLabel = addButtonLabel; + } + + /** + * @return Returns the showAddButton. + */ + public boolean getShowAddButton() + { + ValueBinding vb = getValueBinding("showAddButton"); + if (vb != null) + { + this.showAddButton = (Boolean)vb.getValue(getFacesContext()); + } + + return showAddButton != null ? showAddButton.booleanValue() : true; + } + + /** + * @param showAddButton The showAddButton to set. + */ + public void setShowAddButton(boolean showAddButton) + { + this.showAddButton = Boolean.valueOf(showAddButton); + } + + /** + * @return Returns the showContains. + */ + public boolean getShowContains() + { + ValueBinding vb = getValueBinding("showContains"); + if (vb != null) + { + this.showContains = (Boolean)vb.getValue(getFacesContext()); + } + + return showContains != null ? showContains.booleanValue() : true; + } + + /** + * @param showContains The showContains to set. + */ + public void setShowContains(boolean showContains) + { + this.showContains = Boolean.valueOf(showContains); + } + + /** + * @return Returns the showFilter. + */ + public boolean getShowFilter() + { + ValueBinding vb = getValueBinding("showFilter"); + if (vb != null) + { + this.showFilter = (Boolean)vb.getValue(getFacesContext()); + } + + return showFilter != null ? showFilter.booleanValue() : true; + } + + /** + * @param showFilter The showFilter to set. + */ + public void setShowFilter(boolean showFilter) + { + this.showFilter = Boolean.valueOf(showFilter); + } + + /** + * @return Returns the filterRefresh. + */ + public boolean getFilterRefresh() + { + ValueBinding vb = getValueBinding("filterRefresh"); + if (vb != null) + { + this.filterRefresh = (Boolean)vb.getValue(getFacesContext()); + } + + return filterRefresh != null ? filterRefresh.booleanValue() : false; + } + + /** + * @param filterRefresh The filterRefresh to set. + */ + public void setFilterRefresh(boolean filterRefresh) + { + this.filterRefresh = Boolean.valueOf(filterRefresh); + } + + /** + * @return Returns the width. + */ + public int getWidth() + { + ValueBinding vb = getValueBinding("width"); + if (vb != null) + { + this.width = (Integer)vb.getValue(getFacesContext()); + } + + return width != null ? width.intValue() : DEFAULT_WIDTH; + } + + /** + * @param width The width to set. + */ + public void setWidth(int width) + { + this.width = Integer.valueOf(width); + } + + /** + * @return Returns the height. + */ + public int getHeight() + { + ValueBinding vb = getValueBinding("height"); + if (vb != null) + { + this.height = (Integer)vb.getValue(getFacesContext()); + } + + return height != null ? height.intValue() : DEFAULT_HEIGHT; + } + + /** + * @param height The height to set. + */ + public void setHeight(int height) + { + this.height = Integer.valueOf(height); + } + + /** + * @return Returns the queryCallback. + */ + public MethodBinding getQueryCallback() + { + return this.queryCallback; + } + + /** + * @param binding The queryCallback MethodBinding to set. + */ + public void setQueryCallback(MethodBinding binding) + { + this.queryCallback = binding; + } + + /** + * @return The selected results. An array of whatever string objects were attached to the + * SelectItem[] objects supplied as the result of the picker query. + */ + public String[] getSelectedResults() + { + return this.selectedResults; + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * We use a hidden field per picker instance on the page. + * + * @return hidden field name + */ + private String getHiddenFieldName() + { + return getClientId(getFacesContext()); + } + + /** + * Generate FORM submit JavaScript for the specified action + * + * @param context FacesContext + * @param action Action index + * + * @return FORM submit JavaScript + */ + private String generateFormSubmit(FacesContext context, int action) + { + return Utils.generateFormSubmit(context, this, getHiddenFieldName(), Integer.toString(action)); + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the an action relevant to the Generic Selector component. + */ + public static class PickerEvent extends ActionEvent + { + public PickerEvent(UIComponent component, int action, int filterIndex, String contains, String[] results) + { + super(component); + Action = action; + FilterIndex = filterIndex; + Contains = contains; + Results = results; + } + + public int Action; + public int FilterIndex; + public String Contains; + public String[] Results; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIImagePicker.java b/source/java/org/alfresco/web/ui/common/component/UIImagePicker.java new file mode 100644 index 0000000000..c6864714e6 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIImagePicker.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import javax.faces.component.UIInput; +import javax.faces.context.FacesContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Component to represent a selectable list of images + * + * @author gavinc + */ +public class UIImagePicker extends UIInput +{ + private static Log logger = LogFactory.getLog(UIImagePicker.class); + + /** + * Default constructor + */ + public UIImagePicker() + { + // set the default renderer for an image picker component + setRendererType("org.alfresco.faces.Radio"); + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.ImagePicker"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[1]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + return (values); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIListItem.java b/source/java/org/alfresco/web/ui/common/component/UIListItem.java new file mode 100644 index 0000000000..fb4bbe1a13 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIListItem.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +/** + * @author kevinr + */ +public class UIListItem extends SelfRenderingComponent +{ + // ------------------------------------------------------------------------------ + // Component Impl + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Controls"; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[5]; + values[0] = super.saveState(context); + values[1] = this.value; + values[2] = this.disabled; + values[3] = this.label; + values[4] = this.tooltip; + return ((Object) (values)); + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + super.restoreState(context, values[0]); + this.value = values[1]; + this.disabled = (Boolean)values[2]; + this.label = (String)values[3]; + this.tooltip = (String)values[4]; + } + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Get the value - the value is used in a equals() match against the current value in the + * parent ModeList component to set the selected item. + * + * @return the value + */ + public Object getValue() + { + ValueBinding vb = getValueBinding("value"); + if (vb != null) + { + this.value = vb.getValue(getFacesContext()); + } + + return this.value; + } + + /** + * Set the value - the value is used in a equals() match against the current value in the + * parent ModeList component to set the selected item. + * + * @param value the value + */ + public void setValue(Object value) + { + this.value = value; + } + + /** + * Returns the disabled flag + * + * @return true if the mode list is disabled + */ + public boolean isDisabled() + { + ValueBinding vb = getValueBinding("disabled"); + if (vb != null) + { + this.disabled = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.disabled != null) + { + return this.disabled.booleanValue(); + } + else + { + // return the default + return false; + } + } + + /** + * Sets whether the mode list is disabled + * + * @param disabled the disabled flag + */ + public void setDisabled(boolean disabled) + { + this.disabled = disabled; + } + + /** + * @return Returns the label. + */ + public String getLabel() + { + ValueBinding vb = getValueBinding("label"); + if (vb != null) + { + this.label = (String)vb.getValue(getFacesContext()); + } + + return this.label; + } + + /** + * @param label The label to set. + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * @return Returns the tooltip. + */ + public String getTooltip() + { + ValueBinding vb = getValueBinding("tooltip"); + if (vb != null) + { + this.tooltip = (String)vb.getValue(getFacesContext()); + } + + return this.tooltip; + } + + /** + * @param tooltip The tooltip to set. + */ + public void setTooltip(String tooltip) + { + this.tooltip = tooltip; + } + + + // ------------------------------------------------------------------------------ + // Private data + + /** the component value */ + private Object value = null; + + /** disabled flag */ + private Boolean disabled = null; + + /** the tooltip */ + private String tooltip; + + /** the label */ + private String label; +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIListItems.java b/source/java/org/alfresco/web/ui/common/component/UIListItems.java new file mode 100644 index 0000000000..a4fd4b4c45 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIListItems.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +/** + * Allows a group of UIListItem objects to be represented. + * + * @author gavinc + */ +public class UIListItems extends SelfRenderingComponent +{ + private Object value; + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.ListItems"; + } + + /** + * @return Returns the object holding the decriptions + */ + public Object getValue() + { + if (this.value == null) + { + ValueBinding vb = getValueBinding("value"); + if (vb != null) + { + this.value = vb.getValue(getFacesContext()); + } + } + + return this.value; + } + + /** + * @param value Sets the object holding the description + */ + public void setValue(Object value) + { + this.value = value; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.value = values[1]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.value; + return (values); + } +} + diff --git a/source/java/org/alfresco/web/ui/common/component/UIMenu.java b/source/java/org/alfresco/web/ui/common/component/UIMenu.java new file mode 100644 index 0000000000..881a63b6fe --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIMenu.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.IOException; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; + +import org.alfresco.web.ui.common.Utils; + +/** + * @author Kevin Roast + */ +public class UIMenu extends SelfRenderingComponent +{ + // ------------------------------------------------------------------------------ + // Component Impl + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Controls"; + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + // output a textual label with an optional icon to show the menu + String menuId = getNextMenuId(context); + out.write("'); + + // output label text + String label = getLabel(); + if (label != null) + { + out.write(""); + out.write(Utils.encode(label)); + out.write(""); + } + + // output image + if (getAttributes().get("image") != null) + { + out.write(Utils.buildImageTag(context, (String)getAttributes().get("image"), null, "absmiddle")); + } + + out.write(""); + + // output the hidden DIV section to contain the menu item table + // also output the javascript handlers used to hide the menu after a delay of non-use + out.write("

"); + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.label = (String)values[1]; + this.tooltip = (String)values[2]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[3]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.label; + values[2] = this.tooltip; + return values; + } + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * @return Returns the label. + */ + public String getLabel() + { + ValueBinding vb = getValueBinding("label"); + if (vb != null) + { + this.label = (String)vb.getValue(getFacesContext()); + } + return this.label; + } + + /** + * @param label The label to set. + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * @return Returns the tooltip. + */ + public String getTooltip() + { + ValueBinding vb = getValueBinding("tooltip"); + if (vb != null) + { + this.tooltip = (String)vb.getValue(getFacesContext()); + } + return this.tooltip; + } + + /** + * @param tooltip The tooltip to set. + */ + public void setTooltip(String tooltip) + { + this.tooltip = tooltip; + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Return the next usable menu DIV id in a sequence + * + * @param context FacesContext + * + * @return next menu ID + */ + private String getNextMenuId(FacesContext context) + { + Integer val = (Integer)context.getExternalContext().getRequestMap().get(MENU_ID_KEY); + if (val == null) + { + val = Integer.valueOf(0); + } + + // build next id in sequence + String id = getClientId(context) + '_' + val.toString(); + + // save incremented value in the request ready for next menu component instance + val = Integer.valueOf( val.intValue() + 1 ); + context.getExternalContext().getRequestMap().put(MENU_ID_KEY, val); + + return id; + } + + + // ------------------------------------------------------------------------------ + // Private members + + private final static String MENU_ID_KEY = "__awc_menu_id"; + + private String label; + + private String tooltip; +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIModeList.java b/source/java/org/alfresco/web/ui/common/component/UIModeList.java new file mode 100644 index 0000000000..bb2812e6f1 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIModeList.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +/** + * @author Kevin Roast + */ +public class UIModeList extends UICommand +{ + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default constructor + */ + public UIModeList() + { + setRendererType("org.alfresco.faces.ModeListRenderer"); + } + + + // ------------------------------------------------------------------------------ + // Component Impl + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Controls"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.iconColumnWidth = (Integer)values[1]; + this.horizontal = (Boolean)values[2]; + this.disabled = (Boolean)values[3]; + this.label = (String)values[4]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[5]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.iconColumnWidth; + values[2] = this.horizontal; + values[3] = this.disabled; + values[4] = this.label; + return (values); + } + + /** + * @see javax.faces.component.UICommand#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof ModeListItemSelectedEvent) + { + // found an event for us, update the value for this component + setValue( ((ModeListItemSelectedEvent)event).SelectedValue ); + } + + // default ActionEvent processing for a UICommand + super.broadcast(event); + } + + + // ------------------------------------------------------------------------------ + // Strongly typed property accessors + + /** + * Get the horizontal rendering flag + * + * @return true for horizontal rendering, false otherwise + */ + public boolean isHorizontal() + { + ValueBinding vb = getValueBinding("horizontal"); + if (vb != null) + { + this.horizontal = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.horizontal != null) + { + return this.horizontal.booleanValue(); + } + else + { + // return the default + return false; + } + } + + /** + * Set true for horizontal rendering, false otherwise + * + * @param horizontal the horizontal + */ + public void setHorizontal(boolean horizontal) + { + this.horizontal = horizontal; + } + + /** + * Get the icon column width + * + * @return the icon column width + */ + public int getIconColumnWidth() + { + ValueBinding vb = getValueBinding("iconColumnWidth"); + if (vb != null) + { + this.iconColumnWidth = (Integer)vb.getValue(getFacesContext()); + } + + if (this.iconColumnWidth != null) + { + return this.iconColumnWidth.intValue(); + } + else + { + // return the default + return 20; + } + } + + /** + * Set the icon column width + * + * @param iconColumnWidth the icon column width + */ + public void setIconColumnWidth(int iconColumnWidth) + { + this.iconColumnWidth = Integer.valueOf(iconColumnWidth); + } + + /** + * Returns the disabled flag + * + * @return true if the mode list is disabled + */ + public boolean isDisabled() + { + ValueBinding vb = getValueBinding("disabled"); + if (vb != null) + { + this.disabled = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.disabled != null) + { + return this.disabled.booleanValue(); + } + else + { + // return the default + return false; + } + } + + /** + * Sets whether the mode list is disabled + * + * @param disabled the disabled flag + */ + public void setDisabled(boolean disabled) + { + this.disabled = disabled; + } + + /** + * @return Returns the label. + */ + public String getLabel() + { + ValueBinding vb = getValueBinding("label"); + if (vb != null) + { + this.label = (String)vb.getValue(getFacesContext()); + } + + return this.label; + } + + /** + * @param label The label to set. + */ + public void setLabel(String label) + { + this.label = label; + } + + + // ------------------------------------------------------------------------------ + // Private data + + /** the icon column width */ + private Integer iconColumnWidth; + + /** true for horizontal rendering, false otherwise */ + private Boolean horizontal = null; + + /** disabled flag */ + private Boolean disabled = null; + + /** the label */ + private String label; + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing a change in selection for a ModeList component. + */ + public static class ModeListItemSelectedEvent extends ActionEvent + { + private static final long serialVersionUID = 3618135654274774322L; + + public ModeListItemSelectedEvent(UIComponent component, Object selectedValue) + { + super(component); + SelectedValue = selectedValue; + } + + public Object SelectedValue = null; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIOutputText.java b/source/java/org/alfresco/web/ui/common/component/UIOutputText.java new file mode 100644 index 0000000000..7cf2116bea --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIOutputText.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the GNU Lesser General Public License as + * published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * You may obtain a copy of the License at + * + * http://www.gnu.org/licenses/lgpl.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.IOException; + +import javax.faces.component.UIOutput; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; + +/** + * Component that simply renders text + * + * @author gavinc + */ +public class UIOutputText extends UIOutput +{ + /** + * Default constructor + */ + public UIOutputText() + { + setRendererType(null); + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.OutputText"; + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + out.write((String)getValue()); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIPanel.java b/source/java/org/alfresco/web/ui/common/component/UIPanel.java new file mode 100644 index 0000000000..6d12cafcd2 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIPanel.java @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.IOException; +import java.util.Map; + +import javax.faces.component.NamingContainer; +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.component.UIForm; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.MethodBinding; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +import org.alfresco.web.ui.common.PanelGenerator; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.WebResources; + +/** + * @author kevinr + */ +public class UIPanel extends UICommand +{ + // ------------------------------------------------------------------------------ + // Component Impl + + /** + * Default constructor + */ + public UIPanel() + { + setRendererType(null); + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Controls"; + } + + /** + * Return the UI Component to be displayed on the right of the panel title area + * + * @return UIComponent + */ + public UIComponent getTitleComponent() + { + UIComponent titleComponent = null; + + // attempt to find a component with the specified ID + String facetsId = getFacetsId(); + if (facetsId != null) + { + UIForm parent = Utils.getParentForm(FacesContext.getCurrentInstance(), this); + UIComponent facetsComponent = parent.findComponent(facetsId); + if (facetsComponent != null) + { + // get the 'title' facet from the component + titleComponent = facetsComponent.getFacet("title"); + } + } + + return titleComponent; + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + // determine if we have a component on the header + UIComponent titleComponent = getTitleComponent(); + + // determine whether we have any adornments + String label = getLabel(); + if (label != null || isProgressive() == true || titleComponent != null) + { + this.hasAdornments = true; + } + + // make sure we have a default background color for the content area + String bgcolor = getBgcolor(); + if (bgcolor == null) + { + bgcolor = PanelGenerator.BGCOLOR_WHITE; + } + + // determine if we have a bordered title area, note, we also need to have + // the content area border defined as well + if ((getTitleBgcolor() != null) && (getTitleBorder() != null) && + (getBorder() != null) && this.hasAdornments) + { + this.hasBorderedTitleArea = true; + } + + // output first part of border table + if (this.hasBorderedTitleArea) + { + PanelGenerator.generatePanelStart( + out, + context.getExternalContext().getRequestContextPath(), + getTitleBorder(), + getTitleBgcolor()); + } + else if (getBorder() != null) + { + PanelGenerator.generatePanelStart( + out, + context.getExternalContext().getRequestContextPath(), + getBorder(), + bgcolor); + } + + if (this.hasAdornments) + { + // start the containing table if we have any adornments + out.write(""); + } + + // render the title component if supplied + if (titleComponent != null) + { + out.write(""); + } + + if (this.hasAdornments) + { + out.write("
"); + } + + // output progressive disclosure icon in appropriate state + // TODO: manage state of this icon via component Id! + if (isProgressive() == true) + { + out.write(""); + + if (isExpanded() == true) + { + out.write(Utils.buildImageTag(context, WebResources.IMAGE_EXPANDED, 11, 11, "")); + } + else + { + out.write(Utils.buildImageTag(context, WebResources.IMAGE_COLLAPSED, 11, 11, "")); + } + + out.write("  "); + } + + // output textual label + if (label != null) + { + out.write("'); + + out.write(Utils.encode(label)); + + out.write(""); + } + + if (this.hasAdornments) + { + out.write(""); + Utils.encodeRecursive(context, titleComponent); + out.write("
"); + } + + // if we have the titled border area, output the middle section + if (this.hasBorderedTitleArea && isExpanded()) + { + PanelGenerator.generateTitledPanelMiddle( + out, + context.getExternalContext().getRequestContextPath(), + getTitleBorder(), + getBorder(), + getBgcolor()); + } + } + + /** + * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext) + */ + public void encodeEnd(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + // output final part of border table + if (this.hasBorderedTitleArea && isExpanded() == false) + { + PanelGenerator.generatePanelEnd( + out, + context.getExternalContext().getRequestContextPath(), + getTitleBorder()); + } + else if (getBorder() != null) + { + PanelGenerator.generatePanelEnd( + out, + context.getExternalContext().getRequestContextPath(), + getBorder()); + } + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getHiddenFieldName(); + String value = (String)requestMap.get(fieldId); + + // we encoded the value to start with our Id + if (value != null && value.startsWith(getClientId(context))) + { + // we were clicked, strip out the value + value = value.substring(getClientId(context).length() + 1); + + // the expand/collapse icon was clicked, so toggle the state + ExpandedEvent event = new ExpandedEvent(this, Boolean.parseBoolean(value)); + queueEvent(event); + + // + // TODO: See http://forums.java.sun.com/thread.jspa?threadID=524925&start=15&tstart=0 + // Bug/known issue in JSF 1.1 RI + // This causes a problem where the View attempts to assign duplicate Ids + // to components when createUniqueId() on UIViewRoot is called before the + // render phase. This occurs in the Panel tag as it must call getComponent() + // early to decide whether to allow the tag to render contents or not. + // + // context.getViewRoot().setTransient(true); + // + // The other solution is to explicity give ALL child components of the + // panel a unique Id rather than a generated one! + } + } + + /** + * @see javax.faces.component.UICommand#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof ExpandedEvent) + { + // expanded event - we handle this + setExpanded( ((ExpandedEvent)event).State ); + + if (getExpandedActionListener() != null) + { + Utils.processActionMethod(getFacesContext(), getExpandedActionListener(), (ExpandedEvent)event); + } + } + else + { + super.broadcast(event); + } + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + setExpanded( ((Boolean)values[1]).booleanValue() ); + this.progressive = (Boolean)values[2]; + this.border = (String)values[3]; + this.bgcolor = (String)values[4]; + this.label = (String)values[5]; + this.titleBgcolor = (String)values[6]; + this.titleBorder = (String)values[7]; + this.expandedActionListener = (MethodBinding)values[8]; + this.facetsId = (String)values[9]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[10]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = (isExpanded() ? Boolean.TRUE : Boolean.FALSE); + values[2] = this.progressive; + values[3] = this.border; + values[4] = this.bgcolor; + values[5] = this.label; + values[6] = this.titleBgcolor; + values[7] = this.titleBorder; + values[8] = this.expandedActionListener; + values[9] = this.facetsId; + return values; + } + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * @param binding The MethodBinding to call when expand/collapse is performed by the user. + */ + public void setExpandedActionListener(MethodBinding binding) + { + this.expandedActionListener = binding; + } + + /** + * @return The MethodBinding to call when expand/collapse is performed by the user. + */ + public MethodBinding getExpandedActionListener() + { + return this.expandedActionListener; + } + + /** + * @return Returns the bgcolor. + */ + public String getBgcolor() + { + ValueBinding vb = getValueBinding("bgcolor"); + if (vb != null) + { + this.bgcolor = (String)vb.getValue(getFacesContext()); + } + + return this.bgcolor; + } + + /** + * @param bgcolor The bgcolor to set. + */ + public void setBgcolor(String bgcolor) + { + this.bgcolor = bgcolor; + } + + /** + * @return Returns the border name. + */ + public String getBorder() + { + ValueBinding vb = getValueBinding("border"); + if (vb != null) + { + this.border = (String)vb.getValue(getFacesContext()); + } + + return this.border; + } + + /** + * @param border The border name to user. + */ + public void setBorder(String border) + { + this.border = border; + } + + /** + * @return Returns the bgcolor of the title area + */ + public String getTitleBgcolor() + { + ValueBinding vb = getValueBinding("titleBgcolor"); + if (vb != null) + { + this.titleBgcolor = (String)vb.getValue(getFacesContext()); + } + + return this.titleBgcolor; + } + + /** + * @param titleBgcolor Sets the bgcolor of the title area + */ + public void setTitleBgcolor(String titleBgcolor) + { + this.titleBgcolor = titleBgcolor; + } + + /** + * @return Returns the border style of the title area + */ + public String getTitleBorder() + { + ValueBinding vb = getValueBinding("titleBorder"); + if (vb != null) + { + this.titleBorder = (String)vb.getValue(getFacesContext()); + } + + return this.titleBorder; + } + + /** + * @param titleBorder Sets the border style of the title area + */ + public void setTitleBorder(String titleBorder) + { + this.titleBorder = titleBorder; + } + + /** + * @return Returns the label. + */ + public String getLabel() + { + ValueBinding vb = getValueBinding("label"); + if (vb != null) + { + this.label = (String)vb.getValue(getFacesContext()); + } + + return this.label; + } + + /** + * @param label The label to set. + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * @return Returns the progressive display setting. + */ + public boolean isProgressive() + { + ValueBinding vb = getValueBinding("progressive"); + if (vb != null) + { + this.progressive = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.progressive != null) + { + return this.progressive.booleanValue(); + } + else + { + // return default + return false; + } + } + + /** + * @param progressive The progressive display boolean to set. + */ + public void setProgressive(boolean progressive) + { + this.progressive = Boolean.valueOf(progressive); + } + + /** + * Returns whether the component show allow rendering of its child components. + */ + public boolean isExpanded() + { + ValueBinding vb = getValueBinding("expanded"); + if (vb != null) + { + this.expanded = (Boolean)vb.getValue(getFacesContext()); + } + + if (this.expanded != null) + { + return this.expanded.booleanValue(); + } + else + { + // return default + return true; + } + } + + /** + * Sets whether the component show allow rendering of its child components. + * For this component we change this value if the user indicates to change the + * hidden/visible state of the progressive panel. + */ + public void setExpanded(boolean expanded) + { + this.expanded = Boolean.valueOf(expanded); + } + + /** + * Get the facets component Id to use + * + * @return the facets component Id + */ + public String getFacetsId() + { + ValueBinding vb = getValueBinding("facets"); + if (vb != null) + { + this.facetsId = (String)vb.getValue(getFacesContext()); + } + + return this.facetsId; + } + + /** + * Set the facets component Id to use + * + * @param facets the facets component Id + */ + public void setFacetsId(String facets) + { + this.facetsId = facets; + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * We use a hidden field name based on the parent form component Id and + * the string "panel" to give a hidden field name that can be shared by all panels + * within a single UIForm component. + * + * @return hidden field name + */ + private String getHiddenFieldName() + { + UIForm form = Utils.getParentForm(getFacesContext(), this); + return form.getClientId(getFacesContext()) + NamingContainer.SEPARATOR_CHAR + "panel"; + } + + + // ------------------------------------------------------------------------------ + // Private members + + // component settings + private String border = null; + private String bgcolor = null; + private String titleBorder = null; + private String titleBgcolor = null; + private Boolean progressive = null; + private String label = null; + private String facetsId = null; + private MethodBinding expandedActionListener = null; + + // component state + private boolean hasAdornments = false; + private boolean hasBorderedTitleArea = false; + private Boolean expanded = Boolean.TRUE; + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the an action relevant when the panel is expanded or collapsed. + */ + public static class ExpandedEvent extends ActionEvent + { + public ExpandedEvent(UIComponent component, boolean state) + { + super(component); + State = state; + } + + public boolean State; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/UIStatusMessage.java b/source/java/org/alfresco/web/ui/common/component/UIStatusMessage.java new file mode 100644 index 0000000000..2da387abb9 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/UIStatusMessage.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component; + +import java.io.IOException; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.faces.application.FacesMessage; +import javax.faces.component.NamingContainer; +import javax.faces.component.UIComponent; +import javax.faces.component.UIForm; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +import org.alfresco.web.app.Application; +import org.alfresco.web.ui.common.PanelGenerator; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.WebResources; + +/** + * @author Kevin Roast + */ +public class UIStatusMessage extends SelfRenderingComponent +{ + /** + * Default Constructor + */ + public UIStatusMessage() + { + setRendererType(null); + + // add default message to display + FacesContext fc = FacesContext.getCurrentInstance(); + String msg = Application.getMessage(fc, MSG_DEFAULT_STATUS); + String time = Utils.getTimeFormat(fc).format(new Date(System.currentTimeMillis())); + this.messages.add(new FacesMessage(FacesMessage.SEVERITY_INFO, time, msg)); + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.StatusMessage"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.border = (String)values[1]; + this.bgcolor = (String)values[2]; + this.messages = (List)values[3]; + this.currentMessage = ((Integer)values[4]).intValue(); + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[5]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.border; + values[2] = this.bgcolor; + values[3] = this.messages; + values[4] = Integer.valueOf(this.currentMessage); + return values; + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + + String bgColor = getBgcolor(); + if (bgColor == null) + { + bgColor = PanelGenerator.BGCOLOR_WHITE; + } + + String panel = getBorder(); + if (panel != null) + { + PanelGenerator.generatePanelStart(out, + context.getExternalContext().getRequestContextPath(), + panel, + bgColor); + } + + // Previous Message icon image - clicking shows previous message + out.write("
"); + String field = getHiddenFieldName(); + String leftValue = getClientId(context) + NamingContainer.SEPARATOR_CHAR + Integer.toString(ACTION_PREVIOUS); + String leftOnclick = Utils.generateFormSubmit(context, this, field, leftValue); + out.write(Utils.buildImageTag(context, WebResources.IMAGE_MOVELEFT, 12, 12, null, leftOnclick, "absmiddle")); + out.write(""); + + // get messages for the component and crop the stack to the maximum history size + Iterator msgIterator = context.getMessages(STATUS_MESSAGE); + while (msgIterator.hasNext()) + { + if (messages.size() >= HISTORY_SIZE) + { + messages.remove(HISTORY_SIZE); + } + // add new messages to the stack in turn + messages.add(0, msgIterator.next()); + // reset current message to top if new one added + currentMessage = 0; + } + + // TODO: show different icon depending on SEVERITY of the message? + // Message text + String style = CSS_ERROR; + String icon = WebResources.IMAGE_INFO; + FacesMessage msg = messages.get(currentMessage); + if (msg.getSeverity() == FacesMessage.SEVERITY_INFO) + { + style = CSS_INFO; + } + else if (msg.getSeverity() == FacesMessage.SEVERITY_WARN) + { + style = CSS_WARNING; + } + + out.write(Utils.buildImageTag(context, icon, null, "absmiddle")); + out.write(" "); + out.write(msg.getSummary()); + out.write(" - "); + out.write(Utils.encode(msg.getDetail())); + out.write(""); + out.write(""); + + // Next Message icon image - clicking shows next message + String rightValue = getClientId(context) + NamingContainer.SEPARATOR_CHAR + Integer.toString(ACTION_NEXT); + String rightOnclick = Utils.generateFormSubmit(context, this, field, rightValue); + out.write(Utils.buildImageTag(context, WebResources.IMAGE_MOVERIGHT, 12, 12, null, rightOnclick, "absmiddle")); + out.write("
"); + + if (panel != null) + { + PanelGenerator.generatePanelEnd(out, + context.getExternalContext().getRequestContextPath(), + panel); + } + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getHiddenFieldName(); + String value = (String)requestMap.get(fieldId); + + // we encoded the value to start with our Id + if (value != null && value.startsWith(getClientId(context))) + { + // we were clicked, strip out the value + int action = Integer.parseInt(value.substring(getClientId(context).length() + 1)); + + // raise an event to represent the requested action + MessageEvent event = new MessageEvent(this, action); + queueEvent(event); + } + } + + /** + * @see javax.faces.component.UICommand#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof MessageEvent) + { + switch (((MessageEvent)event).Action) + { + case ACTION_NEXT: + currentMessage++; + if (currentMessage >= this.messages.size()) + { + currentMessage = 0; + } + break; + + case ACTION_PREVIOUS: + currentMessage--; + if (currentMessage < 0) + { + currentMessage = this.messages.size() - 1; + } + break; + } + } + else + { + super.broadcast(event); + } + } + + /** + * @return Returns the bgcolor. + */ + public String getBgcolor() + { + ValueBinding vb = getValueBinding("bgcolor"); + if (vb != null) + { + this.bgcolor = (String)vb.getValue(getFacesContext()); + } + + return this.bgcolor; + } + + /** + * @param bgcolor The bgcolor to set. + */ + public void setBgcolor(String bgcolor) + { + this.bgcolor = bgcolor; + } + + /** + * @return Returns the border name. + */ + public String getBorder() + { + ValueBinding vb = getValueBinding("border"); + if (vb != null) + { + this.border = (String)vb.getValue(getFacesContext()); + } + + return this.border; + } + + /** + * @param border The border name to user. + */ + public void setBorder(String border) + { + this.border = border; + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * We use a hidden field name based on the parent form component Id and + * the string "status" to give a hidden field name that can be shared by all status messages + * within a single UIForm component. + * + * @return hidden field name + */ + private String getHiddenFieldName() + { + UIForm form = Utils.getParentForm(getFacesContext(), this); + return form.getClientId(getFacesContext()) + NamingContainer.SEPARATOR_CHAR + "status"; + } + + + // ------------------------------------------------------------------------------ + // Private members + + public static final String STATUS_MESSAGE = "status-message"; + + private final static String CSS_INFO = "statusInfoText"; + private final static String CSS_WARNING = "statusWarningText"; + private final static String CSS_ERROR = "statusErrorText"; + + private final static int ACTION_PREVIOUS = 0; + private final static int ACTION_NEXT = 1; + + private final static int HISTORY_SIZE = 10; + + private final static String MSG_DEFAULT_STATUS = "status_message_default"; + + private List messages = new LinkedList(); + private int currentMessage = 0; + + // component settings + private String border = null; + private String bgcolor = null; + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the an action that occurs when the previous/next buttons are clicked. + */ + public static class MessageEvent extends ActionEvent + { + public MessageEvent(UIComponent component, int action) + { + super(component); + Action = action; + } + + public int Action; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/GridArrayDataModel.java b/source/java/org/alfresco/web/ui/common/component/data/GridArrayDataModel.java new file mode 100644 index 0000000000..0cd1d453de --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/GridArrayDataModel.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +/** + * @author kevinr + */ +public class GridArrayDataModel implements IGridDataModel +{ + /** + * Constructor + * + * @param data Array of Object (beans) row data + */ + public GridArrayDataModel(Object[] data) + { + this.data = data; + } + + /** + * Get a row object for the specified row index + * + * @param index valid row index + * + * @return row object for the specified index + */ + public Object getRow(int index) + { + return this.data[index]; + } + + /** + * Return the number of rows in the data model + * + * @return row count + */ + public int size() + { + return this.data.length; + } + + /** + * Sort the data set using the specified sort parameters + * + * @param column Column to sort + * @param descending True for descending sort, false for ascending + * @param mode Sort mode to use (see IDataContainer constants) + */ + public void sort(String column, boolean descending, String mode) + { + } + + private Object[] data = null; +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/GridListDataModel.java b/source/java/org/alfresco/web/ui/common/component/data/GridListDataModel.java new file mode 100644 index 0000000000..0251aa0e02 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/GridListDataModel.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +import java.util.List; + +import org.alfresco.web.data.QuickSort; + +/** + * @author kevinr + */ +public class GridListDataModel implements IGridDataModel +{ + /** + * Constructor + * + * @param data List of Object[] row data + */ + public GridListDataModel(List data) + { + this.data = data; + } + + /** + * Get a row object for the specified row index + * + * @param index valid row index + * + * @return row object for the specified index + */ + public Object getRow(int index) + { + return this.data.get(index); + } + + /** + * Return the number of rows in the data model + * + * @return row count + */ + public int size() + { + return this.data.size(); + } + + /** + * Sort the data set using the specified sort parameters + * + * @param column Column to sort + * @param descending True for descending sort, false for ascending + * @param mode Sort mode to use (see IDataContainer constants) + */ + public void sort(String column, boolean descending, String mode) + { + try + { + QuickSort sorter = new QuickSort(this.data, column, !descending, mode); + sorter.sort(); + } + catch (Exception err) + { + throw new RuntimeException("Failed to sort data: " + err.getMessage(), err); + } + } + + private List data = null; +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/IGridDataModel.java b/source/java/org/alfresco/web/ui/common/component/data/IGridDataModel.java new file mode 100644 index 0000000000..4f0e58ea8c --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/IGridDataModel.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +/** + * @author kevinr + */ +public interface IGridDataModel +{ + /** + * Get a row object for the specified row index + * + * @param index valid row index + * + * @return row object for the specified index + */ + public Object getRow(int index); + + /** + * Return the number of rows in the data model + * + * @return row count + */ + public int size(); + + /** + * Sort the data set using the specified sort parameters + * + * @param column Column to sort + * @param descending True for descending sort, false for ascending + * @param mode Sort mode to use (see IDataContainer constants) + */ + public void sort(String column, boolean descending, String mode); +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/UIColumn.java b/source/java/org/alfresco/web/ui/common/component/data/UIColumn.java new file mode 100644 index 0000000000..9a3f2f3c20 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/UIColumn.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +import javax.faces.component.UIComponent; +import javax.faces.component.UIComponentBase; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +/** + * @author Kevin Roast + */ +public class UIColumn extends UIComponentBase +{ + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Data"; + } + + /** + * Return the UI Component to be used as the header for this column + * + * @return UIComponent + */ + public UIComponent getHeader() + { + return getFacet("header"); + } + + /** + * Return the UI Component to be used as the footer for this column + * + * @return UIComponent + */ + public UIComponent getFooter() + { + return getFacet("footer"); + } + + /** + * Return the UI Component to be used as the large icon for this column + * + * @return UIComponent + */ + public UIComponent getLargeIcon() + { + return getFacet("large-icon"); + } + + /** + * Return the UI Component to be used as the small icon for this column + * + * @return UIComponent + */ + public UIComponent getSmallIcon() + { + return getFacet("small-icon"); + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.primary = ((Boolean)values[1]).booleanValue(); + this.actions = ((Boolean)values[2]).booleanValue(); + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[3]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = (this.primary ? Boolean.TRUE : Boolean.FALSE); + values[2] = (this.actions ? Boolean.TRUE : Boolean.FALSE); + return (values); + } + + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * @return true if this is the primary column + */ + public boolean getPrimary() + { + ValueBinding vb = getValueBinding("primary"); + if (vb != null) + { + this.primary = (Boolean)vb.getValue(getFacesContext()); + } + return this.primary; + } + + /** + * @param primary True if this is the primary column, false otherwise + */ + public void setPrimary(boolean primary) + { + this.primary = primary; + } + + /** + * @return true if this is the column containing actions for the current row + */ + public boolean getActions() + { + ValueBinding vb = getValueBinding("actions"); + if (vb != null) + { + this.actions = (Boolean)vb.getValue(getFacesContext()); + } + return this.actions; + } + + /** + * @param actions True if this is the column containing actions for the current row + */ + public void setActions(boolean actions) + { + this.actions = actions; + } + + + // ------------------------------------------------------------------------------ + // Private data + + private boolean primary = false; + private boolean actions = false; +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/UIDataPager.java b/source/java/org/alfresco/web/ui/common/component/data/UIDataPager.java new file mode 100644 index 0000000000..05a3a85f7e --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/UIDataPager.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.component.NamingContainer; +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +import org.apache.log4j.Logger; + +import org.alfresco.web.app.Application; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.WebResources; + +/** + * @author Kevin Roast + */ +public class UIDataPager extends UICommand +{ + private static Logger s_logger = Logger.getLogger(IDataContainer.class); + + private static final String LAST_PAGE = "last_page"; + private static final String NEXT_PAGE = "next_page"; + private static final String PREVIOUS_PAGE = "prev_page"; + private static final String FIRST_PAGE = "first_page"; + private static final String MSG_PAGEINFO = "page_info"; + + + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default constructor + */ + public UIDataPager() + { + setRendererType(null); + } + + + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + IDataContainer dataContainer = getDataContainer(); + if (dataContainer == null) + { + throw new IllegalStateException("Must nest UISortLink inside component implementing IDataContainer!"); + } + + // this component will only render itself if the parent DataContainer is setup + // with a valid "pageSize" property + if (isRendered() == false || dataContainer.getPageSize() == -1) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + ResourceBundle bundle = Application.getBundle(context); + StringBuilder buf = new StringBuilder(512); + + int currentPage = dataContainer.getCurrentPage(); + int pageCount = dataContainer.getPageCount(); + + buf.append("'); + + // output Page X of Y text + buf.append(MessageFormat.format(bundle.getString(MSG_PAGEINFO), new Object[] { + Integer.toString(currentPage + 1), // current page can be zero if no data present + Integer.toString(pageCount) + })); + + buf.append(" "); + + // output HTML links or labels to render the paging controls + // first page + if (currentPage != 0) + { + buf.append(""); + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_FIRSTPAGE, 16, 16, bundle.getString(FIRST_PAGE))); + buf.append(""); + } + else + { + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_FIRSTPAGE_NONE, 16, 16, null)); + } + + // previous page + if (currentPage != 0) + { + buf.append(""); + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_PREVIOUSPAGE, 16, 16, bundle.getString(PREVIOUS_PAGE))); + buf.append(""); + } + else + { + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_PREVIOUSPAGE_NONE, 16, 16, null)); + } + + buf.append(" "); + + // clickable digits for pages 1 to 10 + int totalIndex = (pageCount < 10 ? pageCount : 10); + for (int i=0; i") + .append(i + 1) + .append(" "); + } + else + { + buf.append("") + .append(i + 1) + .append(" "); + } + } + // clickable digits for pages 20 to 100 (in jumps of 10) + if (pageCount >= 20) + { + buf.append("... "); + totalIndex = (pageCount / 10) * 10; + totalIndex = (totalIndex < 100 ? totalIndex : 100); + for (int i=19; i") + .append(i + 1) + .append(" "); + } + else + { + buf.append("") + .append(i + 1) + .append(" "); + } + } + } + // clickable digits for last page if > 10 and not already shown + if ((pageCount > 10) && (pageCount % 10 != 0)) + { + if (pageCount-1 != currentPage) + { + if (pageCount < 20) + { + buf.append("... "); + } + buf.append("") + .append(pageCount) + .append(" "); + } + else + { + if (pageCount < 20) + { + buf.append("... "); + } + buf.append("") + .append(pageCount) + .append(" "); + } + } + + // next page + if ((dataContainer.getCurrentPage() < dataContainer.getPageCount() - 1) == true) + { + buf.append(""); + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_NEXTPAGE, 16, 16, bundle.getString(NEXT_PAGE))); + buf.append(""); + } + else + { + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_NEXTPAGE_NONE, 16, 16, null)); + } + + // last page + if ((dataContainer.getCurrentPage() < dataContainer.getPageCount() - 1) == true) + { + buf.append(""); + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_LASTPAGE, 16, 16, bundle.getString(LAST_PAGE))); + buf.append(""); + } + else + { + buf.append(Utils.buildImageTag(context, WebResources.IMAGE_LASTPAGE_NONE, 16, 16, null)); + } + + buf.append(""); + + out.write(buf.toString()); + } + + /** + * @see javax.faces.component.UIComponentBase#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getHiddenFieldName(); + String value = (String)requestMap.get(fieldId); + if (value != null && value.length() != 0) + { + // we were clicked - queue an event to represent the click + // cannot handle the event here as other components etc. have not had + // a chance to decode() - we queue an event to be processed later + PageEvent actionEvent = new PageEvent(this, Integer.valueOf(value).intValue()); + this.queueEvent(actionEvent); + } + } + + /** + * @see javax.faces.component.UICommand#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof PageEvent == false) + { + // let the super class handle events which we know nothing about + super.broadcast(event); + } + else + { + // found a sort event for us! + if (s_logger.isDebugEnabled()) + s_logger.debug("Handling paging event to index: " + ((PageEvent)event).Page); + getDataContainer().setCurrentPage(((PageEvent)event).Page); + } + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Return the parent data container for this component + */ + private IDataContainer getDataContainer() + { + return Utils.getParentDataContainer(getFacesContext(), this); + } + + /** + * Output the JavaScript event script to jump to a specified page + * + * @param page page index to generate script to jump too + */ + private String generateEventScript(int page) + { + return Utils.generateFormSubmit(getFacesContext(), this, getHiddenFieldName(), Integer.toString(page)); + } + + /** + * We use a hidden field name based on the parent data container component Id and + * the string "pager" to give a field name that can be shared by all pager links + * within a single data container component. + * + * @return hidden field name + */ + private String getHiddenFieldName() + { + UIComponent dataContainer = (UIComponent)Utils.getParentDataContainer(getFacesContext(), this); + return dataContainer.getClientId(getFacesContext()) + NamingContainer.SEPARATOR_CHAR + "pager"; + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the clicking of a sortable column. + */ + private static class PageEvent extends ActionEvent + { + public PageEvent(UIComponent component, int page) + { + super(component); + Page = page; + } + + public int Page = 0; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/UIRichList.java b/source/java/org/alfresco/web/ui/common/component/data/UIRichList.java new file mode 100644 index 0000000000..e6071c7cd3 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/UIRichList.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.faces.component.UIComponent; +import javax.faces.component.UIComponentBase; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigService; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.config.ClientConfigElement; +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.ui.common.renderer.data.IRichListRenderer; +import org.alfresco.web.ui.common.renderer.data.RichListRenderer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * @author Kevin Roast + */ +public class UIRichList extends UIComponentBase implements IDataContainer +{ + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default constructor + */ + public UIRichList() + { + setRendererType("org.alfresco.faces.RichListRenderer"); + + + IRichListRenderer test = new RichListRenderer.IconViewRenderer(); + + // get the list of views from the client configuration + ConfigService configSvc = (ConfigService)FacesContextUtils.getRequiredWebApplicationContext( + FacesContext.getCurrentInstance()).getBean(Application.BEAN_CONFIG_SERVICE); + ClientConfigElement clientConfig = (ClientConfigElement)configSvc.getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + List views = clientConfig.getViews(); + + // instantiate each renderer and add to the list + for (String view : views) + { + try + { + Class clazz = Class.forName(view); + IRichListRenderer renderer = (IRichListRenderer)clazz.newInstance(); + UIRichList.viewRenderers.put(renderer.getViewModeID(), renderer); + + if (logger.isDebugEnabled()) + logger.debug("Added view '" + renderer.getViewModeID() + + "' to UIRichList"); + } + catch (Exception e) + { + if (logger.isWarnEnabled()) + { + logger.warn("Failed to create renderer: " + view, e); + } + } + } + } + + + // ------------------------------------------------------------------------------ + // Component implementation + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Data"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.currentPage = ((Integer)values[1]).intValue(); + this.sortColumn = (String)values[2]; + this.sortDescending = ((Boolean)values[3]).booleanValue(); + this.value = values[4]; // not serializable! + this.dataModel = (IGridDataModel)values[5]; // not serializable! + this.viewMode = (String)values[6]; + this.pageSize = ((Integer)values[7]).intValue(); + this.initialSortColumn = (String)values[8]; + this.initialSortDescending = ((Boolean)values[9]).booleanValue(); + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[10]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = Integer.valueOf(this.currentPage); + values[2] = this.sortColumn; + values[3] = (this.sortDescending ? Boolean.TRUE : Boolean.FALSE); + values[4] = this.value; + values[5] = this.dataModel; + values[6] = this.viewMode; + values[7] = Integer.valueOf(this.pageSize); + values[8] = this.initialSortColumn; + values[9] = (this.initialSortDescending ? Boolean.TRUE : Boolean.FALSE); + + return (values); + } + + /** + * Get the value (for this component the value is an object used as the DataModel) + * + * @return the value + */ + public Object getValue() + { + if (this.value == null) + { + ValueBinding vb = getValueBinding("value"); + if (vb != null) + { + this.value = vb.getValue(getFacesContext()); + } + } + return this.value; + } + + /** + * Set the value (for this component the value is an object used as the DataModel) + * + * @param value the value + */ + public void setValue(Object value) + { + this.dataModel = null; + this.value = value; + } + + /** + * Clear the current sorting settings back to the defaults + */ + public void clearSort() + { + this.sortColumn = null; + this.sortDescending = true; + this.initialSortColumn = null; + this.initialSortDescending = false; + } + + /** + * Get the view mode for this Rich List + * + * @return view mode as a String + */ + public String getViewMode() + { + ValueBinding vb = getValueBinding("viewMode"); + if (vb != null) + { + this.viewMode = (String)vb.getValue(getFacesContext()); + } + + return this.viewMode; + } + + /** + * Set the current view mode for this Rich List + * + * @param viewMode the view mode as a String + */ + public void setViewMode(String viewMode) + { + this.viewMode = viewMode; + } + + /** + * Return the UI Component to be used as the "no items available" message + * + * @return UIComponent + */ + public UIComponent getEmptyMessage() + { + return getFacet("empty"); + } + + + // ------------------------------------------------------------------------------ + // IDataContainer implementation + + /** + * Return the currently sorted column if any + * + * @return current sorted column if any + */ + public String getCurrentSortColumn() + { + return this.sortColumn; + } + + /** + * @see org.alfresco.web.data.IDataContainer#isCurrentSortDescending() + */ + public boolean isCurrentSortDescending() + { + return this.sortDescending; + } + + /** + * @return Returns the initialSortColumn. + */ + public String getInitialSortColumn() + { + return this.initialSortColumn; + } + + /** + * @param initialSortColumn The initialSortColumn to set. + */ + public void setInitialSortColumn(String initialSortColumn) + { + this.initialSortColumn = initialSortColumn; + } + + /** + * @return Returns the initialSortDescending. + */ + public boolean isInitialSortDescending() + { + return this.initialSortDescending; + } + + /** + * @param initialSortDescending The initialSortDescending to set. + */ + public void setInitialSortDescending(boolean initialSortDescending) + { + this.initialSortDescending = initialSortDescending; + } + + /** + * Returns the current page size used for this list, or -1 for no paging. + */ + public int getPageSize() + { + ValueBinding vb = getValueBinding("pageSize"); + if (vb != null) + { + int pageSize = ((Integer)vb.getValue(getFacesContext())).intValue(); + if (pageSize != this.pageSize) + { + // force a reset of the current page - else the bind may show a page that isn't there + setPageSize(pageSize); + } + } + + return this.pageSize; + } + + /** + * Sets the current page size used for the list. + * + * @param val + */ + public void setPageSize(int val) + { + if (val >= -1) + { + this.pageSize = val; + setCurrentPage(0); + } + } + + /** + * @see org.alfresco.web.data.IDataContainer#getPageCount() + */ + public int getPageCount() + { + return this.pageCount; + } + + /** + * Return the current page the list is displaying + * + * @return current page zero based index + */ + public int getCurrentPage() + { + return this.currentPage; + } + + /** + * @see org.alfresco.web.data.IDataContainer#setCurrentPage(int) + */ + public void setCurrentPage(int index) + { + this.currentPage = index; + } + + /** + * Returns true if a row of data is available + * + * @return true if data is available, false otherwise + */ + public boolean isDataAvailable() + { + return this.rowIndex < this.maxRowIndex; + } + + /** + * Returns the next row of data from the data model + * + * @return next row of data as a Bean object + */ + public Object nextRow() + { + // get next row and increment row count + Object rowData = getDataModel().getRow(this.rowIndex + 1); + + // Prepare the data-binding variable "var" ready for the next cycle of + // renderering for the child components. + String var = (String)getAttributes().get("var"); + if (var != null) + { + Map requestMap = getFacesContext().getExternalContext().getRequestMap(); + if (isDataAvailable() == true) + { + requestMap.put(var, rowData); + } + else + { + requestMap.remove(var); + } + } + + this.rowIndex++; + + return rowData; + } + + /** + * Sort the dataset using the specified sort parameters + * + * @param column Column to sort + * @param descending True for descending sort, false for ascending + * @param mode Sort mode to use (see IDataContainer constants) + */ + public void sort(String column, boolean descending, String mode) + { + this.sortColumn = column; + this.sortDescending = descending; + + // delegate to the data model to sort its contents + // place in a UserTransaction as we may need to perform a LOT of node calls to complete + UserTransaction tx = null; + try + { + if (getDataModel().size() > 64) + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context, true); + tx.begin(); + } + + getDataModel().sort(column, descending, mode); + + // commit the transaction + if (tx != null) + { + tx.commit(); + } + } + catch (Exception err) + { + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + } + + + // ------------------------------------------------------------------------------ + // UIRichList implementation + + /** + * Method called to bind the RichList component state to the data model value + */ + public void bind() + { + int rowCount = getDataModel().size(); + // if a page size is specified, then we use that + int pageSize = getPageSize(); + if (pageSize != -1) + { + // calc start row index based on current page index + this.rowIndex = (this.currentPage * pageSize) - 1; + + // calc total number of pages available + this.pageCount = (rowCount / this.pageSize) + 1; + if (rowCount % pageSize == 0 && this.pageCount != 1) + { + this.pageCount--; + } + + // calc the maximum row index that can be returned + this.maxRowIndex = this.rowIndex + pageSize; + if (this.maxRowIndex >= rowCount) + { + this.maxRowIndex = rowCount - 1; + } + } + // else we are not paged so show all data from start + else + { + this.rowIndex = -1; + this.pageCount = 1; + this.maxRowIndex = (rowCount - 1); + } + if (logger.isDebugEnabled()) + logger.debug("Bound datasource: PageSize: " + pageSize + "; CurrentPage: " + this.currentPage + "; RowIndex: " + this.rowIndex + "; MaxRowIndex: " + this.maxRowIndex + "; RowCount: " + rowCount); + } + + /** + * @return A new IRichListRenderer implementation for the current view mode + */ + public IRichListRenderer getViewRenderer() + { + // get type from current view mode, then create an instance of the renderer + IRichListRenderer renderer = null; + if (getViewMode() != null) + { + renderer = (IRichListRenderer)UIRichList.viewRenderers.get(getViewMode()); + } + return renderer; + } + + /** + * Return the data model wrapper + * + * @return IGridDataModel + */ + private IGridDataModel getDataModel() + { + if (this.dataModel == null) + { + // build the appropriate data-model wrapper object + Object val = getValue(); + if (val instanceof List) + { + this.dataModel = new GridListDataModel((List)val); + } + else if ( (java.lang.Object[].class).isAssignableFrom(val.getClass()) ) + { + this.dataModel = new GridArrayDataModel((Object[])val); + } + else + { + throw new IllegalStateException("UIRichList 'value' attribute binding should specify data model of a supported type!"); + } + + // sort first time on initially sorted column if set + if (this.sortColumn == null) + { + String initialSortColumn = getInitialSortColumn(); + if (initialSortColumn != null && initialSortColumn.length() != 0) + { + boolean descending = isInitialSortDescending(); + + // TODO: add support for retrieving correct column sort mode here + this.sortColumn = initialSortColumn; + this.sortDescending = descending; + } + } + if (this.sortColumn != null) + { + // delegate to the data model to sort its contents + this.dataModel.sort(this.sortColumn, this.sortDescending, IDataContainer.SORT_CASEINSENSITIVE); + } + + // reset current page + this.currentPage = 0; + } + + return this.dataModel; + } + + + // ------------------------------------------------------------------------------ + // Private data + + /** map of available IRichListRenderer instances */ + private final static Map viewRenderers = new HashMap(5); + + // component state + private int currentPage = 0; + private String sortColumn = null; + private boolean sortDescending = true; + private Object value = null; + private IGridDataModel dataModel = null; + private String viewMode = null; + private int pageSize = -1; + private String initialSortColumn = null; + private boolean initialSortDescending = false; + + // transient component state that exists during a single page refresh only + private int rowIndex = -1; + private int maxRowIndex = -1; + private int pageCount = 1; + + private static Log logger = LogFactory.getLog(IDataContainer.class); +} diff --git a/source/java/org/alfresco/web/ui/common/component/data/UISortLink.java b/source/java/org/alfresco/web/ui/common/component/data/UISortLink.java new file mode 100644 index 0000000000..1f8777cc0c --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/data/UISortLink.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.data; + +import java.io.IOException; +import java.util.Map; + +import javax.faces.component.NamingContainer; +import javax.faces.component.UICommand; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.ActionEvent; +import javax.faces.event.FacesEvent; + +import org.apache.log4j.Logger; + +import org.alfresco.web.data.IDataContainer; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.WebResources; + +/** + * @author Kevin Roast + */ +public class UISortLink extends UICommand +{ + /** + * Default Constructor + */ + public UISortLink() + { + setRendererType(null); + } + + /** + * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + ResponseWriter out = context.getResponseWriter(); + + IDataContainer dataContainer = getDataContainer(); + if (dataContainer == null) + { + throw new IllegalStateException("Must nest UISortLink inside component implementing IDataContainer!"); + } + + // swap sort direction if we were last sorted column + boolean bPreviouslySorted = false; + boolean descending = true; + String lastSortedColumn = dataContainer.getCurrentSortColumn(); + if (lastSortedColumn == (String)getValue()) + { + descending = !dataContainer.isCurrentSortDescending(); + bPreviouslySorted = true; + } + + // render sort link + StringBuilder buf = new StringBuilder(256); + buf.append(""); + + out.write(buf.toString()); + } + + /** + * @see javax.faces.component.UIComponent#decode(javax.faces.context.FacesContext) + */ + public void decode(FacesContext context) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getHiddenFieldName(context); + String value = (String)requestMap.get(fieldId); + if (value != null && value.equals(getClientId(context))) + { + // we were clicked - queue an event to represent the click + // cannot handle the event here as other components etc. have not had + // a chance to decode() - we queue an event to be processed later + SortEvent actionEvent = new SortEvent(this, (String)this.getValue()); + this.queueEvent(actionEvent); + } + } + + /** + * @see javax.faces.component.UIComponent#broadcast(javax.faces.event.FacesEvent) + */ + public void broadcast(FacesEvent event) throws AbortProcessingException + { + if (event instanceof SortEvent == false) + { + // let the super class handle events which we know nothing about + super.broadcast(event); + } + else if ( ((SortEvent)event).Column.equals(getColumn()) ) + { + // found a sort event for us! + if (s_logger.isDebugEnabled()) + s_logger.debug("Handling sort event for column: " + ((SortEvent)event).Column); + + if (getColumn().equals(getDataContainer().getCurrentSortColumn()) == true) + { + // reverse sort direction + this.descending = !this.descending; + } + else + { + // revert to default sort direction + this.descending = true; + } + getDataContainer().sort(getColumn(), this.descending, getMode()); + } + } + + /** + * We use a hidden field name based on the parent data container component Id and + * the string "sort" to give a field name that can be shared by all sort links + * within a single data container component. + * + * @param context FacesContext + * + * @return hidden field name + */ + private String getHiddenFieldName(FacesContext context) + { + UIComponent dataContainer = (UIComponent)Utils.getParentDataContainer(context, this); + return dataContainer.getClientId(context) + NamingContainer.SEPARATOR_CHAR + "sort"; + } + + /** + * Column name referenced by this link + * + * @return column name + */ + public String getColumn() + { + return (String)getValue(); + } + + /** + * Sorting mode - see IDataContainer constants + * + * @return sorting mode - see IDataContainer constants + */ + public String getMode() + { + return this.mode; + } + + /** + * Set the sorting mode - see IDataContainer constants + * + * @param sortMode the sorting mode- see IDataContainer constants + */ + public void setMode(String sortMode) + { + this.mode = sortMode; + } + + /** + * Returns true for descending sort, false for ascending + * + * @return true for descending sort, false for ascending + */ + public boolean isDescending() + { + return this.descending; + } + + /** + * @return Returns the label. + */ + public String getLabel() + { + ValueBinding vb = getValueBinding("label"); + if (vb != null) + { + this.label = (String)vb.getValue(getFacesContext()); + } + return this.label; + } + + /** + * @param label The label to set. + */ + public void setLabel(String label) + { + this.label = label; + } + + /** + * @return Returns the tooltip. + */ + public String getTooltip() + { + ValueBinding vb = getValueBinding("tooltip"); + if (vb != null) + { + this.tooltip = (String)vb.getValue(getFacesContext()); + } + return this.tooltip; + } + + /** + * @param tooltip The tooltip to set. + */ + public void setTooltip(String tooltip) + { + this.tooltip = tooltip; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.descending = ((Boolean)values[1]).booleanValue(); + this.mode = (String)values[2]; + this.label = (String)values[3]; + this.tooltip = (String)values[4]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[5]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = (this.descending ? Boolean.TRUE : Boolean.FALSE); + values[2] = this.mode; + values[3] = this.label; + values[4] = this.tooltip; + return values; + } + + /** + * Return the parent data container for this component + */ + private IDataContainer getDataContainer() + { + return Utils.getParentDataContainer(getFacesContext(), this); + } + + + // ------------------------------------------------------------------------------ + // Inner classes + + /** + * Class representing the clicking of a sortable column. + */ + private static class SortEvent extends ActionEvent + { + public SortEvent(UIComponent component, String column) + { + super(component); + Column = column; + } + + public String Column = null; + } + + + // ------------------------------------------------------------------------------ + // Constants + + private static Logger s_logger = Logger.getLogger(IDataContainer.class); + + /** sorting mode */ + private String mode = IDataContainer.SORT_CASEINSENSITIVE; + + private String label; + + private String tooltip; + + /** true for descending sort, false for ascending */ + private boolean descending = false; +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/BaseDebugComponent.java b/source/java/org/alfresco/web/ui/common/component/debug/BaseDebugComponent.java new file mode 100644 index 0000000000..b86fd5839b --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/BaseDebugComponent.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.io.IOException; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; + +import org.alfresco.web.ui.common.component.SelfRenderingComponent; + +/** + * Base class for all debug components + * + * @author gavinc + */ +public abstract class BaseDebugComponent extends SelfRenderingComponent +{ + private String title; + + /** + * Retrieves the debug data to show for the component as a Map + * + * @return The Map of data + */ + public abstract Map getDebugData(); + + /** + * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (isRendered() == false) + { + return; + } + + ResponseWriter out = context.getResponseWriter(); + out.write(""); + + if (this.getTitle() != null) + { + out.write(""); + } + + out.write(""); + + Map session = getDebugData(); + for (Object key : session.keySet()) + { + out.write(""); + } + + out.write("
"); + out.write(this.getTitle()); + out.write("
PropertyValue
"); + out.write(key.toString()); + out.write(""); + String value = session.get(key).toString(); + if (value == null || value.length() == 0) + { + out.write(" "); + } + else + { + // replace any ; characters with ; as that will help break up long lines + value = value.replaceAll(";", "; "); + out.write(value); + } + out.write("
"); + + super.encodeBegin(context); + } + + /** + * @see javax.faces.component.UIComponent#getRendersChildren() + */ + public boolean getRendersChildren() + { + return false; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.title = (String)values[1]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.title; + return (values); + } + + /** + * Returns the title + * + * @return The title + */ + public String getTitle() + { + ValueBinding vb = getValueBinding("title"); + if (vb != null) + { + this.title = (String)vb.getValue(getFacesContext()); + } + + return this.title; + } + + /** + * Sets the title + * + * @param title The title + */ + public void setTitle(String title) + { + this.title = title; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/UIHttpApplicationState.java b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpApplicationState.java new file mode 100644 index 0000000000..b57278393b --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpApplicationState.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.util.Map; + +import javax.faces.context.FacesContext; + +/** + * Component which displays the current state of the HTTP session + * + * @author gavinc + */ +public class UIHttpApplicationState extends BaseDebugComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.debug.HttpApplicationState"; + } + + /** + * @see org.alfresco.web.ui.common.component.debug.BaseDebugComponent#getDebugData() + */ + public Map getDebugData() + { + return FacesContext.getCurrentInstance().getExternalContext().getApplicationMap(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestHeaders.java b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestHeaders.java new file mode 100644 index 0000000000..77b2b395a2 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestHeaders.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.util.Map; + +import javax.faces.context.FacesContext; + +/** + * Component which displays the headers for the current HTTP request + * + * @author gavinc + */ +public class UIHttpRequestHeaders extends BaseDebugComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.debug.HttpRequestHeaders"; + } + + /** + * @see org.alfresco.web.ui.common.component.debug.BaseDebugComponent#getDebugData() + */ + public Map getDebugData() + { + return FacesContext.getCurrentInstance().getExternalContext().getRequestHeaderMap(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestParams.java b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestParams.java new file mode 100644 index 0000000000..9dce57c0c8 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestParams.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.util.Map; + +import javax.faces.context.FacesContext; + +/** + * Component which displays the parameters for the current HTTP request + * + * @author gavinc + */ +public class UIHttpRequestParams extends BaseDebugComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.debug.HttpRequestParams"; + } + + /** + * @see org.alfresco.web.ui.common.component.debug.BaseDebugComponent#getDebugData() + */ + public Map getDebugData() + { + return FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestState.java b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestState.java new file mode 100644 index 0000000000..19591623af --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpRequestState.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.util.Map; + +import javax.faces.context.FacesContext; + +/** + * Component which displays the current state of the HTTP request + * + * @author gavinc + */ +public class UIHttpRequestState extends BaseDebugComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.debug.HttpRequestState"; + } + + /** + * @see org.alfresco.web.ui.common.component.debug.BaseDebugComponent#getDebugData() + */ + public Map getDebugData() + { + return FacesContext.getCurrentInstance().getExternalContext().getRequestMap(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/UIHttpSessionState.java b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpSessionState.java new file mode 100644 index 0000000000..252a189b7e --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/UIHttpSessionState.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.util.Map; + +import javax.faces.context.FacesContext; + +/** + * Component which displays the current state of the HTTP session + * + * @author gavinc + */ +public class UIHttpSessionState extends BaseDebugComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.debug.HttpSessionState"; + } + + /** + * @see org.alfresco.web.ui.common.component.debug.BaseDebugComponent#getDebugData() + */ + public Map getDebugData() + { + return FacesContext.getCurrentInstance().getExternalContext().getSessionMap(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/debug/UISystemProperties.java b/source/java/org/alfresco/web/ui/common/component/debug/UISystemProperties.java new file mode 100644 index 0000000000..5ec200545f --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/debug/UISystemProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.debug; + +import java.util.Map; + +/** + * Component which displays the system properties of the VM + * + * @author gavinc + */ +public class UISystemProperties extends BaseDebugComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.debug.SystemProperties"; + } + + /** + * @see org.alfresco.web.ui.common.component.debug.BaseDebugComponent#getDebugData() + */ + public Map getDebugData() + { + return System.getProperties(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/description/UIDescription.java b/source/java/org/alfresco/web/ui/common/component/description/UIDescription.java new file mode 100644 index 0000000000..3bac55508d --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/description/UIDescription.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.description; + +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +import org.alfresco.web.ui.common.component.SelfRenderingComponent; + +/** + * Description component that outputs a dynamic description + * + * @author gavinc + */ +public class UIDescription extends SelfRenderingComponent +{ + private String controlValue; + private String text; + + /** + * @return The control value the description is for + */ + public String getControlValue() + { + if (this.controlValue == null) + { + ValueBinding vb = getValueBinding("controlValue"); + if (vb != null) + { + this.controlValue = (String)vb.getValue(getFacesContext()); + } + } + + return this.controlValue; + } + + /** + * @param controlValue Sets the control value this description is for + */ + public void setControlValue(String controlValue) + { + this.controlValue = controlValue; + } + + /** + * @return Returns the description text + */ + public String getText() + { + if (this.text == null) + { + ValueBinding vb = getValueBinding("text"); + if (vb != null) + { + this.text = (String)vb.getValue(getFacesContext()); + } + } + + return this.text; + } + + /** + * @param text Sets the description text + */ + public void setText(String text) + { + this.text = text; + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Description"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.controlValue = (String)values[1]; + this.text = (String)values[2]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[3]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.controlValue; + values[2] = this.text; + return (values); + } + + /** + * @see javax.faces.component.UIComponent#getRendersChildren() + */ + public boolean getRendersChildren() + { + return false; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/description/UIDescriptions.java b/source/java/org/alfresco/web/ui/common/component/description/UIDescriptions.java new file mode 100644 index 0000000000..e2a7c00f5c --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/description/UIDescriptions.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.description; + +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +import org.alfresco.web.ui.common.component.SelfRenderingComponent; + +/** + * Descriptions component that outputs descriptions held in a backing object + * + * @author gavinc + */ +public class UIDescriptions extends SelfRenderingComponent +{ + private Object value; + + /** + * @return Returns the object holding the decriptions + */ + public Object getValue() + { + if (this.value == null) + { + ValueBinding vb = getValueBinding("value"); + if (vb != null) + { + this.value = vb.getValue(getFacesContext()); + } + } + + return this.value; + } + + /** + * @param value Sets the object holding the description + */ + public void setValue(Object value) + { + this.value = value; + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.Descriptions"; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.value = values[1]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.value; + return (values); + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/description/UIDynamicDescription.java b/source/java/org/alfresco/web/ui/common/component/description/UIDynamicDescription.java new file mode 100644 index 0000000000..ad5bdc2f68 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/description/UIDynamicDescription.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.description; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; + +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.SelfRenderingComponent; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Dynamic description component that switches text based on the events + * of another input control + * + * @author gavinc + */ +public class UIDynamicDescription extends SelfRenderingComponent +{ + private static Log logger = LogFactory.getLog(UIDynamicDescription.class); + private String selected; + private String functionName; + + /** + * @return The id of the selected description + */ + public String getSelected() + { + if (this.selected == null) + { + ValueBinding vb = getValueBinding("selected"); + if (vb != null) + { + this.selected = (String)vb.getValue(getFacesContext()); + } + } + + return this.selected; + } + + /** + * @param selected The id of the selected + */ + public void setSelected(String selected) + { + this.selected = selected; + } + + /** + * @return Returns the JavaScript function name to use + */ + public String getFunctionName() + { + if (this.functionName == null) + { + this.functionName = "itemSelected"; + } + + return this.functionName; + } + + /** + * @param functionName Sets the name of the JavaScript function to use + */ + public void setFunctionName(String functionName) + { + this.functionName = functionName; + } + + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public String getFamily() + { + return "org.alfresco.faces.DynamicDescription"; + } + + /** + * @see javax.faces.component.UIComponent#encodeBegin(javax.faces.context.FacesContext) + */ + public void encodeBegin(FacesContext context) throws IOException + { + if (this.isRendered() == false) + { + return; + } + + // output the required JavaScript + ResponseWriter out = context.getResponseWriter(); + + out.write("\n"); + } + + /** + * @see javax.faces.component.UIComponent#encodeChildren(javax.faces.context.FacesContext) + */ + public void encodeChildren(FacesContext context) throws IOException + { + if (this.isRendered() == false) + { + return; + } + + List kids = getChildren(); + for (UIComponent child : kids) + { + if (child instanceof UIDescription) + { + // render the single description + renderDescription(context, ((UIDescription)child).getControlValue(), + ((UIDescription)child).getText()); + } + else if (child instanceof UIDescriptions) + { + // retrieve the object being pointed to and get + // the descriptions from that + renderDescriptions(context, (UIDescriptions)child); + } + } + } + + /** + * @see javax.faces.component.UIComponent#encodeEnd(javax.faces.context.FacesContext) + */ + public void encodeEnd(FacesContext context) throws IOException + { + // don't need to do anything + } + + /** + * @see javax.faces.component.UIComponent#getRendersChildren() + */ + public boolean getRendersChildren() + { + return true; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.selected = (String)values[1]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.selected; + return (values); + } + + /** + * Renders a description item + * + * @param context The faces context + * @param controlId The id of the control the description is for + * @param test The description text + */ + private void renderDescription(FacesContext context, String controlId, String text) + throws IOException + { + ResponseWriter out = context.getResponseWriter(); + out.write(""); + out.write(Utils.encode(text)); + out.write("\n"); + } + + /** + * Renders the given descriptions component + * + * @param context The faces context + * @param descriptions The descriptions to render + */ + private void renderDescriptions(FacesContext context, UIDescriptions descriptions) + throws IOException + { + // get hold of the object holding the descriptions and make sure + // it is of the correct type + Object obj = descriptions.getValue(); + + if (obj instanceof Map) + { + Map items = (Map)obj; + for (String id : items.keySet()) + { + renderDescription(context, id, items.get(id)); + } + } + else if (obj instanceof List) + { + Iterator iter = ((List)obj).iterator(); + while (iter.hasNext()) + { + UIDescription desc = (UIDescription)iter.next(); + renderDescription(context, desc.getControlValue(), desc.getText()); + } + } + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/evaluator/BaseEvaluator.java b/source/java/org/alfresco/web/ui/common/component/evaluator/BaseEvaluator.java new file mode 100644 index 0000000000..58d38b667a --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/evaluator/BaseEvaluator.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.evaluator; + +import java.io.IOException; + +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +import org.apache.log4j.Logger; + +import org.alfresco.web.ui.common.component.SelfRenderingComponent; + +/** + * @author kevinr + */ +public abstract class BaseEvaluator extends SelfRenderingComponent +{ + /** + * @see javax.faces.component.UIComponent#getFamily() + */ + public final String getFamily() + { + return "org.alfresco.faces.evaluators"; + } + + /** + * @see javax.faces.component.UIComponentBase#getRendersChildren() + */ + public final boolean getRendersChildren() + { + return !evaluate(); + } + + /** + * @see javax.faces.component.UIComponentBase#encodeBegin(javax.faces.context.FacesContext) + */ + public final void encodeBegin(FacesContext context) throws IOException + { + // no output for this component + } + + /** + * @see javax.faces.component.UIComponentBase#encodeChildren(javax.faces.context.FacesContext) + */ + public final void encodeChildren(FacesContext context) throws IOException + { + // if this is called, then the evaluate returned false which means + // the child components show not be allowed to render themselves + } + + /** + * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext) + */ + public final void encodeEnd(FacesContext context) throws IOException + { + // no output for this component + } + + /** + * Get the value for this component to be evaluated against + * + * @return the value for this component to be evaluated against + */ + public Object getValue() + { + ValueBinding vb = getValueBinding("value"); + if (vb != null) + { + this.value = vb.getValue(getFacesContext()); + } + + return this.value; + } + + /** + * Set the value for this component to be evaluated against + * + * @param value the value for this component to be evaluated against + */ + public void setValue(Object value) + { + this.value = value; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.value = values[1]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.value; + return (values); + } + + /** + * Evaluate against the component attributes. Return true to allow the inner + * components to render, false to hide them during rendering. + * + * @return true to allow rendering of child components, false otherwise + */ + public abstract boolean evaluate(); + + + protected static Logger s_logger = Logger.getLogger(BaseEvaluator.class); + + /** the value to be evaluated against */ + private Object value; +} diff --git a/source/java/org/alfresco/web/ui/common/component/evaluator/BooleanEvaluator.java b/source/java/org/alfresco/web/ui/common/component/evaluator/BooleanEvaluator.java new file mode 100644 index 0000000000..f28e8835fb --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/evaluator/BooleanEvaluator.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.evaluator; + +/** + * @author kevinr + * + * Evaluates to true if the value suppied is a boolean string of "true". + */ +public class BooleanEvaluator extends BaseEvaluator +{ + /** + * Evaluate against the component attributes. Return true to allow the inner + * components to render, false to hide them during rendering. + * + * @return true to allow rendering of child components, false otherwise + */ + public boolean evaluate() + { + boolean result = false; + + try + { + if (getValue() instanceof Boolean) + { + result = ((Boolean)getValue()).booleanValue(); + } + else + { + result = Boolean.valueOf((String)getValue()).booleanValue(); + } + } + catch (Exception err) + { + // return default value on error + s_logger.debug("Unable to evaluate value to boolean: " + getValue()); + } + + return result; + } +} diff --git a/source/java/org/alfresco/web/ui/common/component/evaluator/StringEqualsEvaluator.java b/source/java/org/alfresco/web/ui/common/component/evaluator/StringEqualsEvaluator.java new file mode 100644 index 0000000000..207e0d1ae9 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/evaluator/StringEqualsEvaluator.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.evaluator; + +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; + +/** + * @author kevinr + * + * Evaluates to true if the value exactly matches the supplied string condition. + */ +public class StringEqualsEvaluator extends BaseEvaluator +{ + /** + * Evaluate against the component attributes. Return true to allow the inner + * components to render, false to hide them during rendering. + * + * @return true to allow rendering of child components, false otherwise + */ + public boolean evaluate() + { + boolean result = false; + + try + { + result = getCondition().equals((String)getValue()); + } + catch (Exception err) + { + // return default value on error + s_logger.debug("Expected String value for evaluation: " + getValue()); + } + + return result; + } + + /** + * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext, java.lang.Object) + */ + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.condition = (String)values[1]; + } + + /** + * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext) + */ + public Object saveState(FacesContext context) + { + Object values[] = new Object[2]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.condition; + return (values); + } + + /** + * Get the string condition to match value against + * + * @return the string condition to match value against + */ + public String getCondition() + { + ValueBinding vb = getValueBinding("condition"); + if (vb != null) + { + this.condition = (String)vb.getValue(getFacesContext()); + } + + return this.condition; + } + + /** + * Set the string condition to match value against + * + * @param condition string condition to match value against + */ + public void setCondition(String condition) + { + this.condition = condition; + } + + + /** the string condition to match value against */ + private String condition = null; +} diff --git a/source/java/org/alfresco/web/ui/common/component/evaluator/ValueSetEvaluator.java b/source/java/org/alfresco/web/ui/common/component/evaluator/ValueSetEvaluator.java new file mode 100644 index 0000000000..24427c0922 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/component/evaluator/ValueSetEvaluator.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.component.evaluator; + +/** + * @author kevinr + * + * Evaluates to true if the value supplied is not null. + */ +public class ValueSetEvaluator extends BaseEvaluator +{ + /** + * Evaluate against the component attributes. Return true to allow the inner + * components to render, false to hide them during rendering. + * + * @return true to allow rendering of child components, false otherwise + */ + public boolean evaluate() + { + return getValue() != null ? true : false; + } +} diff --git a/source/java/org/alfresco/web/ui/common/converter/BooleanLabelConverter.java b/source/java/org/alfresco/web/ui/common/converter/BooleanLabelConverter.java new file mode 100644 index 0000000000..99e3f72176 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/converter/BooleanLabelConverter.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.converter; + +import java.util.ResourceBundle; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.Converter; +import javax.faces.convert.ConverterException; + +import org.alfresco.web.app.Application; + +/** + * Converter class to convert a Boolean value (including null) into a human readable form. + * + * @author Kevin Roast + */ +public class BooleanLabelConverter implements Converter +{ + /** + *

The standard converter id for this converter.

+ */ + public static final String CONVERTER_ID = "org.alfresco.faces.BooleanLabelConverter"; + + private static final String MSG_YES = "yes"; + private static final String MSG_NO = "no"; + + /** + * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String) + */ + public Object getAsObject(FacesContext context, UIComponent component, String value) + throws ConverterException + { + return Boolean.valueOf(value); + } + + /** + * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object) + */ + public String getAsString(FacesContext context, UIComponent component, Object value) + throws ConverterException + { + ResourceBundle bundle = Application.getBundle(context); + + String result = bundle.getString(MSG_NO); + + if (value instanceof Boolean) + { + result = ((Boolean)value).booleanValue() ? bundle.getString(MSG_YES) : bundle.getString(MSG_NO); + } + + return result; + } +} diff --git a/source/java/org/alfresco/web/ui/common/converter/ByteSizeConverter.java b/source/java/org/alfresco/web/ui/common/converter/ByteSizeConverter.java new file mode 100644 index 0000000000..88e127cb5e --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/converter/ByteSizeConverter.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.converter; + +import java.text.DecimalFormat; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.Converter; + +import org.alfresco.web.app.Application; + +/** + * Converter class to convert the size of an item in bytes into a readable KB/MB form. + * + * @author Kevin Roast + */ +public class ByteSizeConverter implements Converter +{ + /** + *

The standard converter id for this converter.

+ */ + public static final String CONVERTER_ID = "org.alfresco.faces.ByteSizeConverter"; + + private static final String MSG_POSTFIX_KB = "kilobyte"; + private static final String MSG_POSTFIX_MB = "megabyte"; + private static final String MSG_POSTFIX_GB = "gigabyte"; + + private static final String NUMBER_PATTERN = "###,###.##"; + + /** + * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String) + */ + public Object getAsObject(FacesContext context, UIComponent component, String value) + { + return Long.parseLong(value); + } + + /** + * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object) + */ + public String getAsString(FacesContext context, UIComponent component, Object value) + { + long size; + if (value instanceof Long) + { + size = (Long)value; + } + else if (value instanceof String) + { + try + { + size = Long.parseLong((String)value); + } + catch (NumberFormatException ne) + { + return (String)value; + } + } + else + { + return ""; + } + + // get formatter + // TODO: can we cache this instance...? DecimalFormat is not threadsafe! Need threadlocal instance. + DecimalFormat formatter = new DecimalFormat(NUMBER_PATTERN); + + StringBuilder buf = new StringBuilder(); + + if (size < 999999) + { + double val = ((double)size) / 1024.0; + buf.append(formatter.format(val)) + .append(' ') + .append(Application.getMessage(context, MSG_POSTFIX_KB)); + } + else if (size < 999999999) + { + double val = ((double)size) / 1048576.0; + buf.append(formatter.format(val)) + .append(' ') + .append(Application.getMessage(context, MSG_POSTFIX_MB)); + } + else + { + double val = ((double)size) / 1073741824.0; + buf.append(formatter.format(val)) + .append(' ') + .append(Application.getMessage(context, MSG_POSTFIX_GB)); + } + + return buf.toString(); + } +} diff --git a/source/java/org/alfresco/web/ui/common/converter/XMLDateConverter.java b/source/java/org/alfresco/web/ui/common/converter/XMLDateConverter.java new file mode 100644 index 0000000000..55d2207418 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/converter/XMLDateConverter.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.converter; + +import java.util.Date; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.DateTimeConverter; + +import org.alfresco.util.ISO8601DateFormat; + +/** + * Converter class to convert an XML date representation into a Date + * + * @author gavinc + */ +public class XMLDateConverter extends DateTimeConverter +{ + /** + *

The standard converter id for this converter.

+ */ + public static final String CONVERTER_ID = "org.alfresco.faces.XMLDataConverter"; + + /** + * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String) + */ + public Object getAsObject(FacesContext context, UIComponent component, String value) + { + return ISO8601DateFormat.parse(value); + } + + /** + * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object) + */ + public String getAsString(FacesContext context, UIComponent component, Object value) + { + String str = null; + + if (value instanceof String) + { + Date date = ISO8601DateFormat.parse((String)value); + str = super.getAsString(context, component, date); + } + else + { + str = super.getAsString(context, component, value); + } + + return str; + } + + +} diff --git a/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java new file mode 100644 index 0000000000..53bd5c5a24 --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.renderer; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; + +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIMenu; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author kevinr + */ +public class ActionLinkRenderer extends BaseRenderer +{ + private static Log logger = LogFactory.getLog(ActionLinkRenderer.class); + + // ------------------------------------------------------------------------------ + // Renderer implementation + + /** + * @see javax.faces.render.Renderer#decode(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + public void decode(FacesContext context, UIComponent component) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = Utils.getActionHiddenFieldName(context, component); + String value = (String)requestMap.get(fieldId); + // we are clicked if the hidden field contained our client id + if (value != null && value.equals(component.getClientId(context))) + { + // get all the params for this actionlink, see if any values have been set + // on the request which match our params and set them into the component + UIActionLink link = (UIActionLink)component; + Map destParams = link.getParameterMap(); + destParams.clear(); + Map actionParams = getParameterMap(link); + if (actionParams != null) + { + for (String name : actionParams.keySet()) + { + String paramValue = (String)requestMap.get(name); + destParams.put(name, paramValue); + } + } + + ActionEvent event = new ActionEvent(component); + component.queueEvent(event); + } + } + + /** + * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + public void encodeEnd(FacesContext context, UIComponent component) throws IOException + { + // always check for this flag - as per the spec + if (component.isRendered() == true) + { + Writer out = context.getResponseWriter(); + + UIActionLink link = (UIActionLink)component; + + if (isInMenu(link) == true) + { + // render as menu item + out.write( renderMenuAction(context, link) ); + } + else + { + // render as action link + out.write( renderActionLink(context, link) ); + } + } + } + + /** + * Render ActionLink as plain link and image + * + * @param context + * @param link + * + * @return action link HTML + */ + private String renderActionLink(FacesContext context, UIActionLink link) + { + Map attrs = link.getAttributes(); + StringBuilder linkBuf = new StringBuilder(256); + + if (link.getHref() == null) + { + linkBuf.append(""); + } + + return buf.toString(); + } + + /** + * Render ActionLink as menu image and item link + * + * @param context + * @param link + * + * @return action link HTML + */ + private String renderMenuAction(FacesContext context, UIActionLink link) + { + StringBuilder buf = new StringBuilder(256); + + buf.append(""); + + // render image cell first for a menu + if (link.getImage() != null) + { + buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue())); + } + + buf.append(""); + + Map attrs = link.getAttributes(); + + // render text link cell for the menu + if (attrs.get("href") == null) + { + buf.append("'); + buf.append(Utils.encode(link.getValue().toString())); + buf.append(""); + + buf.append(""); + + return buf.toString(); + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Return true if the action link is present within a UIMenu component container + * + * @param link The ActionLink to test + * + * @return true if the action link is present within a UIMenu component + */ + private static boolean isInMenu(UIActionLink link) + { + UIComponent parent = link.getParent(); + while (parent != null) + { + if (parent instanceof UIMenu) + { + break; + } + parent = parent.getParent(); + } + return (parent != null); + } +} diff --git a/source/java/org/alfresco/web/ui/common/renderer/BaseRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/BaseRenderer.java new file mode 100644 index 0000000000..e009bc995c --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/renderer/BaseRenderer.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.renderer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.faces.component.UIComponent; +import javax.faces.component.UIParameter; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.render.Renderer; + +/** + * Base renderer class. Contains helper methods to assist most renderers. + * + * @author kevinr + */ +public abstract class BaseRenderer extends Renderer +{ + /** + * Helper to output an attribute to the output stream + * + * @param out ResponseWriter + * @param attr attribute value object (cannot be null) + * @param mapping mapping to output as e.g. style="..." + * + * @throws IOException + */ + protected static void outputAttribute(ResponseWriter out, Object attr, String mapping) + throws IOException + { + if (attr != null) + { + out.write(' '); + out.write(mapping); + out.write("=\""); + out.write(attr.toString()); + out.write('"'); + } + } + + /** + * Ensures that the given context and component are not null. This method + * should be called by all renderer methods that are given these parameters. + * + * @param ctx Faces context + * @param component The component + */ + protected static void assertParmeters(FacesContext ctx, UIComponent component) + { + if (ctx == null) + { + throw new IllegalStateException("context can not be null"); + } + + if (component == null) + { + throw new IllegalStateException("component can not be null"); + } + } + + /** + * Return the map of name/value pairs for any child UIParameter components. + * + * @param component to find UIParameter child values for + * + * @return a Map of name/value pairs or null if none found + */ + protected static Map getParameterMap(UIComponent component) + { + Map params = null; + + if (component.getChildCount() != 0) + { + params = new HashMap(3, 1.0f); + for (Iterator i=component.getChildren().iterator(); i.hasNext(); /**/) + { + UIComponent child = (UIComponent)i.next(); + if (child instanceof UIParameter) + { + UIParameter param = (UIParameter)child; + params.put(param.getName(), (String)param.getValue()); + } + } + } + + return params; + } +} diff --git a/source/java/org/alfresco/web/ui/common/renderer/BreadcrumbRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/BreadcrumbRenderer.java new file mode 100644 index 0000000000..e43033232a --- /dev/null +++ b/source/java/org/alfresco/web/ui/common/renderer/BreadcrumbRenderer.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.ui.common.renderer; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; +import java.util.Map; + +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; + +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.IBreadcrumbHandler; +import org.alfresco.web.ui.common.component.UIBreadcrumb; + +/** + * Renderer class for the UIBreadcrumb component + * + * @author Kevin Roast + */ +public class BreadcrumbRenderer extends BaseRenderer +{ + // ------------------------------------------------------------------------------ + // Renderer implementation + + /** + * @see javax.faces.render.Renderer#decode(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + public void decode(FacesContext context, UIComponent component) + { + Map requestMap = context.getExternalContext().getRequestParameterMap(); + String fieldId = getHiddenFieldName(context, component); + String value = (String)requestMap.get(fieldId); + if (value != null && value.length() != 0) + { + // create a breadcrumb specific action event if we were clicked + int selectedIndex = Integer.parseInt(value); + UIBreadcrumb.BreadcrumbEvent event = new UIBreadcrumb.BreadcrumbEvent(component, selectedIndex); + component.queueEvent(event); + } + } + + /** + * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent) + */ + public void encodeBegin(FacesContext context, UIComponent component) throws IOException + { + // always check for this flag - as per the spec + if (component.isRendered() == true) + { + Writer out = context.getResponseWriter(); + + UIBreadcrumb breadcrumb = (UIBreadcrumb)component; + // get the List of IBreadcrumbHandler elements from the component + List elements = (List)breadcrumb.getValue(); + + boolean first = true; + for (int index=0; index