added stuff for reverting merge operation

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/widgets/grsf-manage-widget@162895 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Costantino Perciante 2018-02-04 16:42:34 +00:00
parent 97806357e6
commit 0b794c5381
16 changed files with 701 additions and 126 deletions

View File

@ -12,7 +12,7 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/resources">
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
@ -23,7 +23,7 @@
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="${webappDirectory}/WEB-INF/classes" path="src/main/resources">
<classpathentry excluding="**" kind="src" output="${webappDirectory}/WEB-INF/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>

10
pom.xml
View File

@ -83,10 +83,6 @@
<artifactId>portal-manager</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gcube.portal</groupId>
<artifactId>notifications-common-library</artifactId>
</dependency>
<dependency>
<groupId>org.gcube.portlets.user</groupId>
<artifactId>gcube-url-shortener</artifactId>
@ -121,18 +117,18 @@
<dependency>
<groupId>org.gcube.core</groupId>
<artifactId>common-scope-maps</artifactId>
<scope>provided</scope>
<scope>compile</scope>
<!-- put at provided for deploying -->
</dependency>
<dependency>
<groupId>org.gcube.core</groupId>
<artifactId>common-encryption</artifactId>
<scope>provided</scope>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.gcube.common</groupId>
<artifactId>authorization-client</artifactId>
<scope>provided</scope>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>

View File

@ -12,6 +12,8 @@ public class GRSFManageWidget implements EntryPoint {
*/
public void onModuleLoad(){
// HandlerManager eventBus = new HandlerManager(null);
// RootPanel.get("manageDiv").add(new ManageProductWidget("fffb6167-b570-42a8-92b9-5be28549c3b8", eventBus));
//RootPanel.get("manageDiv").add(new ManageProductWidget("fffb6167-b570-42a8-92b9-5be28549c3b8", eventBus));
// RootPanel.get("manageDiv").add(new ManageRevertOperationWidget(
// "random-url-here", eventBus));
}
}

View File

@ -1,6 +1,7 @@
package org.gcube.datacatalogue.grsf_manage_widget.client;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperationInfo;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@ -45,10 +46,18 @@ public interface GRSFManageWidgetService extends RemoteService {
String checkIdentifierExistsInDomain(String id, String domain) throws Exception;
/**
* Check if the given url for reverting the operation is valid and send the request to the knowledge base
* Check if the given url for reverting the operation is valid and get back the needed info to proceed
* @param url
* @throws Exception
*/
void validateRevertOperation(String url) throws Exception;
RevertableOperationInfo validateRevertOperation(String url) throws Exception;
/**
* Perform a revert operation
* @param rInfo
* @return
* @throws Exception
*/
Boolean performRevertOperation(RevertableOperationInfo rInfo) throws Exception;
}

View File

@ -4,6 +4,7 @@
package org.gcube.datacatalogue.grsf_manage_widget.client;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperationInfo;
import com.google.gwt.user.client.rpc.AsyncCallback;
@ -27,6 +28,9 @@ public interface GRSFManageWidgetServiceAsync {
void checkIdentifierExistsInDomain(String id,
String domain, AsyncCallback<String> callback);
void validateRevertOperation(String url, AsyncCallback<Void> callback);
void validateRevertOperation(String url, AsyncCallback<RevertableOperationInfo> callback);
void performRevertOperation(RevertableOperationInfo rInfo,
AsyncCallback<Boolean> callback);
}

View File

