/*
* Copyright 2012 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.artificer.ui.client.local.pages;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextArea;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import org.artificer.ui.client.local.ClientMessages;
import org.artificer.ui.client.local.events.ReloadHandler;
import org.artificer.ui.client.local.pages.artifacts.CommentsPanel;
import org.artificer.ui.client.local.pages.details.AddCustomPropertyDialog;
import org.artificer.ui.client.local.pages.details.AddRelationshipDialog;
import org.artificer.ui.client.local.pages.details.ClassifiersPanel;
import org.artificer.ui.client.local.pages.details.CustomPropertiesPanel;
import org.artificer.ui.client.local.pages.details.DeleteDialog;
import org.artificer.ui.client.local.pages.details.DescriptionInlineLabel;
import org.artificer.ui.client.local.pages.details.ModifyClassifiersDialog;
import org.artificer.ui.client.local.pages.details.RelationshipActionHandler;
import org.artificer.ui.client.local.pages.details.RelationshipsTable;
import org.artificer.ui.client.local.pages.details.SourceEditor;
import org.artificer.ui.client.local.services.ApplicationStateService;
import org.artificer.ui.client.local.services.ArtifactServiceCaller;
import org.artificer.ui.client.local.services.NotificationService;
import org.artificer.ui.client.local.services.callback.IServiceInvocationHandler;
import org.artificer.ui.client.local.util.DOMUtil;
import org.artificer.ui.client.local.util.DataBindingDateConverter;
import org.artificer.ui.client.local.widgets.common.EditableInlineLabel;
import org.artificer.ui.client.shared.beans.ArtifactBean;
import org.artificer.ui.client.shared.beans.ArtifactCommentBean;
import org.artificer.ui.client.shared.beans.ArtifactRelationshipsIndexBean;
import org.artificer.ui.client.shared.beans.NotificationBean;
import org.jboss.errai.databinding.client.api.DataBinder;
import org.jboss.errai.databinding.client.api.InitialState;
import org.jboss.errai.databinding.client.api.PropertyChangeEvent;
import org.jboss.errai.databinding.client.api.PropertyChangeHandler;
import org.jboss.errai.ui.nav.client.local.Navigation;
import org.jboss.errai.ui.nav.client.local.Page;
import org.jboss.errai.ui.nav.client.local.PageShown;
import org.jboss.errai.ui.nav.client.local.PageState;
import org.jboss.errai.ui.nav.client.local.TransitionAnchor;
import org.jboss.errai.ui.shared.api.annotations.AutoBound;
import org.jboss.errai.ui.shared.api.annotations.Bound;
import org.jboss.errai.ui.shared.api.annotations.DataField;
import org.jboss.errai.ui.shared.api.annotations.EventHandler;
import org.jboss.errai.ui.shared.api.annotations.Templated;
import org.overlord.commons.gwt.client.local.widgets.HtmlSnippet;
/**
* The page shown to the user when she clicks on one of the artifacts
* displayed in the Artifacts Table on the Artifacts page.
*
* @author eric.wittmann@redhat.com
*/
@Templated("/org/artificer/ui/client/local/site/artifact-details.html#page")
@Page(path="details")
@Dependent
public class ArtifactDetailsPage extends AbstractPage {
@Inject
protected ClientMessages i18n;
@Inject
protected ArtifactServiceCaller artifactService;
@Inject
protected NotificationService notificationService;
@Inject
protected ApplicationStateService stateService;
@Inject
private Navigation navigation;
protected ArtifactBean currentArtifact;
@PageState
private String uuid;
@Inject @AutoBound
protected DataBinder<ArtifactBean> artifact;
// Breadcrumbs
@Inject @DataField("back-to-dashboard")
TransitionAnchor<DashboardPage> backToDashboard;
@Inject @DataField("back-to-artifacts")
TransitionAnchor<ArtifactsPage> backToArtifacts;
@Inject @DataField("feed-link")
Anchor feedLink;
// Actions
@Inject @DataField("btn-delete")
Button deleteButton;
@Inject
DeleteDialog deleteDialog;
// Overview tab
@Inject @DataField("core-property-name") @Bound(property="name")
EditableInlineLabel name;
@Inject @DataField("core-property-version") @Bound(property="version")
InlineLabel version;
@Inject @DataField("core-property-type-1") @Bound(property="type")
InlineLabel htype;
@Inject @DataField("link-repository-content")
Anchor linkToRepositoryContent;
@Inject @DataField("link-repository-metadata")
Anchor linkToRepositoryMetadata;
@Inject @DataField("core-property-type-2") @Bound(property="type")
Label type;
@Inject @DataField("core-property-uuid") @Bound(property="uuid")
Label uuidField;
@Inject @DataField("core-property-derived") @Bound(property="derived")
Label derived;
@Inject @DataField("core-property-createdOn") @Bound(property="createdOn", converter=DataBindingDateConverter.class)
InlineLabel createdOn;
@Inject @DataField("core-property-createdBy") @Bound(property="createdBy")
InlineLabel createdBy;
@Inject @DataField("core-property-modifiedOn") @Bound(property="updatedOn", converter=DataBindingDateConverter.class)
InlineLabel modifiedOn;
@Inject @DataField("core-property-modifiedBy") @Bound(property="updatedBy")
InlineLabel modifiedBy;
@Inject @DataField("custom-properties-container") @Bound(property="properties")
CustomPropertiesPanel customProperties;
@Inject @DataField("add-property-button")
Button addProperty;
@Inject
Instance<AddCustomPropertyDialog> addPropertyDialogFactory;
@Inject @DataField("classifiers-container") @Bound(property="classifiedBy")
ClassifiersPanel classifiers;
@Inject @DataField("modify-classifiers-button")
Button modifyClassifier;
@Inject
Instance<ModifyClassifiersDialog> modifyClassifiersDialogFactory;
@Inject @DataField("core-property-description") @Bound(property="description")
DescriptionInlineLabel description;
// Relationships tab
@Inject @DataField("sramp-artifact-tabs-relationships")
Anchor relationshipsTabAnchor;
@Inject @DataField("relationship-tab-progress")
HtmlSnippet relationshipsTabProgress;
@Inject @DataField("relationships-table")
RelationshipsTable relationships;
@Inject @DataField("reverse-relationships-table")
RelationshipsTable reverseRelationships;
@Inject @DataField("relationships-header")
InlineLabel relationshipsHeader;
@Inject @DataField("reverse-relationships-header")
InlineLabel reverseRelationshipsHeader;
@Inject @DataField("btn-add-relationship")
Button addRelationshipButton;
@Inject
Instance<AddRelationshipDialog> addRelationshipDialogFactory;
protected boolean relationshipsLoaded;
// Comments tab
@Inject @DataField("sramp-artifact-tabs-comments")
Anchor commentsTabAnchor;
@Inject @DataField("add-comment-text")
TextArea commentText;
@Inject @DataField("add-comment-form-submit-button")
Button commentButton;
@Inject @DataField("comments-container")
CommentsPanel comments;
protected boolean commentsLoaded;
// Source tab
@Inject @DataField("sramp-artifact-tabs-source")
Anchor sourceTabAnchor;
@Inject @DataField("source-tab-progress")
HtmlSnippet sourceTabProgress;
@Inject @DataField("artifact-editor")
SourceEditor sourceEditor;
protected boolean sourceLoaded;
@Inject @DataField("artifact-details-loading-spinner")
protected HtmlSnippet artifactLoading;
protected Element pageContent;
protected Element editorWrapper;
/**
* Constructor.
*/
public ArtifactDetailsPage() {
}
/**
* Called after the widget is constructed.
*/
@PostConstruct
protected void onPostConstruct() {
pageContent = DOMUtil.findElementById(getElement(), "artifact-details-content-wrapper");
pageContent.addClassName("hide");
editorWrapper = DOMUtil.findElementById(getElement(), "editor-wrapper");
artifact.addPropertyChangeHandler(new PropertyChangeHandler<Object>() {
@Override
public void onPropertyChange(PropertyChangeEvent<Object> event) {
pushModelToServer();
}
});
deleteDialog.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
onDeleteConfirm();
}
});
name.setDialogTitle(i18n.format("artifact-details.edit-name"));
name.setDialogLabel(i18n.format("artifact-details.new-name"));
description.setDialogTitle(i18n.format("artifact-details.edit-descr"));
description.setDialogLabel(i18n.format("artifact-details.new-descr"));
}
/**
* @see AbstractPage#onPageShowing()
*/
@Override
protected void onPageShowing() {
sourceLoaded = false;
currentArtifact = null;
pageContent.addClassName("hide");
artifactLoading.getElement().removeClassName("hide");
sourceTabAnchor.setVisible(false);
editorWrapper.setAttribute("style", "display:none");
relationshipsTabAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!relationshipsLoaded) {
loadRelationships(currentArtifact);
}
relationshipsTabAnchor.setFocus(false);
}
});
sourceTabAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!sourceLoaded) {
loadSource(currentArtifact);
}
sourceTabAnchor.setFocus(false);
}
});
commentsTabAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!commentsLoaded) {
loadComments(currentArtifact);
}
commentsTabAnchor.setFocus(false);
}
});
}
@PageShown
public void onPageShown() {
artifactService.get(uuid, new IServiceInvocationHandler<ArtifactBean>() {
@Override
public void onReturn(ArtifactBean data) {
currentArtifact = data;
update(data);
}
@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("artifact-details.error-getting-details"), error);
}
});
}
/**
* Called when the user clicks the Add Property button.
* @param event
*/
@EventHandler("add-property-button")
protected void onAddProperty(ClickEvent event) {
AddCustomPropertyDialog dialog = addPropertyDialogFactory.get();
dialog.addValueChangeHandler(new ValueChangeHandler<Map.Entry<String,String>>() {
@Override
public void onValueChange(ValueChangeEvent<Entry<String, String>> event) {
Entry<String, String> value = event.getValue();
if (value != null) {
String propName = value.getKey();
String propValue = value.getValue();
Map<String, String> newProps = new HashMap<String,String>(artifact.getModel().getProperties());
newProps.put(propName, propValue);
customProperties.setValue(newProps, true);
}
}
});
dialog.show();
}
/**
* Called when the user clicks the Delete button.
* @param event
*/
@EventHandler("btn-delete")
protected void onDeleteClick(ClickEvent event) {
deleteDialog.setLabel(artifact.getModel().getName());
deleteDialog.show();
}
/**
* Called when the user confirms the artifact deletion.
*/
protected void onDeleteConfirm() {
final NotificationBean notificationBean = notificationService.startProgressNotification(
i18n.format("artifact-details.deleting-artifact-title"),
i18n.format("artifact-details.deleting-artifact-msg", artifact.getModel().getName()));
artifactService.delete(artifact.getModel(), new IServiceInvocationHandler<Void>() {
@Override
public void onReturn(Void data) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("artifact-details.artifact-deleted"),
i18n.format("artifact-details.delete-success-msg", artifact.getModel().getName()));
backToArtifacts.click();
}
@Override
public void onError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("artifact-details.delete-error"),
error);
}
});
}
/**
* Called when the user clicks the Add Property button.
* @param event
*/
@EventHandler("modify-classifiers-button")
protected void onModifyClassifiers(ClickEvent event) {
ModifyClassifiersDialog dialog = modifyClassifiersDialogFactory.get();
dialog.addValueChangeHandler(new ValueChangeHandler<List<String>>() {
@Override
public void onValueChange(ValueChangeEvent<List<String>> event) {
List<String> value = event.getValue();
if (value != null) {
List<String> newValue = new ArrayList<String>(value);
classifiers.setValue(newValue, true);
}
}
});
dialog.setValue(artifact.getModel().getClassifiedBy());
dialog.show();
}
/**
* Loads the artifact's relationships and displays them in the proper table.
* @param artifact
*/
protected void loadRelationships(final ArtifactBean artifact) {
relationships.setVisible(false);
relationshipsTabProgress.setVisible(true);
relationshipsHeader.setText("Targeted by " + artifact.getName());
reverseRelationshipsHeader.setText("Targets " + artifact.getName());
relationshipsTabProgress.setVisible(false);
relationships.setVisible(true);
reverseRelationships.setVisible(true);
relationshipsLoaded = true;
relationships.setRelationshipActionHandler(new RelationshipActionHandler() {
@Override
public void editRelationship(String oldRelationshipType, String newRelationshipType, String uuid,
IServiceInvocationHandler<Void> deleteArtifactHandler) {
// delete relationships originating from this artifact
artifactService.editRelationship(oldRelationshipType, newRelationshipType, artifact.getUuid(), uuid,
deleteArtifactHandler);
}
@Override
public void deleteRelationship(String relationshipType, String uuid,
IServiceInvocationHandler<Void> deleteArtifactHandler) {
// delete relationships originating from this artifact
artifactService.deleteRelationship(relationshipType, artifact.getUuid(), uuid,
deleteArtifactHandler);
}
});
reverseRelationships.setRelationshipActionHandler(new RelationshipActionHandler() {
@Override
public void editRelationship(String oldRelationshipType, String newRelationshipType, String uuid,
IServiceInvocationHandler<Void> deleteArtifactHandler) {
// delete relationships originating from this artifact
artifactService.editRelationship(oldRelationshipType, newRelationshipType, uuid, artifact.getUuid(),
deleteArtifactHandler);
}
@Override
public void deleteRelationship(String relationshipType, String uuid,
IServiceInvocationHandler<Void> deleteArtifactHandler) {
// delete relationships targeting this artifact
artifactService.deleteRelationship(relationshipType, uuid, artifact.getUuid(),
deleteArtifactHandler);
}
});
relationships.setReloadHandler(new ReloadHandler() {
@Override
public void reload() {
doLoadRelationships(artifact);
}
});
reverseRelationships.setReloadHandler(new ReloadHandler() {
@Override
public void reload() {
doLoadRelationships(artifact);
}
});
doLoadRelationships(artifact);
}
private void doLoadRelationships(final ArtifactBean artifact) {
artifactService.getRelationships(artifact.getUuid(), artifact.getType(), new IServiceInvocationHandler<ArtifactRelationshipsIndexBean>() {
@Override
public void onReturn(ArtifactRelationshipsIndexBean data) {
relationships.setValue(data.getRelationships());
reverseRelationships.setValue(data.getReverseRelationships());
}
@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("artifact-details.error-getting-relationships"), error);
}
});
}
/**
* Called when the user clicks the Add Relationship button.
* @param event
*/
@EventHandler("btn-add-relationship")
protected void onAddRelationship(ClickEvent event) {
AddRelationshipDialog dialog = addRelationshipDialogFactory.get();
dialog.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
String relationshipType = event.getValue();
if (relationshipType != null) {
// set the app into "new relationship" mode
stateService.setNewRelationshipSourceUuid(uuid);
stateService.setNewRelationshipType(relationshipType);
// redirect to artifacts page
navigation.goTo("ArtifactsPage");
}
}
});
dialog.show();
}
/**
* Loads the artifact's source (async) into the source tab's editor control.
* @param artifact
*/
protected void loadSource(ArtifactBean artifact) {
if (!artifact.isTextDocument()) {
return;
}
sourceTabProgress.setVisible(true);
artifactService.getDocumentContent(artifact.getUuid(), artifact.getType(), new IServiceInvocationHandler<String>() {
@Override
public void onReturn(String data) {
sourceEditor.setValue(data);
sourceTabProgress.setVisible(false);
editorWrapper.removeAttribute("style");
sourceLoaded = true;
}
@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("Error getting artifact content."), error);
}
});
}
/**
* Called when the artifact meta data is loaded.
* @param artifact
*/
protected void update(ArtifactBean artifact) {
this.artifact.setModel(artifact, InitialState.FROM_MODEL);
this.feedLink.setHref(artifact.getRepositoryLink());
this.linkToRepositoryContent.setHref(artifact.getRepositoryMediaLink());
this.linkToRepositoryMetadata.setHref(artifact.getRepositoryLink());
this.sourceEditor.setValue("");
if (artifact.isTextDocument()) {
sourceTabAnchor.setVisible(true);
}
deleteButton.setVisible(!artifact.isDerived());
artifactLoading.getElement().addClassName("hide");
pageContent.removeClassName("hide");
if (stateService.inNewRelationshipMode() && stateService.getNewRelationshipSourceUuid().equals(uuid)) {
// ie, we've just gotten back from selecting relationship targets and saving it -- go directly to that tab
// TODO: Doesn't work.
// TODO: Consider just reloading the relationships, then showing a success message?
// relationshipsTabAnchor.fireEvent(new ClickEvent(){});
// reset the state
stateService.setNewRelationshipType(null);
stateService.setNewRelationshipSourceUuid(null);
}
}
/**
* Sends the model back up to the server (saves local changes).
*/
protected void pushModelToServer() {
String noteTitle = i18n.format("artifact-details.updating-artifact.title");
String noteBody = i18n.format("artifact-details.updating-artifact.message", artifact.getModel().getName());
final NotificationBean notificationBean = notificationService.startProgressNotification(
noteTitle, noteBody);
artifactService.update(artifact.getModel(), new IServiceInvocationHandler<Void>() {
@Override
public void onReturn(Void data) {
String noteTitle = i18n.format("artifact-details.updated-artifact.title");
String noteBody = i18n.format("artifact-details.updated-artifact.message", artifact.getModel().getName());
notificationService.completeProgressNotification(notificationBean.getUuid(),
noteTitle, noteBody);
}
@Override
public void onError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("artifact-details.error-updating-arty"),
error);
onPageShown();
}
});
}
protected void loadComments(final ArtifactBean artifact) {
comments.setValue(artifact.getComments());
commentButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
addComment(artifact);
}
});
commentsLoaded = true;
}
protected void addComment(final ArtifactBean artifact) {
String comment = commentText.getValue();
if (comment != null && comment.length() > 0) {
final NotificationBean notificationBean = notificationService.startProgressNotification(
i18n.format("artifact-details.adding-comment"),
i18n.format("artifact-details.adding-comment-msg", artifact.getName()));
artifactService.addComment(artifact.getUuid(), artifact.getType(), comment, new IServiceInvocationHandler<ArtifactCommentBean>() {
@Override
public void onReturn(ArtifactCommentBean data) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("artifact-details.comment-added"),
i18n.format("artifact-details.comment-added-success-msg", artifact.getName()));
// add to the list
comments.addComment(data);
}
@Override
public void onError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
i18n.format("artifact-details.error-adding-comment"),
error);
}
});
// reset form
commentText.setValue("");
}
}
}