grsf-manage-widget/src/main/java/org/gcube/datacatalogue/grsf_manage_widget/server/manage/GRSFNotificationService.java

478 lines
22 KiB
Java

package org.gcube.datacatalogue.grsf_manage_widget.server.manage;
import java.util.ArrayList;
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;
import org.gcube.datacatalogue.common.Constants;
import org.gcube.datacatalogue.common.enums.Product_Type;
import org.gcube.datacatalogue.common.enums.Sources;
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.SimilarGRSFRecord;
import org.gcube.datacatalogue.grsf_manage_widget.shared.SourceRecord;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ex.NoGRSFRecordException;
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;
/**
* Endpoint for sending update records information to GRSF KnowledgeBase.
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public class GRSFNotificationService extends RemoteServiceServlet implements GRSFManageWidgetService{
private static final long serialVersionUID = -4534905087994875893L;
//private static final Log logger = LogFactoryUtil.getLog(GRSFNotificationService.class);
private static final Logger logger = LoggerFactory.getLogger(GRSFNotificationService.class);
/**
* Instanciate the ckan util library.
* Since it needs the scope, we need to check if it is null or not
* @param discoverScope if you want to the discover the utils library in this specified scope
* @return DataCatalogue object
* @throws Exception
*/
public DataCatalogue getCatalogue(String discoverScope) throws Exception{
String currentScope = Utils.getCurrentContext(getThreadLocalRequest(), true);
DataCatalogue instance = null;
try{
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){
logger.error("Unable to retrieve ckan utils. Error was " + e.toString());
throw e;
}
return instance;
}
@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<ConnectedBean> connectTo = new ArrayList<>();
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");
toReturn.setShortName("Widow rockfish - US West Coast");
toReturn.setShortNameUpdated("Widow rockfish - US West Coast");
toReturn.setGrsfName("sebastes entomelas FAO 77 FAO 67");
toReturn.setTraceabilityFlag(true);
toReturn.setCurrentStatus(Status.Pending);
toReturn.setSemanticIdentifier("asfis:WRO+fao:67;FAO");
ArrayList<SourceRecord> sources = new ArrayList<SourceRecord>();
sources.add(new SourceRecord("RAM", "http://www.google.it"));
sources.add(new SourceRecord("FIRMS", "http://www.google.it"));
sources.add(new SourceRecord("FishSource", "http://www.google.it"));
toReturn.setSources(sources);
List<SimilarGRSFRecord> similarGrsfRecords = new ArrayList<SimilarGRSFRecord>();
similarGrsfRecords.add(new SimilarGRSFRecord("same species overlapping water areas",
Utils.getDatasetKnowledgeBaseIdFromUrl("http://data.d4science.org/ctlg/GRSF_Admin/1a03d6d8-002d-39b9-9255-a954c8ee2bf0")
,"unknown:ONCORHYNCHUS GORBUSCHA+unknown:USA-AKSTATE-KELPB",
"Pink Salmon Kelp By (District112)",
"http://data.d4science.org/ctlg/GRSF_Admin/1a03d6d8-002d-39b9-9255-a954c8ee2bf0"));
similarGrsfRecords.add(new SimilarGRSFRecord("same species overlapping water areas 2",
Utils.getDatasetKnowledgeBaseIdFromUrl("http://data.d4science.org/ctlg/GRSF_Admin/1a03d6d8-002d-39b9-9255-a954c8ee2bf0"),
"unknown:ONCORHYNCHUS GORBUSCHA+unknown:USA-AKSTATE-KELPB",
"Pink Salmon Kelp By (District112) 2",
"http://data.d4science.org/ctlg/GRSF_Admin/1a03d6d8-002d-39b9-9255-a954c8ee2bf0"));
toReturn.setSimilarGrsfRecords(similarGrsfRecords);
}else{
String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest());
DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl);
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();
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 the record....");
// check it is a grsf record (Source records have a different System Type)
Map<String, String> extrasAsMap = record.getExtrasAsHashMap();
// get extras as hashmap and pairs
List<CkanPair> 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<String, String> fieldsNamespacesMap =
Utils.getFieldToFieldNameSpaceMapping(httpSession, isStock ?
Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_STOCK : Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_FISHERY);
Map<String, List<String>> 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();
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);
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 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<String> similarGrsfRecordsAsStrings = extrasWithoutNamespaces.containsKey(Constants.SIMILAR_GRSF_RECORDS_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.SIMILAR_GRSF_RECORDS_CUSTOM_KEY): null;
List<SimilarGRSFRecord> similarRecords = new ArrayList<SimilarGRSFRecord>(0);
if(similarGrsfRecordsAsStrings != null){
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 (and the proposed ones)
List<String> connectedBeanUrls =
extrasWithoutNamespaces.containsKey(Constants.CONNECTED_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.CONNECTED_CUSTOM_KEY): null;
List<ConnectedBean> connectedBeans = new ArrayList<ConnectedBean>(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("Already connected records are " + connectedBeans);
// get the connections the knowledge base suggests
List<ConnectedBean> suggestedConnectionsByKnowledgeBase = new ArrayList<ConnectedBean>(0);
List<String> 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);
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<CkanResource> resources = record.getResources();
List<SourceRecord> sources = new ArrayList<SourceRecord>(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{
boolean toSetInSession = false;
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<GCubeTeam> teamRoles = roleManager.listTeamsByUserAndGroup(userId, groupId);
toSetInSession = isEditor(username, teamRoles) | isReviewer(username, teamRoles);
}
getThreadLocalRequest().getSession().setAttribute(Constants.GRSF_ADMIN_SESSION_KEY, toSetInSession);
return toSetInSession;
}
}catch(Exception e){
logger.error("Failed to check if the user belongs to team " + Constants.GRSF_CATALOGUE_EDITOR_ROLE + " or " + Constants.GRSF_CATALOGUE_REVIEWER_ROLE +"!", e);
}
return false;
}
@Override
public String notifyProductUpdate(ManageProductBean bean) throws Exception{
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 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<GCubeTeam> teamRoles = roleManager.listTeamsByUserAndGroup(userId, groupId);
boolean isEditor = isEditor(username, teamRoles);
boolean isReviewer = isReviewer(username, teamRoles);
if(!(isEditor | isReviewer))
throw new Exception("You are not allowed to perform this operation!");
// 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<GCubeTeam> 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;
}
/**
* 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<GCubeTeam> 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 {
String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest());
DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl);
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();
CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username));
if(dataset == null)
throw new Exception("A record with id " + id + " doesn't exist");
return dataset.getExtrasAsHashMap().get(Constants.ITEM_URL_FIELD);
}
@Override
public String checkIdentifierExistsInDomain(String id,
String domain) throws Exception {
String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest());
DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl);
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();
CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username));
if(dataset == null)
throw new Exception("A record with id " + id + " doesn't exist");
List<CkanPair> extrasAsPairs = dataset.getExtras();
for (CkanPair ckanPair : extrasAsPairs) {
if(ckanPair.getKey().contains(Constants.DOMAIN_CUSTOM_KEY)){
if(ckanPair.getValue().equalsIgnoreCase(domain))
return dataset.getExtrasAsHashMap().get(Constants.ITEM_URL_FIELD);
}
}
throw new Exception("A record with id " + id + " doesn't exist in domain " + domain);
}
// @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<CkanPair> extrasAsPairs = dataset.getExtras();
//
// for (CkanPair ckanPair : extrasAsPairs) {
// if(ckanPair.getKey().contains(Constants.DOMAIN_CUSTOM_KEY)){
// return ckanPair.getValue().equalsIgnoreCase(domain);
// }
// }
//
// 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;
// }
}