diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index b90405e..13b3428 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -3,11 +3,11 @@ org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component index d0ac04b..3afe46d 100644 --- a/.settings/org.eclipse.wst.common.component +++ b/.settings/org.eclipse.wst.common.component @@ -4,7 +4,7 @@ - + uses diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetService.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetService.java index c0fcf38..d674253 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetService.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetService.java @@ -27,22 +27,21 @@ public interface GRSFManageWidgetService extends RemoteService { */ String notifyProductUpdate(ManageProductBean bean) throws Exception; - /** - * Check that a record with such semantic identifier exists - * @param semanticIdentifier - * @return true or false - */ - boolean checkSemanticIdentifierExists(String semanticIdentifier) throws Exception; - - /** - * Check that a record with such semantic identifier exists in a given domain - * @param semanticIdentifier - * @param domain - * @return - * @throws Exception - */ - boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) throws Exception; - + // /** + // * Check that a record with such semantic identifier exists + // * @param semanticIdentifier + // * @return true or false + // */ + // boolean checkSemanticIdentifierExists(String semanticIdentifier) throws Exception; + // + // /** + // * Check that a record with such semantic identifier exists in a given domain + // * @param semanticIdentifier + // * @param domain + // * @return + // * @throws Exception + // */ + // boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) throws Exception; /** * Identifier of the record (UUID) @@ -61,4 +60,11 @@ public interface GRSFManageWidgetService extends RemoteService { */ String checkIdentifierExistsInDomain(String id, String domain) throws Exception; + /** + * Check if the given url for revertin the operation is valid and send the request to the knowledge base + * @param url + * @throws Exception + */ + void validateRevertOperation(String url) throws Exception; + } diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetServiceAsync.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetServiceAsync.java index 6501992..df46c05 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetServiceAsync.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/client/GRSFManageWidgetServiceAsync.java @@ -21,11 +21,11 @@ public interface GRSFManageWidgetServiceAsync { void isAdminUser(AsyncCallback callback); - void checkSemanticIdentifierExists(String semanticIdentifier, - AsyncCallback callback); - - void checkSemanticIdentifierExistsInDomain(String semanticIdentifier, - String domain, AsyncCallback callback); + // void checkSemanticIdentifierExists(String semanticIdentifier, + // AsyncCallback callback); + // + // void checkSemanticIdentifierExistsInDomain(String semanticIdentifier, + // String domain, AsyncCallback callback); void checkIdentifierExists(String id, AsyncCallback callback); @@ -33,4 +33,6 @@ public interface GRSFManageWidgetServiceAsync { void checkIdentifierExistsInDomain(String id, String domain, AsyncCallback callback); + void validateRevertOperation(String url, AsyncCallback callback); + } diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFNotificationService.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFNotificationService.java index 9d2ebde..fe35a55 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFNotificationService.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFNotificationService.java @@ -5,7 +5,12 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; import org.gcube.common.portal.PortalContext; +import org.gcube.common.scope.api.ScopeProvider; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueFactory; import org.gcube.datacatalogue.ckanutillibrary.server.utils.UtilMethods; @@ -22,11 +27,14 @@ import org.gcube.datacatalogue.grsf_manage_widget.shared.ex.NoGRSFRecordExceptio import org.gcube.vomanagement.usermanagement.RoleManager; import org.gcube.vomanagement.usermanagement.impl.LiferayRoleManager; import org.gcube.vomanagement.usermanagement.model.GCubeTeam; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gwt.user.server.rpc.RemoteServiceServlet; +import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.CloseableHttpClient; +import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.HttpClientBuilder; import eu.trentorise.opendata.jackan.model.CkanDataset; import eu.trentorise.opendata.jackan.model.CkanPair; import eu.trentorise.opendata.jackan.model.CkanResource; @@ -52,7 +60,7 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS String currentScope = Utils.getCurrentContext(getThreadLocalRequest(), true); DataCatalogue instance = null; try{ - String scopeInWhichDiscover = discoverScope != null && !discoverScope.isEmpty() ? discoverScope : currentScope; + String scopeInWhichDiscover = (discoverScope != null && !discoverScope.isEmpty()) ? discoverScope : currentScope; logger.debug("Discovering ckan utils library into scope " + scopeInWhichDiscover); instance = DataCatalogueFactory.getFactory().getUtilsPerScope(scopeInWhichDiscover); }catch(Exception e){ @@ -65,17 +73,29 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS @Override public ManageProductBean getProductBeanById(String productIdentifier) throws Exception { - ManageProductBean toReturn = null; + // check into session first + HttpSession httpSession = getThreadLocalRequest().getSession(); + String sessionProductKey = ScopeProvider.instance.get() + productIdentifier; + if(httpSession.getAttribute(sessionProductKey) != null) + return (ManageProductBean) httpSession.getAttribute(sessionProductKey); + if(!Utils.isIntoPortal()){ toReturn = new ManageProductBean(); toReturn.setCatalogueIdentifier(UUID.randomUUID().toString()); List connectTo = new ArrayList<>(); - connectTo.add(new ConnectedBean("91f1e413-dc9f-3b4e-b1c5-0e8560177253","Stock", - "aksldsam asd", "asdasjnk:fas", UUID.randomUUID().toString(), "http://data.d4science.org/ctlg/GRSF_Admin/91f1e413-dc9f-3b4e-b1c5-0e8560177253")); - toReturn.setConnectTo(connectTo); + connectTo.add(new ConnectedBean( + "91f1e413-dc9f-3b4e-b1c5-0e8560177253", + "Stock", + "http://data.d4science.org/ctlg/GRSF_Admin/91f1e413-dc9f-3b4e-b1c5-0e8560177253", + "89f1e413-dc9f-3b4e-b1c5-0e8560177254", + "Random title", + "http://data.d4science.org/ctlg/GRSF_Admin/89f1e413-dc9f-3b4e-b1c5-0e8560177254", + "Fishery" + )); + toReturn.setCurrentConnections(connectTo); toReturn.setGrsfDomain("Stock"); toReturn.setGrsfType("Assessment Unit"); toReturn.setKnowledgeBaseIdentifier("91f1e413-dc9f-3b4e-b1c5-0e8560177253"); @@ -105,52 +125,63 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS }else{ - // retrieve scope per current portlet url String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest()); DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl); String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); - CkanDataset record = catalogue.getDataset(productIdentifier, catalogue.getApiKeyFromUsername(username)); + String apiKey = catalogue.getApiKeyFromUsername(username); + CkanDataset record = catalogue.getDataset(productIdentifier, apiKey); // it cannot be enabled in this case ... if(record == null) throw new Exception("Unable to retrieve information for the selected record, sorry"); else{ - logger.debug("Trying to fetch record...."); + logger.debug("Trying to fetch the record...."); // check it is a grsf record (Source records have a different System Type) - String systemType = record.getExtrasAsHashMap().get(Constants.SYSTEM_TYPE_CUSTOM_KEY); - if(systemType == null || systemType.isEmpty() || systemType.equals(Constants.SYSTEM_TYPE_FOR_SOURCES_VALUE)) - throw new NoGRSFRecordException("This is not a GRSF Record"); + Map extrasAsMap = record.getExtrasAsHashMap(); // get extras as hashmap and pairs List extrasAsPairs = record.getExtras(); + String systemType = extrasAsMap.get(Constants.SYSTEM_TYPE_CUSTOM_KEY); + if(systemType == null || systemType.isEmpty() || systemType.equals(Constants.SYSTEM_TYPE_FOR_SOURCES_VALUE)) + throw new NoGRSFRecordException("This is not a GRSF Record"); + + boolean isStock = record.getExtrasAsHashMap().get(Constants.DOMAIN_CUSTOM_KEY).contains(Product_Type.STOCK.getOrigName()); + // fetch map for namespaces - Map fieldsNamespacesMap = Utils.getFieldToFieldNameSpaceMapping(getThreadLocalRequest().getSession(), - record.getExtrasAsHashMap().get(Constants.DOMAIN_CUSTOM_KEY).contains(Product_Type.STOCK.getOrigName()) ? Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_STOCK - : Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_FISHERY); + Map fieldsNamespacesMap = + Utils.getFieldToFieldNameSpaceMapping(httpSession, isStock ? + Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_STOCK : Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_FISHERY); Map> extrasWithoutNamespaces = Utils.replaceFieldsKey(extrasAsPairs, fieldsNamespacesMap); // get extras fields (wrt the mandatory ones) to show in the management panel TODO // Utils.getExtrasToShow(); String catalogueIdentifier = record.getId(); - String status = extrasWithoutNamespaces.get(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY).get(0); + Status status = Status.fromString(extrasWithoutNamespaces.get(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY).get(0)); String uuidKB = extrasWithoutNamespaces.get(Constants.UUID_KB_CUSTOM_KEY).get(0); String grsfDomain = extrasWithoutNamespaces.get(Constants.DOMAIN_CUSTOM_KEY).get(0); - logger.debug(Constants.DOMAIN_CUSTOM_KEY + " is " + grsfDomain); if(status == null || uuidKB == null) throw new Exception("Some information is missing in this record: Status = " + status + ", knowledge base uuid = " + uuidKB + ", and grsf domain is = " + grsfDomain); + if(status.equals(Status.To_be_Merged) || status.equals(Status.Rejected)) + throw new Exception("Records under merging activity or rejected cannot be updated"); + String semanticId = extrasWithoutNamespaces.get(Constants.GRSF_SEMANTIC_IDENTIFIER_CUSTOM_KEY).get(0); String shortName = extrasWithoutNamespaces.get(Constants.SHORT_NAME_CUSTOM_KEY).get(0); String grsfType = extrasWithoutNamespaces.get(Constants.GRSF_TYPE_CUSTOM_KEY).get(0); - String grsfName = extrasWithoutNamespaces.get(grsfDomain.contains(Product_Type.STOCK.getOrigName()) ? - Constants.STOCK_NAME_CUSTOM_KEY : Constants.FISHERY_NAME_CUSTOM_KEY).get(0); - boolean traceabilityFlag = extrasWithoutNamespaces.get(Constants.TRACEABILITY_FLAG_CUSTOM_KEY).get(0).equalsIgnoreCase("true"); + String recordUrl = extrasWithoutNamespaces.get(Constants.ITEM_URL_FIELD).get(0); + String grsfName = extrasWithoutNamespaces.get(grsfDomain.contains(Product_Type.STOCK.getOrigName()) ? Constants.STOCK_NAME_CUSTOM_KEY : Constants.FISHERY_NAME_CUSTOM_KEY).get(0); + boolean traceabilityFlag = false; + try{ + traceabilityFlag = extrasWithoutNamespaces.get(Constants.TRACEABILITY_FLAG_CUSTOM_KEY).get(0).equalsIgnoreCase("true"); + }catch(Exception e){ + logger.warn("Unable to fetch traceability flag", e); + } // Get similar GRSF records, if any (each of which should have name, description, url and id(i.e semantic identifier)) List similarGrsfRecordsAsStrings = extrasWithoutNamespaces.containsKey(Constants.SIMILAR_GRSF_RECORDS_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.SIMILAR_GRSF_RECORDS_CUSTOM_KEY): null; @@ -160,87 +191,87 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS for (String similarGRSFRecord : similarGrsfRecordsAsStrings) { if(similarGRSFRecord.equals(Constants.NO_SIMILAR_GRSF_RECORDS)) // stop here if there is a single element with this information break; - similarRecords.add(Utils.similarGRSFRecordFromJson(similarGRSFRecord)); } } logger.debug("SimilarGRSFRecords are " + similarRecords); - // get connected records - List connectedBeansAsStrings = extrasWithoutNamespaces.containsKey(Constants.CONNECTED_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.CONNECTED_CUSTOM_KEY): null; + // get connected records (and the proposed ones) + List connectedBeanUrls = + extrasWithoutNamespaces.containsKey(Constants.CONNECTED_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.CONNECTED_CUSTOM_KEY): null; - List connectedBeans = new ArrayList(0); - if(connectedBeansAsStrings != null){ - for (String connectedBean : connectedBeansAsStrings) { - if(connectedBean.equals(Constants.NO_CONNECTED_RECORDS)) // stop here if there is a single element with this information - break; - connectedBeans.add(Utils.connectedBeanRecordFromJson(connectedBean, uuidKB, grsfDomain, catalogue)); - } - } + List connectedBeans = new ArrayList(0); + if(connectedBeanUrls != null){ + for (String connectedBean : connectedBeanUrls) { + if(connectedBean.equals(Constants.NO_CONNECTED_RECORDS)) // stop here if there is a single element with this information + break; + ConnectedBean builtBean = Utils.connectedBeanRecordFromUrl(recordUrl, connectedBean, uuidKB, grsfDomain, catalogue, apiKey); + if(builtBean != null) + connectedBeans.add(builtBean); + } + } - logger.debug("Connected records are " + connectedBeans); + logger.debug("Already connected records are " + connectedBeans); - // Get sources - List resources = record.getResources(); - List sources = new ArrayList(3); - for (CkanResource ckanResource : resources) { - if(Sources.getListNames().contains(ckanResource.getName())) - sources.add(new SourceRecord(ckanResource.getName(), ckanResource.getUrl())); - } + // get the connections the knowledge base suggests + List suggestedConnectionsByKnowledgeBase = new ArrayList(0); + List exploitedResourcesUrls = isStock ? + (extrasWithoutNamespaces.containsKey(Constants.EXPLOITING_FISHERY_JSON_KEY) ? + extrasWithoutNamespaces.get(Constants.EXPLOITING_FISHERY_JSON_KEY) : null): + (extrasWithoutNamespaces.containsKey(Constants.RESOURCES_EXPLOITED_JSON_KEY) ? extrasWithoutNamespaces.get(Constants.RESOURCES_EXPLOITED_JSON_KEY) : null); - // set the values - toReturn = new ManageProductBean(semanticId, catalogueIdentifier, uuidKB, grsfType, - grsfDomain, grsfName, shortName, traceabilityFlag, Status.fromString(status), null, - null, null, sources, similarRecords, connectedBeans, false); + if(exploitedResourcesUrls != null && !exploitedResourcesUrls.isEmpty()){ + for (String exploited : exploitedResourcesUrls) { + ConnectedBean builtBean = Utils.connectedBeanRecordFromUrl(recordUrl, exploited, uuidKB, grsfDomain, catalogue, apiKey); + if(builtBean != null) + suggestedConnectionsByKnowledgeBase.add(builtBean); + } + } + + logger.debug("Knowledge base suggests " + suggestedConnectionsByKnowledgeBase); + + // Get sources + List resources = record.getResources(); + List sources = new ArrayList(3); + for (CkanResource ckanResource : resources) { + if(Sources.getListNames().contains(ckanResource.getName())) + sources.add(new SourceRecord(ckanResource.getName(), ckanResource.getUrl())); + } + + // set the values + toReturn = new ManageProductBean(semanticId, catalogueIdentifier, uuidKB, grsfType, + grsfDomain, grsfName, shortName, traceabilityFlag, status, recordUrl, + null, sources, similarRecords, connectedBeans, suggestedConnectionsByKnowledgeBase); } } logger.info("Returning item bean " + toReturn); + httpSession.setAttribute(sessionProductKey, toReturn); return toReturn; } @Override public boolean isAdminUser() { try{ - Boolean inSession = (Boolean)getThreadLocalRequest().getSession().getAttribute(Constants.GRSF_ADMIN_SESSION_KEY); - if(inSession != null) return inSession; else{ - if(!Utils.isIntoPortal()){ - getThreadLocalRequest().getSession().setAttribute(Constants.GRSF_ADMIN_SESSION_KEY, true); - return true; - } - - PortalContext pContext = PortalContext.getConfiguration(); - RoleManager roleManager = new LiferayRoleManager(); - String username = pContext.getCurrentUser(getThreadLocalRequest()).getUsername(); - long userId = pContext.getCurrentUser(getThreadLocalRequest()).getUserId(); - long groupId = pContext.getCurrentGroupId(getThreadLocalRequest()); - // List vreRoles = roleManager.listRolesByUserAndGroup(userId, groupId); - List teamRoles = roleManager.listTeamsByUserAndGroup(userId, groupId); boolean toSetInSession = false; - for (GCubeTeam team : teamRoles) { - if(team.getTeamName().equals(Constants.GRSF_CATALOGUE_EDITOR_ROLE) || team.getTeamName().equals(Constants.GRSF_CATALOGUE_REVIEWER_ROLE)){ - logger.info("User " + username + " is allowed to modify GRSF records"); - toSetInSession = true; - break; - } + if(!Utils.isIntoPortal()){ + toSetInSession = true; + }else{ + PortalContext pContext = PortalContext.getConfiguration(); + RoleManager roleManager = new LiferayRoleManager(); + String username = pContext.getCurrentUser(getThreadLocalRequest()).getUsername(); + long userId = pContext.getCurrentUser(getThreadLocalRequest()).getUserId(); + long groupId = pContext.getCurrentGroupId(getThreadLocalRequest()); + List teamRoles = roleManager.listTeamsByUserAndGroup(userId, groupId); + toSetInSession = isEditor(username, teamRoles) | isReviewer(username, teamRoles); } - - // if(!toSetInSession) - // for (GCubeRole gCubeTeam : vreRoles) { - // if(gCubeTeam.getRoleName().equals(GatewayRolesNames.VRE_MANAGER.getRoleName())){ - // logger.info("User " + username + " is " + GatewayRolesNames.VRE_MANAGER.getRoleName()); - // toSetInSession = true; - // break; - // } - // } - getThreadLocalRequest().getSession().setAttribute(Constants.GRSF_ADMIN_SESSION_KEY, toSetInSession); return toSetInSession; } @@ -251,42 +282,132 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS } @Override - public boolean checkSemanticIdentifierExists(String semanticIdentifier) - throws Exception { + public String notifyProductUpdate(ManageProductBean bean) throws Exception{ - return getDataset(semanticIdentifier) != null; + logger.info("Creating notification for the bean " + bean + " to send to the knowledge base"); + try{ + + String context = Utils.getScopeFromClientUrl(getThreadLocalRequest()); + String token = SecurityTokenProvider.instance.get(); + DataCatalogue catalogue = getCatalogue(context); + String administratorFullName = Utils.getCurrentUser(getThreadLocalRequest()).getFullname(); + String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); + // check if the base url of the service is in session + String keyPerContext = UtilMethods.concatenateSessionKeyScope(Constants.GRSF_UPDATER_SERVICE, context); + HttpServletRequest threadRequest = getThreadLocalRequest(); + String baseUrl = (String)threadRequest.getSession().getAttribute(keyPerContext); + if(baseUrl == null || baseUrl.isEmpty()){ + baseUrl = GRSFUpdaterServiceClient.discoverEndPoint(context); + threadRequest.getSession().setAttribute(keyPerContext, baseUrl); + } + + // remove it from the session + String sessionProductKey = ScopeProvider.instance.get() + bean.getCatalogueIdentifier(); + threadRequest.getSession().removeAttribute(sessionProductKey); + + return Utils.updateRecord(baseUrl, bean, catalogue, username, administratorFullName, threadRequest, + PortalContext.getConfiguration().getCurrentGroupId(threadRequest), context, token); + + }catch(Exception e){ + logger.error("Unable to update the product", e); + throw e; + } } @Override - public boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) - throws Exception { + public void validateRevertOperation(String encryptedUrl) throws Exception { + PortalContext pContext = PortalContext.getConfiguration(); + String context = Utils.getScopeFromClientUrl(getThreadLocalRequest()); + RoleManager roleManager = new LiferayRoleManager(); + GCubeUser user = pContext.getCurrentUser(getThreadLocalRequest()); + String username = user.getUsername(); + String fullName = user.getFullname(); + long userId = pContext.getCurrentUser(getThreadLocalRequest()).getUserId(); + long groupId = pContext.getCurrentGroupId(getThreadLocalRequest()); + List teamRoles = roleManager.listTeamsByUserAndGroup(userId, groupId); - CkanDataset dataset = getDataset(semanticIdentifier); + boolean isEditor = isEditor(username, teamRoles); + boolean isReviewer = isReviewer(username, teamRoles); - // look for the right domain this time - List extrasAsPairs = dataset.getExtras(); + if(!(isEditor | isReviewer)) + throw new Exception("You are not allowed to perform this operation!"); - for (CkanPair ckanPair : extrasAsPairs) { - if(ckanPair.getKey().contains(Constants.DOMAIN_CUSTOM_KEY)){ - return ckanPair.getValue().equalsIgnoreCase(domain); - } + // decrypt the url + RevertOperationUrl decryptedUrl = new RevertOperationUrl(encryptedUrl); + String adminInUrl = decryptedUrl.getAdmin(); + String uuid = decryptedUrl.getUuid(); + + logger.info("User " + username + " has requested to invert an operation on record with id " + uuid + " and admin in url was " + adminInUrl); + + // we need to check the timestamp (it has 24h validity) + boolean isValidTimestamp = decryptedUrl.isTimestampValid(); + + 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!"); + + // 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(!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; + } + + } + + /** + * Check if the current user is an editor + * @param username + * @param teamRoles + * @return true if he/she is an editor, false otherwise + */ + private boolean isEditor(String username, List teamRoles){ + + for (GCubeTeam team : teamRoles) { + if(team.getTeamName().equals(Constants.GRSF_CATALOGUE_EDITOR_ROLE)){ + logger.info("User " + username + " is allowed to modify GRSF records as editor"); + return true; + } + } return false; } - private CkanDataset getDataset(String semanticIdentifier) throws Exception{ - - String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest()); - DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl); - String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); - CkanDataset dataset = Utils.getRecordBySemanticIdentifier(semanticIdentifier, catalogue, catalogue.getApiKeyFromUsername(username)); - return dataset; + /** + * Check if the current user is a reviewer + * @param username + * @param teamRoles + * @return true if he/she is an reviewer, false otherwise + */ + private boolean isReviewer(String username, List teamRoles){ + for (GCubeTeam team : teamRoles) { + if(team.getTeamName().equals(team.getTeamName().equals(Constants.GRSF_CATALOGUE_REVIEWER_ROLE))){ + logger.info("User " + username + " is allowed to modify GRSF records as reviewer"); + return true; + } + } + return false; } - @Override public String checkIdentifierExists(String id) throws Exception { @@ -295,8 +416,7 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username)); if(dataset == null) - throw new Exception("This record doesn't exist"); - + throw new Exception("A record with id " + id + " doesn't exist"); return dataset.getExtrasAsHashMap().get(Constants.ITEM_URL_FIELD); } @@ -308,7 +428,7 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username)); if(dataset == null) - throw new Exception("This record doesn't exist"); + throw new Exception("A record with id " + id + " doesn't exist"); List extrasAsPairs = dataset.getExtras(); @@ -319,33 +439,39 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS } } - throw new Exception("This record doesn't exist in the specified domain"); + throw new Exception("A record with id " + id + " doesn't exist in domain " + domain); } - @Override - public String notifyProductUpdate(ManageProductBean bean) throws Exception{ + // @Override + // public boolean checkSemanticIdentifierExists(String semanticIdentifier) + // throws Exception { + // + // return getDataset(semanticIdentifier) != null; + // } + // + // @Override + // public boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) + // throws Exception { + // CkanDataset dataset = getDataset(semanticIdentifier); + // + // // look for the right domain this time + // List extrasAsPairs = dataset.getExtras(); + // + // for (CkanPair ckanPair : extrasAsPairs) { + // if(ckanPair.getKey().contains(Constants.DOMAIN_CUSTOM_KEY)){ + // return ckanPair.getValue().equalsIgnoreCase(domain); + // } + // } + // + // return false; + // } - logger.info("Creating notification for the bean " + bean + " to send to the knowledge base"); - try{ - - String context = Utils.getScopeFromClientUrl(getThreadLocalRequest()); - DataCatalogue catalogue = getCatalogue(context); - String administratorFullName = Utils.getCurrentUser(getThreadLocalRequest()).getFullname(); - String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); - // check if the base url of the service is in session - String keyPerContext = UtilMethods.concatenateSessionKeyScope(Constants.GRSF_UPDATER_SERVICE, context); - String baseUrl = (String)getThreadLocalRequest().getSession().getAttribute(keyPerContext); - if(baseUrl == null || baseUrl.isEmpty()){ - baseUrl = Utils.discoverEndPoint(context); - getThreadLocalRequest().getSession().setAttribute(keyPerContext, baseUrl); - } - return Utils.updateRecord(baseUrl, bean, catalogue, username, administratorFullName, getThreadLocalRequest(), - PortalContext.getConfiguration().getCurrentGroupId(getThreadLocalRequest())); - - }catch(Exception e){ - logger.error("Unable to update the product.." + e.getMessage()); - throw e; - } - } + // private CkanDataset getDataset(String semanticIdentifier) throws Exception{ + // String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest()); + // DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl); + // String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); + // CkanDataset dataset = Utils.getRecordBySemanticIdentifier(semanticIdentifier, catalogue, catalogue.getApiKeyFromUsername(username)); + // return dataset; + // } } diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFUpdaterServiceClient.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFUpdaterServiceClient.java new file mode 100644 index 0000000..01ba036 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFUpdaterServiceClient.java @@ -0,0 +1,214 @@ +package org.gcube.datacatalogue.grsf_manage_widget.server.manage; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.Iterator; +import java.util.List; + +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint; +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.shared.ConnectedBean; +import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean; +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.SimpleQuery; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpResponse; +import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpPost; +import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.StringEntity; +import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.CloseableHttpClient; +import eu.trentorise.opendata.jackan.internal.org.apache.http.util.EntityUtils; + +/** + * Exploits the grsf-services-updater service's methods https://app.swaggerhub.com/apis/ymark/grsf-services-updater/1.1.0 + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class GRSFUpdaterServiceClient { + + private static final Logger logger = LoggerFactory.getLogger(GRSFUpdaterServiceClient.class); + + /** + * Discover the service endpoint of the GRSF Updater service and return its url + * @param context + * @return + * @throws Exception + */ + public static String discoverEndPoint(String context) throws Exception{ + + String oldContext = ScopeProvider.instance.get(); + ScopeProvider.instance.set(context); + String toReturn = null; + try{ + SimpleQuery query = queryFor(ServiceEndpoint.class); + query.addCondition("$resource/Profile/Name/text() eq '"+ Constants.SERVICE_NAME +"'"); + query.addCondition("$resource/Profile/Category/text() eq '"+ Constants.SERVICE_CATEGORY +"'"); + DiscoveryClient client = clientFor(ServiceEndpoint.class); + List resources = client.submit(query); + + if (resources.size() == 0){ + logger.error("There is no Runtime Resource having name " + Constants.SERVICE_NAME +" and Category " + Constants.SERVICE_CATEGORY + " in this scope."); + throw new Exception("There is no Runtime Resource having name " + Constants.SERVICE_NAME +" and Category " + Constants.SERVICE_CATEGORY + " in this scope."); + } + else { + + for (ServiceEndpoint res : resources) { + Iterator accessPointIterator = res.profile().accessPoints().iterator(); + + while (accessPointIterator.hasNext()) { + ServiceEndpoint.AccessPoint accessPoint = (ServiceEndpoint.AccessPoint) accessPointIterator + .next(); + + // return the path + toReturn = accessPoint.address(); + } + } + } + }catch(Exception e){ + logger.error("Unable to retrieve such service endpoint information!", e); + throw e; + }finally{ + if(oldContext != null && !oldContext.equals(context)) + ScopeProvider.instance.set(oldContext); + } + return toReturn; + } + + /** + * Send updates to the knowledge base + * @param httpClient + * @param serviceUrl + * @param bean + * @param catalogue + * @param username + * @param fullName + */ + @SuppressWarnings("unchecked") + public static void updateKB(CloseableHttpClient httpClient, String serviceUrl, ManageProductBean bean, + DataCatalogue catalogue, String username, String fullName) throws Exception{ + + JSONObject obj = new JSONObject(); + obj.put(Constants.ADMINISTRATOR_FULLNAME, fullName); + obj.put(Constants.CATALOGUE_ID, bean.getCatalogueIdentifier()); + obj.put(Constants.KB_ID, bean.getKnowledgeBaseIdentifier()); + obj.put(Constants.NEW_STATUS, bean.getNewStatus().toString().toLowerCase()); + obj.put(Constants.OLD_STATUS, bean.getCurrentStatus().toString().toLowerCase()); + obj.put(Constants.TRACEABILITY_FLAG, bean.isTraceabilityFlag()); + + String annotation = bean.getAnnotation(); + if(annotation != null) + obj.put(Constants.ANNOTATION, annotation.replaceAll("\"", "")); + + obj.put(Constants.SHORT_NAME_OLD, bean.getShortName()); + + if(bean.getShortNameUpdated() == null || bean.getShortNameUpdated().isEmpty()) + bean.setShortNameUpdated(bean.getShortName()); + + obj.put(Constants.SHORT_NAME_NEW, bean.getShortNameUpdated()); + obj.put(Constants.OLD_STATUS, bean.getCurrentStatus().toString().toLowerCase()); + + // prepare connections + List connections = bean.getConnections(); + JSONArray connectionsJson = new JSONArray(); + + for(ConnectedBean c: connections){ + JSONObject cc = new JSONObject(); + if(c.isRemove() || (c.isConnect() && !c.isRemove())){ // do not send it if it needs to be unconnected but not removed + cc.put(Constants.SOURCE_KNOWLEDGE_BASE_ID, c.getSourceKnowledgeBaseId()); + cc.put(Constants.DEST_KNOWLEDGE_BASE_ID, c.getDestKnowledgeBaseId()); + cc.put(Constants.SOURCE_DOMAIN, c.getSourceDomain()); + cc.put(Constants.CONNECTION_TO_REMOVE, c.isRemove()); + } + connectionsJson.add(cc); + } + obj.put(Constants.CONNECTIONS, connectionsJson); + + // prepare similar grsf records + List similarRecords = bean.getSimilarGrsfRecords(); + JSONArray similarRecordsJson = new JSONArray(); + for(SimilarGRSFRecord s: similarRecords){ + JSONObject ss = new JSONObject(); + ss.put(Constants.KB_ID, s.getKnowledgeBaseId()); + ss.put(Constants.MERGE, s.isSuggestedMerge()); + similarRecordsJson.add(ss); + } + obj.put(Constants.SIMILAR_GRSF_RECORDS, similarRecordsJson); + + logger.info("Update request looks like " + obj.toJSONString()); + + HttpPost request = new HttpPost(serviceUrl + Constants.SERVICE_POST_UPDATER_METHOD); + request.setHeader("Accept", "application/json"); + request.setHeader("Content-type", "application/json"); + StringEntity params = new StringEntity(obj.toJSONString()); + request.setEntity(params); + HttpResponse response = httpClient.execute(request); + + logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); + + String result = EntityUtils.toString(response.getEntity()); + JSONParser parser = new JSONParser(); + JSONObject parsedJSON = (JSONObject)parser.parse(result); + + if(parsedJSON == null) + throw new Exception("There was a problem while performing this operation at knowledge base side"); + + if(response.getStatusLine().getStatusCode() == 200){ + logger.info("Record updated " + bean); + }else if(!(boolean) parsedJSON.get(Constants.UPDATE_RESULT)) + throw new IllegalArgumentException( + "Update failed for the following reason " + parsedJSON.get(Constants.ERROR_MESSAGE)); + + } + + /** + * Send updates to the knowledge base + * @param httpClient + * @param serviceUrl + * @param bean + * @param catalogue + * @param username + * @param fullName + */ + @SuppressWarnings("unchecked") + public static void revertOperation(CloseableHttpClient httpClient, String serviceUrl, String fullName, String uuid) throws Exception{ + + JSONObject obj = new JSONObject(); + obj.put(Constants.ADMINISTRATOR_FULLNAME, fullName); + obj.put(Constants.KB_ID, uuid); + + logger.info("Update request looks like " + obj.toJSONString()); + + HttpPost request = new HttpPost(serviceUrl + Constants.SERVICE_POST_REVERT_METHOD); + request.setHeader("Accept", "application/json"); + request.setHeader("Content-type", "application/json"); + StringEntity params = new StringEntity(obj.toJSONString()); + request.setEntity(params); + HttpResponse response = httpClient.execute(request); + + logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + + response.getStatusLine().getReasonPhrase()); + + String result = EntityUtils.toString(response.getEntity()); + JSONParser parser = new JSONParser(); + JSONObject parsedJSON = (JSONObject)parser.parse(result); + + if(parsedJSON == null) + throw new Exception("There was a problem while performing this operation at knowledge base side"); + + if(response.getStatusLine().getStatusCode() == 200){ + logger.info("Request has been submitted"); + }else if(!(boolean) parsedJSON.get(Constants.UPDATE_RESULT)) + throw new IllegalArgumentException( + "Request failed for the following reason " + parsedJSON.get(Constants.ERROR_MESSAGE)); + + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/RevertOperationUrl.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/RevertOperationUrl.java similarity index 72% rename from src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/RevertOperationUrl.java rename to src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/RevertOperationUrl.java index b16fb6e..4b20e0b 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/RevertOperationUrl.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/RevertOperationUrl.java @@ -1,4 +1,4 @@ -package org.gcube.datacatalogue.grsf_manage_widget.shared; +package org.gcube.datacatalogue.grsf_manage_widget.server.manage; import java.net.URLDecoder; import java.net.URLEncoder; @@ -22,11 +22,16 @@ public class RevertOperationUrl { public static final String TIMESTAMP_QUERY_PARAM = "t"; public static final String UUID_QUERY_PARAM = "uuid"; 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"); + MERGE("merge"); + // DISSECT("dissect"); private String name; private Operation(String name) { @@ -45,7 +50,6 @@ public class RevertOperationUrl { private long timestamp; private String uuid; private Operation operation; - private Operation op; /** * @param admin @@ -55,17 +59,20 @@ public class RevertOperationUrl { * @param op */ public RevertOperationUrl(String baseUrl, String admin, long timestamp, String uuid, - Operation operation, Operation op) { + Operation operation) { super(); this.baseUrl = baseUrl; this.admin = admin; this.timestamp = timestamp; this.uuid = uuid; this.operation = operation; - this.op = op; } - + /** + * Build a crypted, encoded and shortened url + * @return + * @throws Exception + */ public String getShortUrl() throws Exception{ String query = ADMIN_QUERY_PARAM + "=" + admin + "&" + TIMESTAMP_QUERY_PARAM + "=" + timestamp +"&" + UUID_QUERY_PARAM + "=" + uuid + "&" + OPERATION_REVERT_QUERY_PARAM + "=" + operation; @@ -131,13 +138,16 @@ public class RevertOperationUrl { default: break; } - } }catch(Exception e){ logger.error("Failed to parse url", e); } } + + public boolean isTimestampValid() { + return TTL + this.timestamp < System.currentTimeMillis(); + } public String getBaseUrl() { return baseUrl; @@ -188,58 +198,11 @@ public class RevertOperationUrl { this.operation = operation; } - - public Operation getOp() { - return op; - } - - - public void setOp(Operation op) { - this.op = op; - } - - @Override public String toString() { return "RevertOperationUrl [baseUrl=" + baseUrl + ", admin=" + admin + ", timestamp=" + timestamp + ", uuid=" + uuid - + ", operation=" + operation + ", op=" + op + "]"; + + ", operation=" + operation + "]"; } - // public static void main(String[] args) throws Exception { - // - // ScopeProvider.instance.set("/gcube/devNext/NextNext"); - // String url = "https://bluebridge.d4science.org/group/grsf_admin/data-catalogue?"; - // - // // try encrypt + encode - // String query = "admin=costantino.perciante&t="+ System.currentTimeMillis() +"&uuid=" + UUID.randomUUID().toString() + "&operation_revert=merge"; - // String encrypted = StringEncrypter.getEncrypter().encrypt(query); - // encrypted = URLEncoder.encode(encrypted, "UTF-8"); - // - // - // String encryptedUrl = url + encrypted; - // System.out.println("Encrypted is " + encryptedUrl); - // - // UrlShortener shortener = new UrlShortener(); - // String shortUrl = null; - // try{ - // if(shortener!=null && shortener.isAvailable()) - // shortUrl = shortener.shorten(encryptedUrl); - // }catch (Exception e) { - // e.printStackTrace(); - // shortUrl = encryptedUrl; - // } - // - // System.out.println("Encrypted is " + shortUrl); - // - // - // // try decode + decrypt - // String params = encryptedUrl.split("\\?")[1]; - // System.out.println("Params encrypted are " + params); - // String decoded = URLDecoder.decode(encrypted, "UTF-8"); - // String decrypted = StringEncrypter.getEncrypter().decrypt(decoded); - // - // System.out.println("Decrypted is " + decrypted); - //} - } diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/SocialCommunications.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/SocialCommunications.java new file mode 100644 index 0000000..2a8a60a --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/SocialCommunications.java @@ -0,0 +1,400 @@ +package org.gcube.datacatalogue.grsf_manage_widget.server.manage; + +import static org.gcube.resources.discovery.icclient.ICFactory.client; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +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.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.gcube.vomanagement.usermanagement.RoleManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.impl.LiferayRoleManager; +import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeTeam; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpEntity; +import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpResponse; +import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpPost; +import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.StringEntity; +import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.CloseableHttpClient; +import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.HttpClientBuilder; +import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.LaxRedirectStrategy; +import eu.trentorise.opendata.jackan.internal.org.apache.http.util.EntityUtils; + + +/** + * For managing the different interactions with social channels (posts and mails) + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class SocialCommunications { + + private static final Logger logger = LoggerFactory.getLogger(SocialCommunications.class); + + // for discovering social networking service + private static final String resource = "jersey-servlet"; + private static final String serviceName = "SocialNetworking"; + private static final String serviceClass = "Portal"; + + // social operations + private static final String SOCIAL_SERVICE_APPLICATION_TOKEN = "/2/tokens/generate-application-token/"; + private static final String SOCIAL_SERVICE_WRITE_APPLICATION_POST = "/2/posts/write-post-app/"; + private static final String SOCIAL_SEND_EMAIL = "/2/messages/write-message/"; + private static final String MEDIATYPE_JSON = "application/json"; + + // for writing a post in the GRSF admin context + private static final String APPLICATION_ID_CATALOGUE_MANAGER = "org.gcube.datacatalogue.GRSFNotifier"; + + // emails to be sent to editors and reviewers and post to be written into the grsf admin vre + private static final String POST_MESSAGE = "Dear members," + + "
The record 'PRODUCT_TITLE' has been just updated by USER_FULLNAME." + + "
You can inspect it here: PRODUCT_URL
"; + + private static final String EMAIL_MESSAGE_REVIEWER = "Dear GRSF Reviewer," + + "
an update on the record named 'PRODUCT_TITLE' has been requested by USER_FULLNAME." + + "
It is available here LINK_RECORD."; + + private static final String EMAIL_MESSAGE_EDITOR = "Dear USER_FULLNAME," + + "
your request for the record 'PRODUCT_TITLE' has been accepted." + + "
It is available here LINK_RECORD."; + + private static final String REVERT_LINK_PIECE = "
The request involves a merge operation. You can reject the merge by exploiting this link LINK in the following 24 hours."; + + /** + * + * @param context + * @return + */ + private static String getBaseUrlSocialService(String context){ + + if(context == null || context.isEmpty()) + throw new IllegalArgumentException("A valid context is needed to discover the service"); + + String oldContext = ScopeProvider.instance.get(); + ScopeProvider.instance.set(context); + + String basePath = null; + try{ + + SimpleQuery query = queryFor(GCoreEndpoint.class); + query.addCondition(String.format("$resource/Profile/ServiceClass/text() eq '%s'",serviceClass)); + query.addCondition("$resource/Profile/DeploymentData/Status/text() eq 'ready'"); + query.addCondition(String.format("$resource/Profile/ServiceName/text() eq '%s'",serviceName)); + query.setResult("$resource/Profile/AccessPoint/RunningInstanceInterfaces//Endpoint[@EntryName/string() eq \""+resource+"\"]/text()"); + + DiscoveryClient client = client(); + List endpoints = client.submit(query); + if (endpoints == null || endpoints.isEmpty()) + throw new Exception("Cannot retrieve the GCoreEndpoint serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+context); + + + basePath = endpoints.get(0); + if(basePath==null) + throw new Exception("Endpoint:"+resource+", is null for serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+context); + + }catch(Exception e){ + logger.error("Unable to retrieve such service endpoint information!", e); + }finally{ + if(oldContext != null && !oldContext.equals(context)) + ScopeProvider.instance.set(oldContext); + } + logger.info("Found base path " + basePath + " for the service"); + return basePath; + } + + /** + * Notify the users about the required changes. + * @param bean + * @param url + * @param username + * @param fullName + * @param hashtags + * @param enablePostNotification + */ + @SuppressWarnings("unchecked") + public static void writeProductPost(ManageProductBean bean, String username, String fullName, List hashtags, boolean enablePostNotification){ + + // 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"); + + }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 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"); + + // replace + String message = POST_MESSAGE.replace("PRODUCT_TITLE", bean.getGrsfName()).replace("PRODUCT_URL", bean.getRecordUrl()).replace("USER_FULLNAME", fullName); + + if(hashtags != null && !hashtags.isEmpty()) + for (String hashtag : hashtags) { + String modifiedHashtag = hashtag.replaceAll(" ", "_").replace("_+", "_"); // no empty spaces allowed + if(modifiedHashtag.endsWith("_")) + modifiedHashtag = modifiedHashtag.substring(0, modifiedHashtag.length() - 1); + message += " #" + modifiedHashtag; + } + + logger.info("The post that is going to be written is -> " + message); + postRequest = new HttpPost(basePath + SOCIAL_SERVICE_WRITE_APPLICATION_POST + "?gcube-token=" + applicationToken); + JSONObject object = new JSONObject(); + object.put("text", message); + object.put("enable_notification", enablePostNotification); + input = new StringEntity(object.toJSONString()); + input.setContentType(MEDIATYPE_JSON); + postRequest.setEntity(input); + response = client.execute(postRequest); + + Map mapResponseWritePost = getResponseEntityAsJSON(response); + + if (response.getStatusLine().getStatusCode() != 201) + throw new RuntimeException("Failed to write application post : HTTP error code : " + + response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message")); + } + + } + + }catch(Exception e){ + logger.error("Failed to create a post", e); + } + } + } + + /** + * Send an email to the administrator as well as the + * @param bean + * @param catalogue + * @param username + * @param fullName + * @param isMergeInvolved + * @param httpSession + * @throws Exceptio + */ + @SuppressWarnings("unchecked") + public static void sendEmailAdministrators( + ManageProductBean bean, + DataCatalogue catalogue, + String username, + String fullName, + long groupId, + HttpServletRequest httpServletRequest, + boolean isMergeInvolved) throws Exception { + + // get the list of GRSF Reviewers to alert them as well + RoleManager roleManager = new LiferayRoleManager(); + List teamRoles = roleManager.listTeamsByGroup(groupId); + List 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(username); + + logger.info("List of " + Constants.GRSF_CATALOGUE_REVIEWER_ROLE + " is " + reviewers); + + // build the url that allows to revert the operation + Operation operation = Operation.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"); + + }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 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 revertUrl = getEncodedUrlManage(operation, username, System.currentTimeMillis(), bean.getKnowledgeBaseIdentifier(), httpServletRequest); + + String messageToEditor = (EMAIL_MESSAGE_EDITOR + + (isMergeInvolved? REVERT_LINK_PIECE : "")).replace("USER_FULLNAME", fullName).replace("PRODUCT_TITLE", bean.getGrsfName()).replace("LINK_RECORD", bean.getRecordUrl()).replace("LINK", revertUrl); + String messageToReviewer = (EMAIL_MESSAGE_REVIEWER+ + (isMergeInvolved? REVERT_LINK_PIECE : "")).replace("USER_FULLNAME", fullName).replace("PRODUCT_TITLE", bean.getGrsfName()).replace("LINK_RECORD", bean.getRecordUrl()).replace("LINK", revertUrl); + String subject = "Update 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", username); + recipients.add(recipient); + reqMessage.put("recipients", recipients); + input = new StringEntity(reqMessage.toJSONString()); + input.setContentType(MEDIATYPE_JSON); + postRequest.setEntity(input); + response = client.execute(postRequest); + + Map 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 create a post", e); + } + } + + } + + /** + * Create the url to be send for reverting the operation + * @param httpSession + * @return + * @throws Exception + */ + public static String getEncodedUrlManage(Operation 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(); + return shortUrl; + } + + /** + * Convert the json response to a map + * @param response + * @return + */ + @SuppressWarnings("unchecked") + private static Map getResponseEntityAsJSON(HttpResponse response){ + + Map toReturn = null; + HttpEntity entity = response.getEntity(); + + if (entity != null) { + try { + toReturn = new HashMap(); + String jsonString = EntityUtils.toString(response.getEntity()); + logger.debug("Response as string is " + jsonString); + ObjectMapper objectMapper = new ObjectMapper(); + toReturn = objectMapper.readValue(jsonString, HashMap.class); + logger.debug("Map is " + toReturn); + }catch(Exception e){ + logger.error("Failed to read json object", e); + } + } + + return toReturn; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/Utils.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/Utils.java index a5c388d..03360a5 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/Utils.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/Utils.java @@ -22,6 +22,7 @@ import javax.servlet.http.HttpSession; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; import org.gcube.common.encryption.StringEncrypter; import org.gcube.common.portal.PortalContext; import org.gcube.common.resources.gcore.ServiceEndpoint; @@ -34,10 +35,9 @@ import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueRunningCluste import org.gcube.datacatalogue.ckanutillibrary.shared.ex.ApplicationProfileNotFoundException; import org.gcube.datacatalogue.common.Constants; import org.gcube.datacatalogue.common.enums.Status; +import org.gcube.datacatalogue.grsf_manage_widget.server.manage.RevertOperationUrl.Operation; 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.RevertOperationUrl; -import org.gcube.datacatalogue.grsf_manage_widget.shared.RevertOperationUrl.Operation; import org.gcube.datacatalogue.grsf_manage_widget.shared.SimilarGRSFRecord; import org.gcube.portlets.user.urlshortener.UrlShortener; import org.gcube.resources.discovery.client.api.DiscoveryClient; @@ -177,7 +177,9 @@ public class Utils { } } logger.debug("Map is " + namespacesMap); - httpSession.setAttribute(sessionKey, namespacesMap); + + // put them into session for speeding up the operations + httpSession.setAttribute(sessionKey, namespacesMap); return namespacesMap; } catch (Exception e) { logger.error("Error while trying to fetch applicationProfile profile from the infrastructure", e); @@ -221,7 +223,7 @@ public class Utils { * @param extrasAsPairs * @return */ - public static Map> getExtras(List extrasAsPairs){ + public static Map> getExtrasAsHashMap(List extrasAsPairs){ Map> toReturn = new HashMap>(); @@ -242,52 +244,6 @@ public class Utils { return toReturn; } - /** - * Discover the service endpoint and return its url - * @param context - * @return the url of the service on success, null otherwise - */ - public static String discoverEndPoint(String context){ - - String oldContext = ScopeProvider.instance.get(); - ScopeProvider.instance.set(context); - String toReturn = null; - try{ - SimpleQuery query = queryFor(ServiceEndpoint.class); - query.addCondition("$resource/Profile/Name/text() eq '"+ Constants.SERVICE_NAME +"'"); - query.addCondition("$resource/Profile/Category/text() eq '"+ Constants.SERVICE_CATEGORY +"'"); - DiscoveryClient client = clientFor(ServiceEndpoint.class); - List resources = client.submit(query); - - if (resources.size() == 0){ - logger.error("There is no Runtime Resource having name " + Constants.SERVICE_NAME +" and Category " + Constants.SERVICE_CATEGORY + " in this scope."); - throw new Exception("There is no Runtime Resource having name " + Constants.SERVICE_NAME +" and Category " + Constants.SERVICE_CATEGORY + " in this scope."); - } - else { - - for (ServiceEndpoint res : resources) { - - Iterator accessPointIterator = res.profile().accessPoints().iterator(); - - while (accessPointIterator.hasNext()) { - ServiceEndpoint.AccessPoint accessPoint = (ServiceEndpoint.AccessPoint) accessPointIterator - .next(); - - // return the path - toReturn = accessPoint.address(); - } - } - } - }catch(Exception e){ - logger.error("Unable to retrieve such service endpoint information!", e); - }finally{ - if(oldContext != null && !oldContext.equals(context)) - ScopeProvider.instance.set(oldContext); - } - - return toReturn; - } - /** * Send an update for this bean * @param baseUrl @@ -297,7 +253,7 @@ public class Utils { * @return true on success, false otherwise */ public static String updateRecord(String serviceUrl, ManageProductBean bean, DataCatalogue catalogue, String username, - String fullName, HttpServletRequest httpServletRequest, long groupId) throws Exception{ + String fullName, HttpServletRequest httpServletRequest, long groupId, String context, String token) throws Exception{ if(serviceUrl == null) throw new IllegalArgumentException("GRSF Updater service url cannot be null"); @@ -312,14 +268,21 @@ public class Utils { updateStatusInvolvedRecords(bean, catalogue); // send update to the knowledge base - updateKB(httpClient, serviceUrl, bean, catalogue, username, fullName); + GRSFUpdaterServiceClient.updateKB(httpClient, serviceUrl, bean, catalogue, username, fullName); - // send email to Editors and Reviewers if merges are involved or the record was rejected (but the record was the result of a merge) TODO - if(bean.isMergesInvolved() || bean.getNewStatus().equals(Status.Rejected)) - sendEmailAdministrators(bean, catalogue, username, fullName, groupId, httpServletRequest); + // manage interactions through a separated thread but set there security token and context + new Thread(()->{ + + ScopeProvider.instance.set(context); + SecurityTokenProvider.instance.set(token); + + // send email to Editors and Reviewers + SocialCommunications.sendEmailAdministrators(bean, catalogue, username, fullName, groupId, httpServletRequest, bean.isMergesInvolved()); - // create a post about the operation - createSocialPost(bean, catalogue, username, fullName); + // create a post about the operation + SocialCommunications.createSocialPost(bean, catalogue, username, fullName, hashtags); + + }).start(); }catch(Exception e){ logger.error("Unable to update this Item ", e); @@ -343,7 +306,7 @@ public class Utils { for(SimilarGRSFRecord s: bean.getSimilarGrsfRecords()){ if(s.isSuggestedMerge()){ String productId = s.getKnowledgeBaseId(); - Map> extrasMap = getExtras(catalogue.getDataset(productId, sysApi).getExtras()); + Map> extrasMap = getExtrasAsHashMap(catalogue.getDataset(productId, sysApi).getExtras()); extrasMap.put(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY, Arrays.asList(Status.To_be_Merged.getOrigName())); catalogue.patchProductCustomFields(productId, sysApi, extrasMap); } @@ -351,174 +314,12 @@ public class Utils { // update the current status record String productId = bean.getKnowledgeBaseIdentifier(); - Map> extrasMap = getExtras(catalogue.getDataset(productId, sysApi).getExtras()); + Map> extrasMap = getExtrasAsHashMap(catalogue.getDataset(productId, sysApi).getExtras()); extrasMap.put(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY, Arrays.asList(Status.To_be_Merged.getOrigName())); catalogue.patchProductCustomFields(productId, sysApi, extrasMap); } - /** - * Create a post with proper hashtags of the action taken by who and on which record - * @param bean - * @param catalogue - * @param username - * @param fullName - */ - private static void createSocialPost(ManageProductBean bean, - DataCatalogue catalogue, String username, String fullName) { - List hashtags = getHashTagsFromActions(bean); - // TODO - - } - - /** - * Send an email to the administrator as well as the - * @param bean - * @param catalogue - * @param username - * @param fullName - * @param httpSession - * @throws Exceptio - */ - private static void sendEmailAdministrators(ManageProductBean bean, - DataCatalogue catalogue, String username, String fullName, long groupId, HttpServletRequest httpServletRequest) throws Exception { - - // get the list of GRSF Reviewers to alert as well - RoleManager roleManager = new LiferayRoleManager(); - List teamRoles = roleManager.listTeamsByGroup(groupId); - List 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())); - } - - logger.info("List of " + Constants.GRSF_CATALOGUE_REVIEWER_ROLE + " is " + reviewers); - - // build the url that allows to revert the operation TODO - Operation operation = bean.isMergesInvolved() ? Operation.MERGE : Operation.DISSECT; - getEncodedUrlManage(operation, username, System.currentTimeMillis(), bean.getKnowledgeBaseIdentifier(), httpServletRequest); - - String object = "A GRSF Record has been modified"; - - // send the emails reviewers - String messageReviewer = ""; - - // send email to the editor - String messageEditor = ""; - - // TODO - } - - /** - * Create the url to be send for reverting the operation - * @param httpSession - * @return - * @throws Exception - */ - public static String getEncodedUrlManage(Operation operation, String administrator, long timestamp, String uuid, HttpServletRequest httpServletRequest) throws Exception{ - - String clientUrl = getCurrentClientUrl(httpServletRequest).split("\\?")[0]; // ignore other parameters - RevertOperationUrl operationUrl = new RevertOperationUrl(clientUrl, administrator, timestamp, uuid, operation, operation); - String shortUrl = operationUrl.getShortUrl(); - logger.info("Encrypted and shortened url " + shortUrl); - return shortUrl; - } - - /** - * Get the list of hashtags from the actions taken onto the record - * @param bean - * @return - */ - private static List getHashTagsFromActions(ManageProductBean bean) { - // TODO Auto-generated method stub - return null; - } - - /** - * Send updates to the knowledge base - * @param httpClient - * @param serviceUrl - * @param bean - * @param catalogue - * @param username - * @param fullName - */ - @SuppressWarnings("unchecked") - private static void updateKB(CloseableHttpClient httpClient, String serviceUrl, ManageProductBean bean, - DataCatalogue catalogue, String username, String fullName) throws Exception{ - - JSONObject obj = new JSONObject(); - obj.put(Constants.ADMINISTRATOR_FULLNAME, fullName); - obj.put(Constants.CATALOGUE_ID, bean.getCatalogueIdentifier()); - obj.put(Constants.KB_ID, bean.getKnowledgeBaseIdentifier()); - obj.put(Constants.NEW_STATUS, bean.getNewStatus().toString().toLowerCase()); - obj.put(Constants.OLD_STATUS, bean.getCurrentStatus().toString().toLowerCase()); - obj.put(Constants.TRACEABILITY_FLAG, bean.isTraceabilityFlag()); - - String annotation = bean.getAnnotation(); - if(annotation != null) - obj.put(Constants.ANNOTATION, annotation.replaceAll("\"", "")); - - obj.put(Constants.SHORT_NAME_OLD, bean.getShortName()); - - if(bean.getShortNameUpdated() == null || bean.getShortNameUpdated().isEmpty()) - bean.setShortNameUpdated(bean.getShortName()); - - obj.put(Constants.SHORT_NAME_NEW, bean.getShortNameUpdated()); - obj.put(Constants.OLD_STATUS, bean.getCurrentStatus().toString().toLowerCase()); - - // prepare connections - List connections = bean.getConnectTo(); - JSONArray connectionsJson = new JSONArray(); - - for(ConnectedBean c: connections){ - JSONObject cc = new JSONObject(); - if(c.isRemove() || (c.isConnect() && !c.isRemove())){ // do not send it if it needs to be unconnected but not removed - cc.put(Constants.SOURCE_KNOWLEDGE_BASE_ID, c.getSourceKnowledgeBaseId()); - cc.put(Constants.DEST_KNOWLEDGE_BASE_ID, c.getDestKnowledgeBaseId()); - cc.put(Constants.SOURCE_DOMAIN, c.getSourceDomain()); - cc.put(Constants.CONNECTION_TO_REMOVE, c.isRemove()); - } - connectionsJson.add(cc); - } - obj.put(Constants.CONNECTIONS, connectionsJson); - - // prepare similar grsf records - List similarRecords = bean.getSimilarGrsfRecords(); - JSONArray similarRecordsJson = new JSONArray(); - for(SimilarGRSFRecord s: similarRecords){ - JSONObject ss = new JSONObject(); - ss.put(Constants.KB_ID, s.getKnowledgeBaseId()); - ss.put(Constants.MERGE, s.isSuggestedMerge()); - similarRecordsJson.add(ss); - } - obj.put(Constants.SIMILAR_GRSF_RECORDS, similarRecordsJson); - - logger.info("Update request looks like " + obj.toJSONString()); - - HttpPost request = new HttpPost(serviceUrl + Constants.SERVICE_POST_METHOD); - request.setHeader("Accept", "application/json"); - request.setHeader("Content-type", "application/json"); - StringEntity params = new StringEntity(obj.toJSONString()); - request.setEntity(params); - HttpResponse response = httpClient.execute(request); - - logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); - - String result = EntityUtils.toString(response.getEntity()); - JSONParser parser = new JSONParser(); - JSONObject parsedJSON = (JSONObject)parser.parse(result); - - if(response.getStatusLine().getStatusCode() != Constants.STATUS_SUCCESS){ - throw new Exception("Update failed at knowledge base side!"); - }else if(!(boolean) parsedJSON.get(Constants.UPDATE_RESULT)) - throw new IllegalArgumentException( - "Update failed for the following reason " + parsedJSON.get(Constants.ERROR_MESSAGE)); - - } - /** * Get the scope in which ckan information needs to be discovered from the url * @param httpServletRequest @@ -649,8 +450,9 @@ public class Utils { * @param url * @param clg * @return + * @throws Exception */ - public static CkanDataset getDatasetFromUrl(String url, DataCatalogue clg, String apiKey){ + public static CkanDataset getDatasetFromUrl(String url, DataCatalogue clg, String apiKey) throws Exception{ if(url == null || url.isEmpty()) return null; @@ -667,7 +469,7 @@ public class Utils { return clg.getDataset(uuidFound, apiKey); } - return null; + throw new Exception("No record exists with such url " + url); } /** @@ -768,27 +570,31 @@ public class Utils { * @param json * @param sourceIdentifier * @param sourceDomain + * @param grsfDomain * @return * @throws ParseException */ - public static ConnectedBean connectedBeanRecordFromJson(String json, String sourceIdentifier, String sourceDomain, - DataCatalogue clg) throws ParseException { + public static ConnectedBean connectedBeanRecordFromUrl( + String sourceIdentifier, + String sourceDomain, + String sourceUrl, + String destUrl, + DataCatalogue clg, + String apiKey) throws ParseException { - if(json == null) + if(destUrl == null) return null; - JSONParser parser = new JSONParser(); - JSONObject object = (JSONObject)parser.parse(json); - - String uuidDest = (String)object.get(Constants.CONNECTED_RECORD_KNOWLEDGE_BASE_ID_JSON_KEY); - String url = clg.getUrlFromDatasetIdOrName(uuidDest); + String connectedBeanUuid = Utils.getDatasetKnowledgeBaseIdFromUrl(destUrl); + CkanDataset destDataset = clg.getDataset(connectedBeanUuid, apiKey); return new ConnectedBean( sourceIdentifier, sourceDomain, - (String)object.get(Constants.CONNECTED_RECORD_SHORT_NAME_JSON_KEY), - (String)object.get(Constants.CONNECTED_RECORD_SEMANTIC_IDENTIFIER_JSON_KEY), - uuidDest, - url + sourceUrl, + connectedBeanUuid, + destDataset.getTitle(), + destUrl, + destDataset.getExtrasAsHashMap().get(Constants.DOMAIN_CUSTOM_KEY) ); } @@ -799,10 +605,8 @@ public class Utils { * @throws Exception */ public static String fetchSysAPI(String context) throws Exception{ - DataCatalogueRunningCluster catalogueRunningInstance = new DataCatalogueRunningCluster(context); return catalogueRunningInstance.getSysAdminToken(); - } } diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ConnectedBean.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ConnectedBean.java index 586f5a3..ddcd7e2 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ConnectedBean.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ConnectedBean.java @@ -12,10 +12,11 @@ public class ConnectedBean implements Serializable{ private static final long serialVersionUID = -4863776727351488790L; private String sourceKnowledgeBaseId; private String sourceDomain; // i.e. Stock or Fishery - private String destShortName; - private String destSemanticIdentifier; - private String destKnowledgeBaseId; // the dest indentifier of a Fishery or Stock (the link is from a Stock to a Fishery and viceversa) - private String url; + private String sourceUrl; + private String destKnowledgeBaseId; // the dest identifier of a Fishery or Stock (the link is from a Stock to a Fishery and vice versa) + private String destName; + private String destUrl; + private String destDomain; // please note that this MUST be different from sourceDomain private boolean remove; private boolean connect; @@ -24,25 +25,26 @@ public class ConnectedBean implements Serializable{ } /** - * * @param sourceKnowledgeBaseId * @param sourceDomain - * @param destShortName - * @param destSemanticIdentifier + * @param sourceUrl * @param destKnowledgeBaseId - * @param url + * @param destTitle + * @param destUrl + * @param remove + * @param connect */ public ConnectedBean(String sourceKnowledgeBaseId, String sourceDomain, - String destShortName, String destSemanticIdentifier, - String destKnowledgeBaseId, String url) { + String sourceUrl, String destKnowledgeBaseId, String destName, + String destUrl, String destDomain) { super(); this.sourceKnowledgeBaseId = sourceKnowledgeBaseId; this.sourceDomain = sourceDomain; - this.destShortName = destShortName; - this.destSemanticIdentifier = destSemanticIdentifier; + this.sourceUrl = sourceUrl; this.destKnowledgeBaseId = destKnowledgeBaseId; - this.url = url; - + this.destName = destName; + this.destUrl = destUrl; + this.destDomain = destDomain; } public boolean isConnect() { @@ -53,21 +55,6 @@ public class ConnectedBean implements Serializable{ this.connect = connect; } - public String getDestShortName() { - return destShortName; - } - - public void setDestShortName(String destShortName) { - this.destShortName = destShortName; - } - - public String getDestSemanticIdentifier() { - return destSemanticIdentifier; - } - - public void setDestSemanticIdentifier(String destSemanticIdentifier) { - this.destSemanticIdentifier = destSemanticIdentifier; - } public String getSourceKnowledgeBaseId() { return sourceKnowledgeBaseId; } @@ -99,20 +86,45 @@ public class ConnectedBean implements Serializable{ this.remove = remove; } - public String getUrl() { - return url; + public String getSourceUrl() { + return sourceUrl; } - public void setUrl(String url) { - this.url = url; + + public void setSourceUrl(String sourceUrl) { + this.sourceUrl = sourceUrl; + } + + public String getDestName() { + return destName; + } + + public void setDestName(String destName) { + this.destName = destName; + } + + public String getDestUrl() { + return destUrl; + } + + public void setDestUrl(String destUrl) { + this.destUrl = destUrl; + } + + public String getDestDomain() { + return destDomain; + } + + public void setDestDomain(String destDomain) { + this.destDomain = destDomain; } @Override public String toString() { return "ConnectedBean [sourceKnowledgeBaseId=" + sourceKnowledgeBaseId - + ", sourceDomain=" + sourceDomain + ", destShortName=" - + destShortName + ", destSemanticIdentifier=" - + destSemanticIdentifier + ", destKnowledgeBaseId=" - + destKnowledgeBaseId + ", url=" + url + ", remove=" + remove + + ", sourceDomain=" + sourceDomain + ", sourceUrl=" + sourceUrl + + ", destKnowledgeBaseId=" + destKnowledgeBaseId + + ", destName=" + destName + ", destUrl=" + destUrl + + ", destDomain=" + destDomain + ", remove=" + remove + ", connect=" + connect + "]"; } diff --git a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ManageProductBean.java b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ManageProductBean.java index 14741bd..c30c02a 100644 --- a/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ManageProductBean.java +++ b/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/shared/ManageProductBean.java @@ -1,6 +1,7 @@ package org.gcube.datacatalogue.grsf_manage_widget.shared; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -14,13 +15,13 @@ public class ManageProductBean implements Serializable{ private static final long serialVersionUID = -4882608487467259326L; private String semanticIdentifier; // Stock id or Fishery id - private String catalogueIdentifier; // catalogue id + private String catalogueIdentifier; // Catalogue id private String knowledgeBaseIdentifier; // GRSF UUID private String grsfType; // Fishery or Stock type (e.g., Assessment_Unit, Marine Resource and so on) private String grsfDomain; // fishery/stock private String grsfName; // Fishery name or Stock name private String shortName; // it is editable ... - private String shortNameUpdated; + private String shortNameUpdated; // the updated one, if any private boolean traceabilityFlag; //from false to true etc private Status currentStatus; private Status newStatus; @@ -28,20 +29,34 @@ public class ManageProductBean implements Serializable{ private Map extrasIfAvailable; // read from GRSFManageEntries resource private List sources; // sources for this record private List similarGrsfRecords; - private List connectTo; - private boolean mergesInvolved; + private List suggestedByKnowledgeBase; + private List suggestdByAdministrator = new ArrayList(0); + private List currentConnections; + private List connections; // the one to used eventually + private boolean mergesInvolved; // important: in this case an email must be sent to the editors/reviewers + private String recordUrl; // this record url public ManageProductBean() { super(); } - public ManageProductBean(String semanticIdentifier, - String catalogueIdentifier, String knowledgeBaseIdentifier, - String grsfType, String grsfDomain, String grsfName, - String shortName, boolean traceabilityFlag, Status currentStatus, - Status newStatus, String annotation, - Map extrasIfAvailable, List sources, - List similarGrsfRecords, List connectedBeans, boolean mergesInvolved) { + public ManageProductBean( + String semanticIdentifier, + String catalogueIdentifier, + String knowledgeBaseIdentifier, + String grsfType, + String grsfDomain, + String grsfName, + String shortName, + boolean traceabilityFlag, + Status currentStatus, + String recordUrl, + Map extrasIfAvailable, + List sources, + List similarGrsfRecords, + List currentConnections, + List suggestedByKnowledgeBase + ) { super(); this.semanticIdentifier = semanticIdentifier; this.catalogueIdentifier = catalogueIdentifier; @@ -53,13 +68,12 @@ public class ManageProductBean implements Serializable{ this.shortNameUpdated = shortName; this.traceabilityFlag = traceabilityFlag; this.currentStatus = currentStatus; - this.newStatus = newStatus; - this.annotation = annotation; this.extrasIfAvailable = extrasIfAvailable; this.sources = sources; this.similarGrsfRecords = similarGrsfRecords; - this.connectTo = connectedBeans; - this.mergesInvolved = mergesInvolved; + this.currentConnections = currentConnections; + this.suggestedByKnowledgeBase = suggestedByKnowledgeBase; + this.recordUrl = recordUrl; } public String getSemanticIdentifier() { @@ -184,12 +198,30 @@ public class ManageProductBean implements Serializable{ this.shortNameUpdated = shortNameUpdated; } - public List getConnectTo() { - return connectTo; + public List getSuggestedByKnowledgeBase() { + return suggestedByKnowledgeBase; } - public void setConnectTo(List connectTo) { - this.connectTo = connectTo; + public void setSuggestedByKnowledgeBase( + List suggestedByKnowledgeBase) { + this.suggestedByKnowledgeBase = suggestedByKnowledgeBase; + } + + public List getSuggestdByAdministrator() { + return suggestdByAdministrator; + } + + public void setSuggestdByAdministrator( + List suggestdByAdministrator) { + this.suggestdByAdministrator = suggestdByAdministrator; + } + + public List getCurrentConnections() { + return currentConnections; + } + + public void setCurrentConnections(List currentConnections) { + this.currentConnections = currentConnections; } public boolean isMergesInvolved() { @@ -200,6 +232,22 @@ public class ManageProductBean implements Serializable{ this.mergesInvolved = mergesInvolved; } + public String getRecordUrl() { + return recordUrl; + } + + public void setRecordUrl(String recordUrl) { + this.recordUrl = recordUrl; + } + + public List getConnections() { + return connections; + } + + public void setConnections(List connections) { + this.connections = connections; + } + @Override public String toString() { return "ManageProductBean [semanticIdentifier=" + semanticIdentifier @@ -212,7 +260,12 @@ public class ManageProductBean implements Serializable{ + currentStatus + ", newStatus=" + newStatus + ", annotation=" + annotation + ", extrasIfAvailable=" + extrasIfAvailable + ", sources=" + sources + ", similarGrsfRecords=" - + similarGrsfRecords + ", connectTo=" + connectTo - + ", mergesInvolved=" + mergesInvolved + "]"; + + similarGrsfRecords + ", suggestedByKnowledgeBase=" + + suggestedByKnowledgeBase + ", suggestdByAdministrator=" + + suggestdByAdministrator + ", currentConnections=" + + currentConnections + ", connections=" + connections + + ", mergesInvolved=" + mergesInvolved + ", recordUrl=" + + recordUrl + "]"; } + }