@ -52,8 +52,7 @@ public class ManageProductWidget extends Composite{
private static GRSFManageWidgetServiceAsync service = GWT.create(GRSFManageWidgetService.class);
private static ManageProductWidgetUiBinder uiBinder = GWT
.create(ManageProductWidgetUiBinder.class);
private static ManageProductWidgetUiBinder uiBinder = GWT.create(ManageProductWidgetUiBinder.class);
interface ManageProductWidgetUiBinder extends
UiBinder<Widget, ManageProductWidget> {
@ -174,7 +173,7 @@ public class ManageProductWidget extends Composite{
// show modal
manageProductModal.addStyleName("management-metadata-modal-style");
// manageProductModal.getElement().getStyle().setWidth(60, Unit.PCT);
manageProductModal.getElement().getStyle().setWidth(60, Unit.PCT);
manageProductModal.show();
// async request to fetch the product
@ -331,11 +330,19 @@ public class ManageProductWidget extends Composite{
if(similarRecordPanel != null)
bean.setSimilarGrsfRecords(similarRecordPanel.getSimilarRecords());
else
bean.setSimilarGrsfRecords(new ArrayList<SimilarGRSFRecord>(0));
bean.setSimilarGrsfRecords(new ArrayList<SimilarGRSFRecord>(0));
// add the suggested ones, if any
bean.getSimilarGrsfRecords().addAll(suggestedMergesPanel.getSimilarRecords());
// set the merge operator on the bean if there is at least one merge to be done
for(SimilarGRSFRecord sR: bean.getSimilarGrsfRecords()){
if(sR.isSuggestedMerge()){
bean.setMergesInvolved(true);
break;
}
}
// set new values
bean.setAnnotation(new HTML(annotationArea.getText().trim()).getText());

View File

@ -0,0 +1,198 @@
package org.gcube.datacatalogue.grsf_manage_widget.client.view;
import java.util.Date;
import org.gcube.datacatalogue.grsf_manage_widget.client.GRSFManageWidgetService;
import org.gcube.datacatalogue.grsf_manage_widget.client.GRSFManageWidgetServiceAsync;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperationInfo;
import com.github.gwtbootstrap.client.ui.AlertBlock;
import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.Icon;
import com.github.gwtbootstrap.client.ui.Image;
import com.github.gwtbootstrap.client.ui.Modal;
import com.github.gwtbootstrap.client.ui.TextArea;
import com.github.gwtbootstrap.client.ui.constants.AlertType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
public class ManageRevertOperationWidget extends Composite {
private static ManageRevertOperationWidgetUiBinder uiBinder = GWT
.create(ManageRevertOperationWidgetUiBinder.class);
interface ManageRevertOperationWidgetUiBinder extends
UiBinder<Widget, ManageRevertOperationWidget> {
}
public ManageRevertOperationWidget() {
initWidget(uiBinder.createAndBindUi(this));
}
@UiField
VerticalPanel moreInfoAboutOperation;
@UiField
Modal revertOperationModal;
@UiField
Icon loaderIcon;
@UiField
Image loadingImage;
@UiField
AlertBlock infoBlock;
@UiField
Button revertButton;
@UiField
Button cancelButton;
@UiField
TextArea requestAuthor;
@UiField
TextArea requestTypeBox;
@UiField
TextArea requestRecordUUID;
@UiField
TextArea requestTimestamp;
private static GRSFManageWidgetServiceAsync service = GWT.create(GRSFManageWidgetService.class);
private HandlerManager eventBus;
public static final String LOADING_IMAGE_URL = GWT.getModuleBaseURL() + "../images/loader.gif";
private RevertableOperationInfo revertableOperation = null;
public ManageRevertOperationWidget(String encryptedUrlOperation, HandlerManager eventBus) {
initWidget(uiBinder.createAndBindUi(this));
this.eventBus = eventBus;
GWT.log("Encrypted url is " + encryptedUrlOperation);
if(encryptedUrlOperation == null || encryptedUrlOperation.isEmpty())
return;
// start loader service
loadingImage.setUrl(LOADING_IMAGE_URL);
loadingImage.setVisible(true);
// show modal
revertOperationModal.addStyleName("management-metadata-modal-style");
// revertOperationModal.getElement().getStyle().setWidth(60, Unit.PCT);
revertOperationModal.show();
// async request to fetch the product
loadModalContent(encryptedUrlOperation);
}
/**
* Validate the parameters of the url and ask the editor/reviewer what he/she wants to do.
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
* @param encryptedUrlOperation
*/
private void loadModalContent(String encryptedUrlOperation) {
revertButton.setEnabled(false);
service.validateRevertOperation(encryptedUrlOperation, new AsyncCallback<RevertableOperationInfo>() {
@Override
public void onSuccess(RevertableOperationInfo result) {
loadingImage.setVisible(false);
if(result != null){
revertableOperation = result;
String dateString = DateTimeFormat.getFormat("HH:mm:ss").format(new Date(revertableOperation.getTimestamp()));
requestAuthor.setText(revertableOperation.getAdmin());
requestTypeBox.setText(revertableOperation.getOperation().toString().toUpperCase());
requestRecordUUID.setText(revertableOperation.getUuid());
requestTimestamp.setText(dateString);
Anchor viewRecord = new Anchor();
viewRecord.setText("View Record");
viewRecord.getElement().getStyle().setFontWeight(FontWeight.BOLD);
viewRecord.setHref(revertableOperation.getRecordUrl());
viewRecord.setTarget("_blank");
viewRecord.getElement().getStyle().setMarginBottom(20, Unit.PX);
moreInfoAboutOperation.add(viewRecord);
moreInfoAboutOperation.setVisible(true);
revertButton.setEnabled(true);
}else
displayError(null);
}
@Override
public void onFailure(Throwable caught) {
loadingImage.setVisible(false);
displayError(caught);
}
});
}
@UiHandler("revertButton")
void onSaveButton(ClickEvent ce){
loaderIcon.setVisible(true);
revertButton.setEnabled(false);
service.performRevertOperation(revertableOperation, new AsyncCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
revertButton.setEnabled(true);
loaderIcon.setVisible(false);
if(!result)
displayError(null);
else{
infoBlock.setVisible(true);
infoBlock.setType(AlertType.SUCCESS);
infoBlock.setText("The request has been processed successfully!");
revertButton.removeFromParent();
cancelButton.removeFromParent();
}
}
@Override
public void onFailure(Throwable caught) {
displayError(caught);
}
});
}
@UiHandler("cancelButton")
void onCancelButton(ClickEvent ce){
revertOperationModal.hide();
}
protected void displayError(Throwable caught) {
infoBlock.setVisible(true);
infoBlock.setType(AlertType.ERROR);
infoBlock.setText("Unable to perform this operation. " + (caught != null ? "Error was " + caught : ""));
}
}

View File

@ -0,0 +1,87 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:b="urn:import:com.github.gwtbootstrap.client.ui">
<ui:style>
.loader-image {
display: block;
margin: auto auto;
}
</ui:style>
<g:HTMLPanel>
<b:Modal ui:field="revertOperationModal" title="Revert Operation"
backdrop="STATIC" keyboard="true" animation="true" closeVisible="true">
<g:VerticalPanel width="100%" ui:field="container">
<!-- Loader image at the beginning -->
<b:Image ui:field="loadingImage" styleName="{style.loader-image}"
visible="true"></b:Image>
<!-- Alert blocks for info/errors -->
<b:AlertBlock type="INFO" close="false" animation="true"
visible="false" ui:field="infoBlock"></b:AlertBlock>
<g:VerticalPanel ui:field="moreInfoAboutOperation"
visible="false" width="100%">
<b:Form type="VERTICAL" visible="true" ui:field="formUpdate"
width="100%">
<b:ControlGroup ui:field="originalRequest">
<b:ControlLabel for="requestType" title="Request Type">
<b>Request Type:</b>
</b:ControlLabel>
<b:Controls>
<b:TextArea alternateSize="LARGE" placeholder="Request Type"
visibleLines="2" readOnly="true" width="97%" b:id="requestType"
title="Request Type" ui:field="requestTypeBox" />
</b:Controls>
</b:ControlGroup>
<b:ControlGroup ui:field="originalAuthor">
<b:ControlLabel for="requestPerformedBy" title="Request was performed by">
<b>Request was performed by:</b>
</b:ControlLabel>
<b:Controls>
<b:TextArea alternateSize="LARGE" placeholder="Request author"
visibleLines="2" readOnly="true" width="97%" b:id="requestPerformedBy"
title="Request was performed by" ui:field="requestAuthor" />
</b:Controls>
</b:ControlGroup>
<b:ControlGroup ui:field="originalRecordUUID">
<b:ControlLabel for="requestPerformedOnRecord"
title="Request was performed on Record">
<b>Request was performed on Record:</b>
</b:ControlLabel>
<b:Controls>
<b:TextArea alternateSize="LARGE"
placeholder="Request performed on record" visibleLines="2"
readOnly="true" width="97%" b:id="requestPerformedOnRecord"
title="Request was performed on Record" ui:field="requestRecordUUID" />
</b:Controls>
</b:ControlGroup>
<b:ControlGroup ui:field="originalOperationDate">
<b:ControlLabel for="requestTimestamp" title="Request timestamp">
<b>Request was performed at:</b>
</b:ControlLabel>
<b:Controls>
<b:TextArea alternateSize="LARGE" placeholder="Request timestamp"
visibleLines="2" readOnly="true" width="97%" b:id="requestTimestamp"
title="Request timestamp" ui:field="requestTimestamp" />
</b:Controls>
</b:ControlGroup>
</b:Form>
</g:VerticalPanel>
</g:VerticalPanel>
<b:ModalFooter>
<b:Icon type="GEAR" spin="true" ui:field="loaderIcon"
visible="false" />
<b:Button ui:field="cancelButton">Cancel</b:Button>
<b:Button icon="FILE" type="PRIMARY" ui:field="revertButton">Revert</b:Button>
</b:ModalFooter>
</b:Modal>
</g:HTMLPanel>
</ui:UiBinder>

View File

@ -234,7 +234,7 @@ public class ConnectToWidget extends Composite{
}
});
box.setWidth("512px");
box.setPlaceholder("Insert the Identifier (UUID) of the record to connect and press ENTER");
box.setPlaceholder("Copy and Paste the Identifier (UUID) of the record to connect here");
vpLeft.add(semanticIdentifier);
vpLeft.add(box);
@ -276,6 +276,9 @@ public class ConnectToWidget extends Composite{
protected void validateUUID(final TextBox box, final ConnectedBean c, final Icon icon, final Anchor view, final String acceptedDomain) {
if(!box.isEnabled())
return;
final String currentText = box.getText().trim();
c.setKnowledgeBaseId(null);
c.setConnect(false);
@ -306,7 +309,6 @@ public class ConnectToWidget extends Composite{
public void onSuccess(String result) {
icon.setSpin(false);
if(result != null){
c.setKnowledgeBaseId(currentText);
c.setConnect(true);
@ -314,15 +316,15 @@ public class ConnectToWidget extends Composite{
icon.setTitle("Accepted");
view.setHref(result);
view.setVisible(true);
box.setEnabled(false);
}
else{
icon.setType(IconType.BAN_CIRCLE);
icon.setTitle("Not a valid UUID");
view.setVisible(false);
box.setEnabled(true);
}
icon.setVisible(true);
box.setEnabled(true);
}
@Override

View File

@ -14,8 +14,6 @@ import com.github.gwtbootstrap.client.ui.TextBox;
import com.github.gwtbootstrap.client.ui.constants.ButtonType;
import com.github.gwtbootstrap.client.ui.constants.IconType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Float;
import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.dom.client.Style.Unit;
@ -112,21 +110,14 @@ public class SuggestMerges extends Composite {
// add a couple of handlers
box.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
GWT.log("onKeyPress " + event.getNativeEvent().getKeyCode());
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
box.setFocus(false);
}
});
validateUUID(box, s, icon, view);
}
});
box.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
GWT.log("onChange");
@ -190,6 +181,9 @@ public class SuggestMerges extends Composite {
*/
protected void validateUUID(final TextBox box, final SimilarGRSFRecord s, final Icon icon, final Anchor view) {
if(!box.isEnabled())
return;
final String currentText = box.getText().trim();
s.setKnowledgeBaseId(null);
s.setSuggestedMerge(false);
@ -199,7 +193,7 @@ public class SuggestMerges extends Composite {
if(currentText == null || currentText.isEmpty())
return;
if(!currentText.matches(REGEX_UUID)){
icon.setType(IconType.BAN_CIRCLE);
icon.setTitle("Not a valid UUID");
@ -220,7 +214,7 @@ public class SuggestMerges extends Composite {
@Override
public void onSuccess(String result) {
icon.setSpin(false);
if(result != null){
s.setKnowledgeBaseId(currentText);
s.setSuggestedMerge(true);
@ -228,14 +222,15 @@ public class SuggestMerges extends Composite {
icon.setTitle("Accepted");
view.setHref(result);
view.setVisible(true);
box.setEnabled(false);
}
else{
icon.setType(IconType.BAN_CIRCLE);
icon.setTitle("Not a valid UUID");
view.setVisible(false);
box.setEnabled(true);
}
icon.setVisible(true);
box.setEnabled(true);
}
@Override

View File

@ -21,6 +21,8 @@ import org.gcube.datacatalogue.common.enums.Status;
import org.gcube.datacatalogue.grsf_manage_widget.client.GRSFManageWidgetService;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ConnectedBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperationInfo;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperations;
import org.gcube.datacatalogue.grsf_manage_widget.shared.SimilarGRSFRecord;
import org.gcube.datacatalogue.grsf_manage_widget.shared.SourceRecord;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ex.NoGRSFRecordException;
@ -372,7 +374,16 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
}
@Override
public void validateRevertOperation(String encryptedUrl) throws Exception {
public RevertableOperationInfo validateRevertOperation(String encryptedUrl) throws Exception {
if(!Utils.isIntoPortal()){
String baseUrl = "url of the record here";
String fullName = "Andrea Rossi";
String uuid = UUID.randomUUID().toString();
String adminInUrl = "costantino.perciante";
long timestamp = System.currentTimeMillis() - 1000 * ((long)(Math.random() * 10 * 60 * 60));
return new RevertableOperationInfo(baseUrl, fullName, uuid, adminInUrl, timestamp, RevertableOperations.MERGE);
}
PortalContext pContext = PortalContext.getConfiguration();
String context = Utils.getScopeFromClientUrl(getThreadLocalRequest());
@ -402,35 +413,69 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
if(!isValidTimestamp)
throw new Exception("This operation can no longer be reverted (link expired)!");
String keyPerContext = UtilMethods.concatenateSessionKeyScope(Constants.GRSF_UPDATER_SERVICE, context);
String baseUrl = (String)getThreadLocalRequest().getSession().getAttribute(keyPerContext);
if(baseUrl == null || baseUrl.isEmpty()){
baseUrl = GRSFUpdaterServiceClient.discoverEndPoint(context);
getThreadLocalRequest().getSession().setAttribute(keyPerContext, baseUrl);
}
if(baseUrl == null || baseUrl.isEmpty())
throw new Exception("Unable to discover grsf-updater service!");
DataCatalogue catalogue = getCatalogue(context);
String recordUrl = catalogue.getDataset(uuid, catalogue.getApiKeyFromUsername(username)).getExtrasAsHashMap().get(Constants.ITEM_URL_FIELD);
// check if it is a reviewer, than he can do what he wants (no matter the admin)
try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){
if(isReviewer){
GRSFUpdaterServiceClient.revertOperation(httpClient, baseUrl, fullName, uuid);
}else{
if(isReviewer){
return new RevertableOperationInfo(recordUrl,
fullName, uuid, adminInUrl, decryptedUrl.getTimestamp(), decryptedUrl.getOperation());
}else{
if(!username.equals(adminInUrl))
throw new Exception("You are not the editor allowed to perform this operation!");
else
GRSFUpdaterServiceClient.revertOperation(httpClient, baseUrl, fullName, uuid);
}
}catch(Exception e){
logger.error("Unable to update this Item ", e);
throw e;
if(!username.equals(adminInUrl))
throw new Exception("You are not the editor allowed to perform this operation!");
else
return new RevertableOperationInfo(recordUrl,
fullName, uuid, adminInUrl, decryptedUrl.getTimestamp(), decryptedUrl.getOperation());
}
}
@Override
public Boolean performRevertOperation(RevertableOperationInfo rInfo)
throws Exception {
if(!Utils.isIntoPortal()){
// random result
boolean toReturn = Math.random() > 0.5;
if(toReturn){
boolean throwException = Math.random() > 0.5;
if(throwException)
throw new Exception("Unable to execute request for XYZ");
}
return toReturn;
}
HttpServletRequest threadRequest = getThreadLocalRequest();
String context = Utils.getScopeFromClientUrl(threadRequest);
String token = SecurityTokenProvider.instance.get();
try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){
String keyPerContext = UtilMethods.concatenateSessionKeyScope(Constants.GRSF_UPDATER_SERVICE, context);
String baseUrl = (String)getThreadLocalRequest().getSession().getAttribute(keyPerContext);
if(baseUrl == null || baseUrl.isEmpty()){
baseUrl = GRSFUpdaterServiceClient.discoverEndPoint(context);
getThreadLocalRequest().getSession().setAttribute(keyPerContext, baseUrl);
}
if(baseUrl == null || baseUrl.isEmpty())
throw new Exception("Unable to discover grsf-updater service!");
Utils.revertOperation(httpClient, baseUrl, rInfo, token, context, PortalContext.getConfiguration().getCurrentGroupId(threadRequest));
}
catch(Exception e){
logger.error("Unable to revert operation ", e);
throw e;
}
return true;
}
/**
* Check if the current user is an editor
* @param username
@ -484,21 +529,21 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
@Override
public String checkIdentifierExistsInDomain(String id,
String domain) throws Exception {
if(!Utils.isIntoPortal()){
boolean throwException = Math.random() > 0.5;
// simulate some delay...
Thread.sleep(2500);
if(throwException)
throw new Exception("The suggested record is not a GRSF record");
return "http://data.d4science.org/catalogue/grsf_admin/" + id;
}
String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest());
DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl);
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();

View File

@ -4,6 +4,7 @@ import java.net.URLDecoder;
import java.net.URLEncoder;
import org.gcube.common.encryption.StringEncrypter;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperations;
import org.gcube.portlets.user.urlshortener.UrlShortener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -24,32 +25,11 @@ public class RevertOperationUrl {
public static final String OPERATION_REVERT_QUERY_PARAM = "operation_revert";
public static final long TTL = 1000 * 60 * 60 * 24;
/**
* For now only Merge can be reverted
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public enum Operation {
MERGE("merge");
// DISSECT("dissect");
private String name;
private Operation(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
private String baseUrl;
private String admin;
private long timestamp;
private String uuid;
private Operation operation;
private RevertableOperations operation;
/**
* @param admin
@ -59,7 +39,7 @@ public class RevertOperationUrl {
* @param op
*/
public RevertOperationUrl(String baseUrl, String admin, long timestamp, String uuid,
Operation operation) {
RevertableOperations operation) {
super();
this.baseUrl = baseUrl;
this.admin = admin;
@ -133,7 +113,7 @@ public class RevertOperationUrl {
this.uuid = value;
break;
case OPERATION_REVERT_QUERY_PARAM:
this.operation = Operation.valueOf(value);
this.operation = RevertableOperations.valueOf(value);
break;
default:
break;
@ -189,12 +169,12 @@ public class RevertOperationUrl {
}
public Operation getOperation() {
public RevertableOperations getOperation() {
return operation;
}
public void setOperation(Operation operation) {
public void setOperation(RevertableOperations operation) {
this.operation = operation;
}

View File

@ -15,8 +15,9 @@ import org.gcube.common.resources.gcore.GCoreEndpoint;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue;
import org.gcube.datacatalogue.common.Constants;
import org.gcube.datacatalogue.grsf_manage_widget.server.manage.RevertOperationUrl.Operation;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperationInfo;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperations;
import org.gcube.resources.discovery.client.api.DiscoveryClient;
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
import org.gcube.vomanagement.usermanagement.RoleManager;
@ -78,6 +79,13 @@ public class SocialCommunications {
private static final String REVERT_LINK_PIECE = "<br>The request involves a merge operation. You can reject the merge by exploiting this link LINK in the following 24 hours.";
// on revert operation
private static final String EMAIL_REVIEWER_REVERT = "Dear GRSF Reviewer,"
+ "<br>a revert operation (undo merge) has been requested on record RECORD_URL, by USERNAME.";
private static final String EMAIL_EDITOR_REVERT = "Dear USER_FULLNAME,"
+"<br> a revert operation (undo merge) has been requested on this RECORD_URL you managed.";
/**
*
* @param context
@ -128,9 +136,10 @@ public class SocialCommunications {
* @param fullName
* @param hashtags
* @param enablePostNotification
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static void writeProductPost(ManageProductBean bean, String username, String fullName, String report, boolean enablePostNotification){
public static void writeProductPost(ManageProductBean bean, String username, String fullName, String report, boolean enablePostNotification) throws Exception{
// discover service endpoint for the social networking library
String currentScope = ScopeProvider.instance.get();
@ -142,6 +151,7 @@ public class SocialCommunications {
if(basePath == null){
logger.error("Unable to write a post because there is no social networking service available");
throw new Exception("Unable to discover the social networking service");
}else{
@ -222,7 +232,7 @@ public class SocialCommunications {
* @throws Exceptio
*/
@SuppressWarnings("unchecked")
public static void sendEmailAdministrators(
public static void sendEmailAdministratorsOnMerge(
ManageProductBean bean,
DataCatalogue catalogue,
String username,
@ -248,7 +258,7 @@ public class SocialCommunications {
logger.info("List of " + Constants.GRSF_CATALOGUE_REVIEWER_ROLE + " is " + reviewers);
// build the url that allows to revert the operation
Operation operation = Operation.MERGE;
RevertableOperations operation = RevertableOperations.MERGE;
// discover service endpoint for the social networking library
String currentScope = ScopeProvider.instance.get();
@ -260,6 +270,7 @@ public class SocialCommunications {
if(basePath == null){
logger.error("Unable to write a post because there is no social networking service available");
throw new Exception("Unable to discover the social networking service");
}else{
@ -352,7 +363,137 @@ public class SocialCommunications {
}
}catch(Exception e){
logger.error("Failed to create a post", e);
logger.error("Failed to send messages", e);
}
}
}
public static void sendEmailAdministratorsOnOperationReverted(
RevertableOperationInfo rInfo,
long groupId
) throws Exception {
// get the list of GRSF Reviewers to alert them as well
RoleManager roleManager = new LiferayRoleManager();
List<GCubeTeam> teamRoles = roleManager.listTeamsByGroup(groupId);
List<String> reviewers = new ArrayList<>();
UserManager um = new LiferayUserManager();
for(GCubeTeam tr: teamRoles){
if(tr.getTeamName().equals(Constants.GRSF_CATALOGUE_REVIEWER_ROLE))
reviewers.add(um.getUserById(tr.getUserId()).getUsername());
}
// if the user is a reviewer, then send the email just once
reviewers.remove(rInfo.getAdmin());
logger.info("List of " + Constants.GRSF_CATALOGUE_REVIEWER_ROLE + " is " + reviewers);
// build the url that allows to revert the operation
RevertableOperations operation = RevertableOperations.MERGE;
// discover service endpoint for the social networking library
String currentScope = ScopeProvider.instance.get();
String tokenUser = SecurityTokenProvider.instance.get();
logger.info("Current scope for writeProductPost is " + currentScope + " and token is " + tokenUser.substring(0, 10) + "***************");
String basePath = getBaseUrlSocialService(currentScope);
if(basePath == null){
logger.error("Unable to write a post because there is no social networking service available");
throw new Exception("Unable to discover the social networking service");
}else{
basePath = basePath.endsWith("/") ? basePath : basePath + "/";
try(CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build();){
// ask token application
HttpPost postRequest = new HttpPost(basePath + SOCIAL_SERVICE_APPLICATION_TOKEN + "?gcube-token=" + tokenUser);
StringEntity input = new StringEntity("{\"app_id\":\"" + APPLICATION_ID_CATALOGUE_MANAGER + "\"}");
input.setContentType(MEDIATYPE_JSON);
postRequest.setEntity(input);
HttpResponse response = client.execute(postRequest);
logger.debug("Url is " + basePath + SOCIAL_SERVICE_APPLICATION_TOKEN + "?gcube-token=" + tokenUser);
if (response.getStatusLine().getStatusCode() != 201) {
throw new RuntimeException("Failed to retrieve application token : HTTP error code : "
+ response.getStatusLine().getStatusCode());
}else{
Map<String, Object> mapResponseGeneratedToken = getResponseEntityAsJSON(response);
boolean successGeneratedToken = (boolean)mapResponseGeneratedToken.get("success");
if(!successGeneratedToken){
throw new RuntimeException("Failed to generate the token for the application!"
+ " Error message is " + mapResponseGeneratedToken.get("message"));
}else{
String applicationToken = (String)mapResponseGeneratedToken.get("result");
String messageToEditor = EMAIL_EDITOR_REVERT.replace("RECORD_URL", rInfo.getRecordUrl()).replace("USER_FULLNAME", rInfo.getFullName());
String messageToReviewer = EMAIL_REVIEWER_REVERT.replace("USER_FULLNAME", rInfo.getFullName()).replace("RECORD_URL", rInfo.getRecordUrl());
String subject = "Revert merge request on GRSF Record";
// send email to the editor
logger.info("The message that is going to be send to the editor is\n" + messageToEditor);
postRequest = new HttpPost(basePath + SOCIAL_SEND_EMAIL + "?gcube-token=" + applicationToken);
JSONObject reqMessage = new JSONObject();
reqMessage.put("subject", subject);
reqMessage.put("body", messageToEditor);
JSONArray recipients = new JSONArray();
JSONObject recipient = new JSONObject();
recipient.put("id", rInfo.getAdmin());
recipients.add(recipient);
reqMessage.put("recipients", recipients);
input = new StringEntity(reqMessage.toJSONString());
input.setContentType(MEDIATYPE_JSON);
postRequest.setEntity(input);
response = client.execute(postRequest);
Map<String, Object> mapResponseWritePost = getResponseEntityAsJSON(response);
if (response.getStatusLine().getStatusCode() != 201){
logger.error("Failed to send message to editor : HTTP error code : "
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
}
// send email to the reviewers
logger.info("The message that is going to be send to the reviewers is\n" + messageToReviewer);
postRequest = new HttpPost(basePath + SOCIAL_SEND_EMAIL + "?gcube-token=" + applicationToken);
reqMessage = new JSONObject();
reqMessage.put("subject", subject);
reqMessage.put("body", messageToReviewer);
recipients = new JSONArray();
for(String reviewer: reviewers){
JSONObject recip = new JSONObject();
recip.put("id", reviewer);
recipients.add(recip);
}
reqMessage.put("recipients", recipients);
input = new StringEntity(reqMessage.toJSONString());
input.setContentType(MEDIATYPE_JSON);
postRequest.setEntity(input);
response = client.execute(postRequest);
mapResponseWritePost = getResponseEntityAsJSON(response);
if (response.getStatusLine().getStatusCode() != 201){
logger.error("Failed to send message to editor : HTTP error code : "
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
}
}
}
}catch(Exception e){
logger.error("Failed to send messages", e);
throw new Exception(e);
}
}
@ -364,7 +505,7 @@ public class SocialCommunications {
* @return
* @throws Exception
*/
public static String getEncodedUrlManage(Operation operation, String administrator, long timestamp, String uuid, HttpServletRequest httpServletRequest) throws Exception{
public static String getEncodedUrlManage(RevertableOperations operation, String administrator, long timestamp, String uuid, HttpServletRequest httpServletRequest) throws Exception{
String clientUrl = Utils.getCurrentClientUrl(httpServletRequest).split("\\?")[0]; // ignore other parameters
RevertOperationUrl operationUrl = new RevertOperationUrl(clientUrl, administrator, timestamp, uuid, operation);
String shortUrl = operationUrl.getShortUrl();

View File

@ -28,6 +28,7 @@ import org.gcube.datacatalogue.common.enums.Product_Type;
import org.gcube.datacatalogue.common.enums.Status;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ConnectedBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertableOperationInfo;
import org.gcube.datacatalogue.grsf_manage_widget.shared.SimilarGRSFRecord;
import org.gcube.resources.discovery.client.api.DiscoveryClient;
import org.gcube.resources.discovery.client.queries.api.Query;
@ -212,7 +213,7 @@ public class Utils {
try{
// send email to Editors and Reviewers
SocialCommunications.sendEmailAdministrators(bean, catalogue, username, fullName, groupId, httpServletRequest, bean.isMergesInvolved());
SocialCommunications.sendEmailAdministratorsOnMerge(bean, catalogue, username, fullName, groupId, httpServletRequest, bean.isMergesInvolved());
// create a post about the operation
SocialCommunications.writeProductPost(bean, username, fullName, report, true);
@ -232,6 +233,40 @@ public class Utils {
}
return null;
}
/**
* Revert operation and alert admins/vre users
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
* @param httpClient
* @param baseUrl
* @param fullName
* @param uuid
*/
public static void revertOperation(CloseableHttpClient httpClient, String baseUrl,
RevertableOperationInfo rInfo, String token, String context, long groupId) throws Exception{
GRSFUpdaterServiceClient.revertOperation(httpClient, baseUrl, rInfo.getFullName(), rInfo.getUuid());
// manage interactions through a separated thread but set there security token and context (and then reset them)
new Thread(()->{
ScopeProvider.instance.set(context);
SecurityTokenProvider.instance.set(token);
try{
// create a post about the operation
SocialCommunications.sendEmailAdministratorsOnOperationReverted(rInfo, groupId);
}catch(Exception e){
logger.error("Something failed while alerting editors/reviewers", e);
}finally{
ScopeProvider.instance.reset();
SecurityTokenProvider.instance.reset();
}
}).start();
}
/**
* Update the status of the involved records to "to be merged"
@ -396,33 +431,6 @@ public class Utils {
return null;
}
// /**
// * Get extra information to show in the management panel, if any
// * @param extrasAsPairs
// */
// public static void getExtrasToShow(List<CkanPair> extrasAsPairs, ){
//
// Set<String> extrasToShow = getLookedUpExtrasKeys();
// if(extrasToShow != null && !extrasToShow.isEmpty()){
// Map<String, String> extrasKeyValuePair = new HashMap<String, String>();
// = product.getExtras();
// for (CkanPair ckanPair : extrasAsPairs) {
// String key = ckanPair.getKey();
// String value = ckanPair.getValue();
//
// if(extrasToShow.contains(key)){
// String currentValueInMap = extrasKeyValuePair.get(key);
// if(currentValueInMap == null)
// currentValueInMap = value;
// else
// currentValueInMap += ", " + value;
// extrasKeyValuePair.put(key, currentValueInMap);
// }
// }
// toReturn.setExtrasIfAvailable(extrasKeyValuePair);
// }
//
// }
/**
* Get a {@link SimilarGRSFRecord} from a json string

View File

@ -0,0 +1,79 @@
package org.gcube.datacatalogue.grsf_manage_widget.shared;
import java.io.Serializable;
public class RevertableOperationInfo implements Serializable{
private static final long serialVersionUID = 5274434342849474800L;
private String recordUrl;
private String fullName;
private String uuid;
private String admin;
private long timestamp;
private RevertableOperations operation;
public RevertableOperationInfo() {
super();
}
public RevertableOperationInfo(String recordUrl, String fullName,
String uuid, String admin, long timestamp, RevertableOperations operation) {
super();
this.recordUrl = recordUrl;
this.fullName = fullName;
this.uuid = uuid;
this.admin = admin;
this.timestamp = timestamp;
this.operation = operation;
}
public String getAdmin() {
return admin;
}
public void setAdmin(String admin) {
this.admin = admin;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public RevertableOperations getOperation() {
return operation;
}
public void setOperation(RevertableOperations operation) {
this.operation = operation;
}
public String getRecordUrl() {
return recordUrl;
}
public void setRecordUrl(String recordUrl) {
this.recordUrl = recordUrl;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "RevertableOperationInfo [recordUrl=" + recordUrl + ", fullName="
+ fullName + ", uuid=" + uuid + ", admin=" + admin
+ ", timestamp=" + timestamp + ", operation=" + operation + "]";
}
}

View File

@ -0,0 +1,22 @@
package org.gcube.datacatalogue.grsf_manage_widget.shared;
/**
* For now only Merge can be reverted
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public enum RevertableOperations {
MERGE("merge");
// DISSECT("dissect");
private String name;
private RevertableOperations(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}