server side revised

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/widgets/grsf-manage-widget@162863 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Costantino Perciante 2018-02-02 14:28:35 +00:00
parent b13f1eafc9
commit aee48d3ecd
11 changed files with 1075 additions and 495 deletions

View File

@ -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.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 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.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7 org.eclipse.jdt.core.compiler.source=1.8

View File

@ -4,7 +4,7 @@
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/> <wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<dependent-module archiveName="grsf-common-library-1.0.1-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/grsf-common-library/grsf-common-library"> <dependent-module archiveName="ckan-util-library-2.4.1-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/ckan-util-library/ckan-util-library">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>
<property name="context-root" value="grsf-manage-widget"/> <property name="context-root" value="grsf-manage-widget"/>

View File

@ -27,22 +27,21 @@ public interface GRSFManageWidgetService extends RemoteService {
*/ */
String notifyProductUpdate(ManageProductBean bean) throws Exception; String notifyProductUpdate(ManageProductBean bean) throws Exception;
/** // /**
* Check that a record with such semantic identifier exists // * Check that a record with such semantic identifier exists
* @param semanticIdentifier // * @param semanticIdentifier
* @return true or false // * @return true or false
*/ // */
boolean checkSemanticIdentifierExists(String semanticIdentifier) throws Exception; // boolean checkSemanticIdentifierExists(String semanticIdentifier) throws Exception;
//
/** // /**
* Check that a record with such semantic identifier exists in a given domain // * Check that a record with such semantic identifier exists in a given domain
* @param semanticIdentifier // * @param semanticIdentifier
* @param domain // * @param domain
* @return // * @return
* @throws Exception // * @throws Exception
*/ // */
boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) throws Exception; // boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) throws Exception;
/** /**
* Identifier of the record (UUID) * Identifier of the record (UUID)
@ -61,4 +60,11 @@ public interface GRSFManageWidgetService extends RemoteService {
*/ */
String checkIdentifierExistsInDomain(String id, String domain) throws Exception; 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;
} }

View File

@ -21,11 +21,11 @@ public interface GRSFManageWidgetServiceAsync {
void isAdminUser(AsyncCallback<Boolean> callback); void isAdminUser(AsyncCallback<Boolean> callback);
void checkSemanticIdentifierExists(String semanticIdentifier, // void checkSemanticIdentifierExists(String semanticIdentifier,
AsyncCallback<Boolean> callback); // AsyncCallback<Boolean> callback);
//
void checkSemanticIdentifierExistsInDomain(String semanticIdentifier, // void checkSemanticIdentifierExistsInDomain(String semanticIdentifier,
String domain, AsyncCallback<Boolean> callback); // String domain, AsyncCallback<Boolean> callback);
void checkIdentifierExists(String id, void checkIdentifierExists(String id,
AsyncCallback<String> callback); AsyncCallback<String> callback);
@ -33,4 +33,6 @@ public interface GRSFManageWidgetServiceAsync {
void checkIdentifierExistsInDomain(String id, void checkIdentifierExistsInDomain(String id,
String domain, AsyncCallback<String> callback); String domain, AsyncCallback<String> callback);
void validateRevertOperation(String url, AsyncCallback<Void> callback);
} }

View File

@ -5,7 +5,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; 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.portal.PortalContext;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue;
import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueFactory; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueFactory;
import org.gcube.datacatalogue.ckanutillibrary.server.utils.UtilMethods; 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.RoleManager;
import org.gcube.vomanagement.usermanagement.impl.LiferayRoleManager; import org.gcube.vomanagement.usermanagement.impl.LiferayRoleManager;
import org.gcube.vomanagement.usermanagement.model.GCubeTeam; import org.gcube.vomanagement.usermanagement.model.GCubeTeam;
import org.gcube.vomanagement.usermanagement.model.GCubeUser;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet; 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.CkanDataset;
import eu.trentorise.opendata.jackan.model.CkanPair; import eu.trentorise.opendata.jackan.model.CkanPair;
import eu.trentorise.opendata.jackan.model.CkanResource; import eu.trentorise.opendata.jackan.model.CkanResource;
@ -52,7 +60,7 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
String currentScope = Utils.getCurrentContext(getThreadLocalRequest(), true); String currentScope = Utils.getCurrentContext(getThreadLocalRequest(), true);
DataCatalogue instance = null; DataCatalogue instance = null;
try{ 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); logger.debug("Discovering ckan utils library into scope " + scopeInWhichDiscover);
instance = DataCatalogueFactory.getFactory().getUtilsPerScope(scopeInWhichDiscover); instance = DataCatalogueFactory.getFactory().getUtilsPerScope(scopeInWhichDiscover);
}catch(Exception e){ }catch(Exception e){
@ -65,17 +73,29 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
@Override @Override
public ManageProductBean getProductBeanById(String productIdentifier) throws Exception { public ManageProductBean getProductBeanById(String productIdentifier) throws Exception {
ManageProductBean toReturn = null; 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()){ if(!Utils.isIntoPortal()){
toReturn = new ManageProductBean(); toReturn = new ManageProductBean();
toReturn.setCatalogueIdentifier(UUID.randomUUID().toString()); toReturn.setCatalogueIdentifier(UUID.randomUUID().toString());
List<ConnectedBean> connectTo = new ArrayList<>(); List<ConnectedBean> connectTo = new ArrayList<>();
connectTo.add(new ConnectedBean("91f1e413-dc9f-3b4e-b1c5-0e8560177253","Stock", connectTo.add(new ConnectedBean(
"aksldsam asd", "asdasjnk:fas", UUID.randomUUID().toString(), "http://data.d4science.org/ctlg/GRSF_Admin/91f1e413-dc9f-3b4e-b1c5-0e8560177253")); "91f1e413-dc9f-3b4e-b1c5-0e8560177253",
toReturn.setConnectTo(connectTo); "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.setGrsfDomain("Stock");
toReturn.setGrsfType("Assessment Unit"); toReturn.setGrsfType("Assessment Unit");
toReturn.setKnowledgeBaseIdentifier("91f1e413-dc9f-3b4e-b1c5-0e8560177253"); toReturn.setKnowledgeBaseIdentifier("91f1e413-dc9f-3b4e-b1c5-0e8560177253");
@ -105,52 +125,63 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
}else{ }else{
// retrieve scope per current portlet url
String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest()); String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest());
DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl); DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl);
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); 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 ... // it cannot be enabled in this case ...
if(record == null) if(record == null)
throw new Exception("Unable to retrieve information for the selected record, sorry"); throw new Exception("Unable to retrieve information for the selected record, sorry");
else{ 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) // check it is a grsf record (Source records have a different System Type)
String systemType = record.getExtrasAsHashMap().get(Constants.SYSTEM_TYPE_CUSTOM_KEY); Map<String, String> extrasAsMap = record.getExtrasAsHashMap();
if(systemType == null || systemType.isEmpty() || systemType.equals(Constants.SYSTEM_TYPE_FOR_SOURCES_VALUE))
throw new NoGRSFRecordException("This is not a GRSF Record");
// get extras as hashmap and pairs // get extras as hashmap and pairs
List<CkanPair> extrasAsPairs = record.getExtras(); 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 // fetch map for namespaces
Map<String, String> fieldsNamespacesMap = Utils.getFieldToFieldNameSpaceMapping(getThreadLocalRequest().getSession(), Map<String, String> fieldsNamespacesMap =
record.getExtrasAsHashMap().get(Constants.DOMAIN_CUSTOM_KEY).contains(Product_Type.STOCK.getOrigName()) ? Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_STOCK Utils.getFieldToFieldNameSpaceMapping(httpSession, isStock ?
: Constants.GENERIC_RESOURCE_NAME_MAP_KEY_NAMESPACES_FISHERY); 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); Map<String, List<String>> extrasWithoutNamespaces = Utils.replaceFieldsKey(extrasAsPairs, fieldsNamespacesMap);
// get extras fields (wrt the mandatory ones) to show in the management panel TODO // get extras fields (wrt the mandatory ones) to show in the management panel TODO
// Utils.getExtrasToShow(); // Utils.getExtrasToShow();
String catalogueIdentifier = record.getId(); 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 uuidKB = extrasWithoutNamespaces.get(Constants.UUID_KB_CUSTOM_KEY).get(0);
String grsfDomain = extrasWithoutNamespaces.get(Constants.DOMAIN_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) if(status == null || uuidKB == null)
throw new Exception("Some information is missing in this record: Status = " + status + ", knowledge base uuid = " + uuidKB + throw new Exception("Some information is missing in this record: Status = " + status + ", knowledge base uuid = " + uuidKB +
", and grsf domain is = " + grsfDomain); ", 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 semanticId = extrasWithoutNamespaces.get(Constants.GRSF_SEMANTIC_IDENTIFIER_CUSTOM_KEY).get(0);
String shortName = extrasWithoutNamespaces.get(Constants.SHORT_NAME_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 grsfType = extrasWithoutNamespaces.get(Constants.GRSF_TYPE_CUSTOM_KEY).get(0);
String grsfName = extrasWithoutNamespaces.get(grsfDomain.contains(Product_Type.STOCK.getOrigName()) ? String recordUrl = extrasWithoutNamespaces.get(Constants.ITEM_URL_FIELD).get(0);
Constants.STOCK_NAME_CUSTOM_KEY : Constants.FISHERY_NAME_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"); 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)) // 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<String> 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) { for (String similarGRSFRecord : similarGrsfRecordsAsStrings) {
if(similarGRSFRecord.equals(Constants.NO_SIMILAR_GRSF_RECORDS)) // stop here if there is a single element with this information if(similarGRSFRecord.equals(Constants.NO_SIMILAR_GRSF_RECORDS)) // stop here if there is a single element with this information
break; break;
similarRecords.add(Utils.similarGRSFRecordFromJson(similarGRSFRecord)); similarRecords.add(Utils.similarGRSFRecordFromJson(similarGRSFRecord));
} }
} }
logger.debug("SimilarGRSFRecords are " + similarRecords); logger.debug("SimilarGRSFRecords are " + similarRecords);
// get connected records // get connected records (and the proposed ones)
List<String> connectedBeansAsStrings = extrasWithoutNamespaces.containsKey(Constants.CONNECTED_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.CONNECTED_CUSTOM_KEY): null; List<String> connectedBeanUrls =
extrasWithoutNamespaces.containsKey(Constants.CONNECTED_CUSTOM_KEY) ? extrasWithoutNamespaces.get(Constants.CONNECTED_CUSTOM_KEY): null;
List<ConnectedBean> connectedBeans = new ArrayList<ConnectedBean>(0); List<ConnectedBean> connectedBeans = new ArrayList<ConnectedBean>(0);
if(connectedBeansAsStrings != null){ if(connectedBeanUrls != null){
for (String connectedBean : connectedBeansAsStrings) { for (String connectedBean : connectedBeanUrls) {
if(connectedBean.equals(Constants.NO_CONNECTED_RECORDS)) // stop here if there is a single element with this information if(connectedBean.equals(Constants.NO_CONNECTED_RECORDS)) // stop here if there is a single element with this information
break; break;
connectedBeans.add(Utils.connectedBeanRecordFromJson(connectedBean, uuidKB, grsfDomain, catalogue)); 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 // get the connections the knowledge base suggests
List<CkanResource> resources = record.getResources(); List<ConnectedBean> suggestedConnectionsByKnowledgeBase = new ArrayList<ConnectedBean>(0);
List<SourceRecord> sources = new ArrayList<SourceRecord>(3); List<String> exploitedResourcesUrls = isStock ?
for (CkanResource ckanResource : resources) { (extrasWithoutNamespaces.containsKey(Constants.EXPLOITING_FISHERY_JSON_KEY) ?
if(Sources.getListNames().contains(ckanResource.getName())) extrasWithoutNamespaces.get(Constants.EXPLOITING_FISHERY_JSON_KEY) : null):
sources.add(new SourceRecord(ckanResource.getName(), ckanResource.getUrl())); (extrasWithoutNamespaces.containsKey(Constants.RESOURCES_EXPLOITED_JSON_KEY) ? extrasWithoutNamespaces.get(Constants.RESOURCES_EXPLOITED_JSON_KEY) : null);
}
// set the values if(exploitedResourcesUrls != null && !exploitedResourcesUrls.isEmpty()){
toReturn = new ManageProductBean(semanticId, catalogueIdentifier, uuidKB, grsfType, for (String exploited : exploitedResourcesUrls) {
grsfDomain, grsfName, shortName, traceabilityFlag, Status.fromString(status), null, ConnectedBean builtBean = Utils.connectedBeanRecordFromUrl(recordUrl, exploited, uuidKB, grsfDomain, catalogue, apiKey);
null, null, sources, similarRecords, connectedBeans, false); 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); logger.info("Returning item bean " + toReturn);
httpSession.setAttribute(sessionProductKey, toReturn);
return toReturn; return toReturn;
} }
@Override @Override
public boolean isAdminUser() { public boolean isAdminUser() {
try{ try{
Boolean inSession = (Boolean)getThreadLocalRequest().getSession().getAttribute(Constants.GRSF_ADMIN_SESSION_KEY); Boolean inSession = (Boolean)getThreadLocalRequest().getSession().getAttribute(Constants.GRSF_ADMIN_SESSION_KEY);
if(inSession != null) if(inSession != null)
return inSession; return inSession;
else{ 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<GCubeRole> vreRoles = roleManager.listRolesByUserAndGroup(userId, groupId);
List<GCubeTeam> teamRoles = roleManager.listTeamsByUserAndGroup(userId, groupId);
boolean toSetInSession = false; boolean toSetInSession = false;
for (GCubeTeam team : teamRoles) { if(!Utils.isIntoPortal()){
if(team.getTeamName().equals(Constants.GRSF_CATALOGUE_EDITOR_ROLE) || team.getTeamName().equals(Constants.GRSF_CATALOGUE_REVIEWER_ROLE)){ toSetInSession = true;
logger.info("User " + username + " is allowed to modify GRSF records"); }else{
toSetInSession = true; PortalContext pContext = PortalContext.getConfiguration();
break; 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);
} }
// 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); getThreadLocalRequest().getSession().setAttribute(Constants.GRSF_ADMIN_SESSION_KEY, toSetInSession);
return toSetInSession; return toSetInSession;
} }
@ -251,42 +282,132 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
} }
@Override @Override
public boolean checkSemanticIdentifierExists(String semanticIdentifier) public String notifyProductUpdate(ManageProductBean bean) throws Exception{
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 @Override
public boolean checkSemanticIdentifierExistsInDomain(String semanticIdentifier, String domain) public void validateRevertOperation(String encryptedUrl) throws Exception {
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);
CkanDataset dataset = getDataset(semanticIdentifier); boolean isEditor = isEditor(username, teamRoles);
boolean isReviewer = isReviewer(username, teamRoles);
// look for the right domain this time if(!(isEditor | isReviewer))
List<CkanPair> extrasAsPairs = dataset.getExtras(); throw new Exception("You are not allowed to perform this operation!");
for (CkanPair ckanPair : extrasAsPairs) { // decrypt the url
if(ckanPair.getKey().contains(Constants.DOMAIN_CUSTOM_KEY)){ RevertOperationUrl decryptedUrl = new RevertOperationUrl(encryptedUrl);
return ckanPair.getValue().equalsIgnoreCase(domain); 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; return false;
} }
private CkanDataset getDataset(String semanticIdentifier) throws Exception{ /**
* Check if the current user is a reviewer
String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest()); * @param username
DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl); * @param teamRoles
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); * @return true if he/she is an reviewer, false otherwise
CkanDataset dataset = Utils.getRecordBySemanticIdentifier(semanticIdentifier, catalogue, catalogue.getApiKeyFromUsername(username)); */
return dataset; 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 @Override
public String checkIdentifierExists(String id) public String checkIdentifierExists(String id)
throws Exception { throws Exception {
@ -295,8 +416,7 @@ public class GRSFNotificationService extends RemoteServiceServlet implements GRS
String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername(); String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();
CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username)); CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username));
if(dataset == null) 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); 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(); String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();
CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username)); CkanDataset dataset = catalogue.getDataset(id, catalogue.getApiKeyFromUsername(username));
if(dataset == null) if(dataset == null)
throw new Exception("This record doesn't exist"); throw new Exception("A record with id " + id + " doesn't exist");
List<CkanPair> extrasAsPairs = dataset.getExtras(); List<CkanPair> 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 // @Override
public String notifyProductUpdate(ManageProductBean bean) throws Exception{ // 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;
// }
logger.info("Creating notification for the bean " + bean + " to send to the knowledge base"); // private CkanDataset getDataset(String semanticIdentifier) throws Exception{
try{ // String scopePerCurrentUrl = Utils.getScopeFromClientUrl(getThreadLocalRequest());
// DataCatalogue catalogue = getCatalogue(scopePerCurrentUrl);
String context = Utils.getScopeFromClientUrl(getThreadLocalRequest()); // String username = Utils.getCurrentUser(getThreadLocalRequest()).getUsername();
DataCatalogue catalogue = getCatalogue(context); // CkanDataset dataset = Utils.getRecordBySemanticIdentifier(semanticIdentifier, catalogue, catalogue.getApiKeyFromUsername(username));
String administratorFullName = Utils.getCurrentUser(getThreadLocalRequest()).getFullname(); // return dataset;
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;
}
}
} }

View File

@ -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<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
List<ServiceEndpoint> 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<AccessPoint> 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<ConnectedBean> 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<SimilarGRSFRecord> 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));
}
}

View File

@ -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.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
@ -22,11 +22,16 @@ public class RevertOperationUrl {
public static final String TIMESTAMP_QUERY_PARAM = "t"; public static final String TIMESTAMP_QUERY_PARAM = "t";
public static final String UUID_QUERY_PARAM = "uuid"; public static final String UUID_QUERY_PARAM = "uuid";
public static final String OPERATION_REVERT_QUERY_PARAM = "operation_revert"; 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 { public enum Operation {
MERGE("merge"), MERGE("merge");
DISSECT("dissect"); // DISSECT("dissect");
private String name; private String name;
private Operation(String name) { private Operation(String name) {
@ -45,7 +50,6 @@ public class RevertOperationUrl {
private long timestamp; private long timestamp;
private String uuid; private String uuid;
private Operation operation; private Operation operation;
private Operation op;
/** /**
* @param admin * @param admin
@ -55,17 +59,20 @@ public class RevertOperationUrl {
* @param op * @param op
*/ */
public RevertOperationUrl(String baseUrl, String admin, long timestamp, String uuid, public RevertOperationUrl(String baseUrl, String admin, long timestamp, String uuid,
Operation operation, Operation op) { Operation operation) {
super(); super();
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.admin = admin; this.admin = admin;
this.timestamp = timestamp; this.timestamp = timestamp;
this.uuid = uuid; this.uuid = uuid;
this.operation = operation; this.operation = operation;
this.op = op;
} }
/**
* Build a crypted, encoded and shortened url
* @return
* @throws Exception
*/
public String getShortUrl() 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; 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: default:
break; break;
} }
} }
}catch(Exception e){ }catch(Exception e){
logger.error("Failed to parse url", e); logger.error("Failed to parse url", e);
} }
} }
public boolean isTimestampValid() {
return TTL + this.timestamp < System.currentTimeMillis();
}
public String getBaseUrl() { public String getBaseUrl() {
return baseUrl; return baseUrl;
@ -188,58 +198,11 @@ public class RevertOperationUrl {
this.operation = operation; this.operation = operation;
} }
public Operation getOp() {
return op;
}
public void setOp(Operation op) {
this.op = op;
}
@Override @Override
public String toString() { public String toString() {
return "RevertOperationUrl [baseUrl=" + baseUrl + ", admin=" + admin return "RevertOperationUrl [baseUrl=" + baseUrl + ", admin=" + admin
+ ", timestamp=" + timestamp + ", uuid=" + uuid + ", 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);
//}
} }

View File

@ -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,"
+ "<br>The record 'PRODUCT_TITLE' has been just updated by USER_FULLNAME."
+ "<br>You can inspect it here: PRODUCT_URL<br>";
private static final String EMAIL_MESSAGE_REVIEWER = "Dear GRSF Reviewer,"
+ "<br>an update on the record named 'PRODUCT_TITLE' has been requested by USER_FULLNAME."
+ "<br>It is available here LINK_RECORD.";
private static final String EMAIL_MESSAGE_EDITOR = "Dear USER_FULLNAME,"
+ "<br>your request for the record 'PRODUCT_TITLE' has been accepted."
+ "<br>It is available here LINK_RECORD.";
private static final String REVERT_LINK_PIECE = "<br>The request involves a merge operation. You can reject the merge by exploiting this link LINK in the following 24 hours.";
/**
*
* @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<String> client = client();
List<String> 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<String> 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<String, Object> mapResponseGeneratedToken = getResponseEntityAsJSON(response);
boolean successGeneratedToken = (boolean)mapResponseGeneratedToken.get("success");
if(!successGeneratedToken){
throw new RuntimeException("Failed to generate the token for the application!"
+ " Error message is " + mapResponseGeneratedToken.get("message"));
}else{
String applicationToken = (String)mapResponseGeneratedToken.get("result");
// 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<String, Object> 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<GCubeTeam> teamRoles = roleManager.listTeamsByGroup(groupId);
List<String> reviewers = new ArrayList<>();
UserManager um = new LiferayUserManager();
for(GCubeTeam tr: teamRoles){
if(tr.getTeamName().equals(Constants.GRSF_CATALOGUE_REVIEWER_ROLE))
reviewers.add(um.getUserById(tr.getUserId()).getUsername());
}
// if the user is a reviewer, then send the email just once
reviewers.remove(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<String, Object> mapResponseGeneratedToken = getResponseEntityAsJSON(response);
boolean successGeneratedToken = (boolean)mapResponseGeneratedToken.get("success");
if(!successGeneratedToken){
throw new RuntimeException("Failed to generate the token for the application!"
+ " Error message is " + mapResponseGeneratedToken.get("message"));
}else{
String applicationToken = (String)mapResponseGeneratedToken.get("result");
String 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<String, Object> mapResponseWritePost = getResponseEntityAsJSON(response);
if (response.getStatusLine().getStatusCode() != 201){
logger.error("Failed to send message to editor : HTTP error code : "
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
}
// send email to the reviewers
logger.info("The message that is going to be send to the reviewers is\n" + messageToReviewer);
postRequest = new HttpPost(basePath + SOCIAL_SEND_EMAIL + "?gcube-token=" + applicationToken);
reqMessage = new JSONObject();
reqMessage.put("subject", subject);
reqMessage.put("body", messageToReviewer);
recipients = new JSONArray();
for(String reviewer: reviewers){
JSONObject recip = new JSONObject();
recip.put("id", reviewer);
recipients.add(recip);
}
reqMessage.put("recipients", recipients);
input = new StringEntity(reqMessage.toJSONString());
input.setContentType(MEDIATYPE_JSON);
postRequest.setEntity(input);
response = client.execute(postRequest);
mapResponseWritePost = getResponseEntityAsJSON(response);
if (response.getStatusLine().getStatusCode() != 201){
logger.error("Failed to send message to editor : HTTP error code : "
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
}
}
}
}catch(Exception e){
logger.error("Failed to 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<String, Object> getResponseEntityAsJSON(HttpResponse response){
Map<String, Object> toReturn = null;
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
toReturn = new HashMap<String, Object>();
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;
}
}

View File

@ -22,6 +22,7 @@ import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
import org.gcube.common.encryption.StringEncrypter; import org.gcube.common.encryption.StringEncrypter;
import org.gcube.common.portal.PortalContext; import org.gcube.common.portal.PortalContext;
import org.gcube.common.resources.gcore.ServiceEndpoint; 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.ckanutillibrary.shared.ex.ApplicationProfileNotFoundException;
import org.gcube.datacatalogue.common.Constants; import org.gcube.datacatalogue.common.Constants;
import org.gcube.datacatalogue.common.enums.Status; 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.ConnectedBean;
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean; 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.datacatalogue.grsf_manage_widget.shared.SimilarGRSFRecord;
import org.gcube.portlets.user.urlshortener.UrlShortener; import org.gcube.portlets.user.urlshortener.UrlShortener;
import org.gcube.resources.discovery.client.api.DiscoveryClient; import org.gcube.resources.discovery.client.api.DiscoveryClient;
@ -177,7 +177,9 @@ public class Utils {
} }
} }
logger.debug("Map is " + namespacesMap); logger.debug("Map is " + namespacesMap);
httpSession.setAttribute(sessionKey, namespacesMap);
// put them into session for speeding up the operations
httpSession.setAttribute(sessionKey, namespacesMap);
return namespacesMap; return namespacesMap;
} catch (Exception e) { } catch (Exception e) {
logger.error("Error while trying to fetch applicationProfile profile from the infrastructure", e); logger.error("Error while trying to fetch applicationProfile profile from the infrastructure", e);
@ -221,7 +223,7 @@ public class Utils {
* @param extrasAsPairs * @param extrasAsPairs
* @return * @return
*/ */
public static Map<String, List<String>> getExtras(List<CkanPair> extrasAsPairs){ public static Map<String, List<String>> getExtrasAsHashMap(List<CkanPair> extrasAsPairs){
Map<String, List<String>> toReturn = new HashMap<String, List<String>>(); Map<String, List<String>> toReturn = new HashMap<String, List<String>>();
@ -242,52 +244,6 @@ public class Utils {
return toReturn; 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<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
List<ServiceEndpoint> 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<AccessPoint> 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 * Send an update for this bean
* @param baseUrl * @param baseUrl
@ -297,7 +253,7 @@ public class Utils {
* @return true on success, false otherwise * @return true on success, false otherwise
*/ */
public static String updateRecord(String serviceUrl, ManageProductBean bean, DataCatalogue catalogue, String username, 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) if(serviceUrl == null)
throw new IllegalArgumentException("GRSF Updater service url cannot be null"); throw new IllegalArgumentException("GRSF Updater service url cannot be null");
@ -312,14 +268,21 @@ public class Utils {
updateStatusInvolvedRecords(bean, catalogue); updateStatusInvolvedRecords(bean, catalogue);
// send update to the knowledge base // 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 // manage interactions through a separated thread but set there security token and context
if(bean.isMergesInvolved() || bean.getNewStatus().equals(Status.Rejected)) new Thread(()->{
sendEmailAdministrators(bean, catalogue, username, fullName, groupId, httpServletRequest);
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 // create a post about the operation
createSocialPost(bean, catalogue, username, fullName); SocialCommunications.createSocialPost(bean, catalogue, username, fullName, hashtags);
}).start();
}catch(Exception e){ }catch(Exception e){
logger.error("Unable to update this Item ", e); logger.error("Unable to update this Item ", e);
@ -343,7 +306,7 @@ public class Utils {
for(SimilarGRSFRecord s: bean.getSimilarGrsfRecords()){ for(SimilarGRSFRecord s: bean.getSimilarGrsfRecords()){
if(s.isSuggestedMerge()){ if(s.isSuggestedMerge()){
String productId = s.getKnowledgeBaseId(); String productId = s.getKnowledgeBaseId();
Map<String, List<String>> extrasMap = getExtras(catalogue.getDataset(productId, sysApi).getExtras()); Map<String, List<String>> extrasMap = getExtrasAsHashMap(catalogue.getDataset(productId, sysApi).getExtras());
extrasMap.put(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY, Arrays.asList(Status.To_be_Merged.getOrigName())); extrasMap.put(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY, Arrays.asList(Status.To_be_Merged.getOrigName()));
catalogue.patchProductCustomFields(productId, sysApi, extrasMap); catalogue.patchProductCustomFields(productId, sysApi, extrasMap);
} }
@ -351,174 +314,12 @@ public class Utils {
// update the current status record // update the current status record
String productId = bean.getKnowledgeBaseIdentifier(); String productId = bean.getKnowledgeBaseIdentifier();
Map<String, List<String>> extrasMap = getExtras(catalogue.getDataset(productId, sysApi).getExtras()); Map<String, List<String>> extrasMap = getExtrasAsHashMap(catalogue.getDataset(productId, sysApi).getExtras());
extrasMap.put(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY, Arrays.asList(Status.To_be_Merged.getOrigName())); extrasMap.put(Constants.STATUS_OF_THE_GRSF_RECORD_CUSTOM_KEY, Arrays.asList(Status.To_be_Merged.getOrigName()));
catalogue.patchProductCustomFields(productId, sysApi, extrasMap); 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<String> 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<GCubeTeam> teamRoles = roleManager.listTeamsByGroup(groupId);
List<GCubeUser> 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<String> 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<ConnectedBean> 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<SimilarGRSFRecord> 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 * Get the scope in which ckan information needs to be discovered from the url
* @param httpServletRequest * @param httpServletRequest
@ -649,8 +450,9 @@ public class Utils {
* @param url * @param url
* @param clg * @param clg
* @return * @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()) if(url == null || url.isEmpty())
return null; return null;
@ -667,7 +469,7 @@ public class Utils {
return clg.getDataset(uuidFound, apiKey); 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 json
* @param sourceIdentifier * @param sourceIdentifier
* @param sourceDomain * @param sourceDomain
* @param grsfDomain
* @return * @return
* @throws ParseException * @throws ParseException
*/ */
public static ConnectedBean connectedBeanRecordFromJson(String json, String sourceIdentifier, String sourceDomain, public static ConnectedBean connectedBeanRecordFromUrl(
DataCatalogue clg) throws ParseException { String sourceIdentifier,
String sourceDomain,
String sourceUrl,
String destUrl,
DataCatalogue clg,
String apiKey) throws ParseException {
if(json == null) if(destUrl == null)
return null; return null;
JSONParser parser = new JSONParser(); String connectedBeanUuid = Utils.getDatasetKnowledgeBaseIdFromUrl(destUrl);
JSONObject object = (JSONObject)parser.parse(json); CkanDataset destDataset = clg.getDataset(connectedBeanUuid, apiKey);
String uuidDest = (String)object.get(Constants.CONNECTED_RECORD_KNOWLEDGE_BASE_ID_JSON_KEY);
String url = clg.getUrlFromDatasetIdOrName(uuidDest);
return new ConnectedBean( return new ConnectedBean(
sourceIdentifier, sourceIdentifier,
sourceDomain, sourceDomain,
(String)object.get(Constants.CONNECTED_RECORD_SHORT_NAME_JSON_KEY), sourceUrl,
(String)object.get(Constants.CONNECTED_RECORD_SEMANTIC_IDENTIFIER_JSON_KEY), connectedBeanUuid,
uuidDest, destDataset.getTitle(),
url destUrl,
destDataset.getExtrasAsHashMap().get(Constants.DOMAIN_CUSTOM_KEY)
); );
} }
@ -799,10 +605,8 @@ public class Utils {
* @throws Exception * @throws Exception
*/ */
public static String fetchSysAPI(String context) throws Exception{ public static String fetchSysAPI(String context) throws Exception{
DataCatalogueRunningCluster catalogueRunningInstance = new DataCatalogueRunningCluster(context); DataCatalogueRunningCluster catalogueRunningInstance = new DataCatalogueRunningCluster(context);
return catalogueRunningInstance.getSysAdminToken(); return catalogueRunningInstance.getSysAdminToken();
} }
} }

View File

@ -12,10 +12,11 @@ public class ConnectedBean implements Serializable{
private static final long serialVersionUID = -4863776727351488790L; private static final long serialVersionUID = -4863776727351488790L;
private String sourceKnowledgeBaseId; private String sourceKnowledgeBaseId;
private String sourceDomain; // i.e. Stock or Fishery private String sourceDomain; // i.e. Stock or Fishery
private String destShortName; private String sourceUrl;
private String destSemanticIdentifier; 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 destKnowledgeBaseId; // the dest indentifier of a Fishery or Stock (the link is from a Stock to a Fishery and viceversa) private String destName;
private String url; private String destUrl;
private String destDomain; // please note that this MUST be different from sourceDomain
private boolean remove; private boolean remove;
private boolean connect; private boolean connect;
@ -24,25 +25,26 @@ public class ConnectedBean implements Serializable{
} }
/** /**
*
* @param sourceKnowledgeBaseId * @param sourceKnowledgeBaseId
* @param sourceDomain * @param sourceDomain
* @param destShortName * @param sourceUrl
* @param destSemanticIdentifier
* @param destKnowledgeBaseId * @param destKnowledgeBaseId
* @param url * @param destTitle
* @param destUrl
* @param remove
* @param connect
*/ */
public ConnectedBean(String sourceKnowledgeBaseId, String sourceDomain, public ConnectedBean(String sourceKnowledgeBaseId, String sourceDomain,
String destShortName, String destSemanticIdentifier, String sourceUrl, String destKnowledgeBaseId, String destName,
String destKnowledgeBaseId, String url) { String destUrl, String destDomain) {
super(); super();
this.sourceKnowledgeBaseId = sourceKnowledgeBaseId; this.sourceKnowledgeBaseId = sourceKnowledgeBaseId;
this.sourceDomain = sourceDomain; this.sourceDomain = sourceDomain;
this.destShortName = destShortName; this.sourceUrl = sourceUrl;
this.destSemanticIdentifier = destSemanticIdentifier;
this.destKnowledgeBaseId = destKnowledgeBaseId; this.destKnowledgeBaseId = destKnowledgeBaseId;
this.url = url; this.destName = destName;
this.destUrl = destUrl;
this.destDomain = destDomain;
} }
public boolean isConnect() { public boolean isConnect() {
@ -53,21 +55,6 @@ public class ConnectedBean implements Serializable{
this.connect = connect; 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() { public String getSourceKnowledgeBaseId() {
return sourceKnowledgeBaseId; return sourceKnowledgeBaseId;
} }
@ -99,20 +86,45 @@ public class ConnectedBean implements Serializable{
this.remove = remove; this.remove = remove;
} }
public String getUrl() { public String getSourceUrl() {
return url; 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 @Override
public String toString() { public String toString() {
return "ConnectedBean [sourceKnowledgeBaseId=" + sourceKnowledgeBaseId return "ConnectedBean [sourceKnowledgeBaseId=" + sourceKnowledgeBaseId
+ ", sourceDomain=" + sourceDomain + ", destShortName=" + ", sourceDomain=" + sourceDomain + ", sourceUrl=" + sourceUrl
+ destShortName + ", destSemanticIdentifier=" + ", destKnowledgeBaseId=" + destKnowledgeBaseId
+ destSemanticIdentifier + ", destKnowledgeBaseId=" + ", destName=" + destName + ", destUrl=" + destUrl
+ destKnowledgeBaseId + ", url=" + url + ", remove=" + remove + ", destDomain=" + destDomain + ", remove=" + remove
+ ", connect=" + connect + "]"; + ", connect=" + connect + "]";
} }

View File

@ -1,6 +1,7 @@
package org.gcube.datacatalogue.grsf_manage_widget.shared; package org.gcube.datacatalogue.grsf_manage_widget.shared;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -14,13 +15,13 @@ public class ManageProductBean implements Serializable{
private static final long serialVersionUID = -4882608487467259326L; private static final long serialVersionUID = -4882608487467259326L;
private String semanticIdentifier; // Stock id or Fishery id 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 knowledgeBaseIdentifier; // GRSF UUID
private String grsfType; // Fishery or Stock type (e.g., Assessment_Unit, Marine Resource and so on) private String grsfType; // Fishery or Stock type (e.g., Assessment_Unit, Marine Resource and so on)
private String grsfDomain; // fishery/stock private String grsfDomain; // fishery/stock
private String grsfName; // Fishery name or Stock name private String grsfName; // Fishery name or Stock name
private String shortName; // it is editable ... 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 boolean traceabilityFlag; //from false to true etc
private Status currentStatus; private Status currentStatus;
private Status newStatus; private Status newStatus;
@ -28,20 +29,34 @@ public class ManageProductBean implements Serializable{
private Map<String, String> extrasIfAvailable; // read from GRSFManageEntries resource private Map<String, String> extrasIfAvailable; // read from GRSFManageEntries resource
private List<SourceRecord> sources; // sources for this record private List<SourceRecord> sources; // sources for this record
private List<SimilarGRSFRecord> similarGrsfRecords; private List<SimilarGRSFRecord> similarGrsfRecords;
private List<ConnectedBean> connectTo; private List<ConnectedBean> suggestedByKnowledgeBase;
private boolean mergesInvolved; private List<ConnectedBean> suggestdByAdministrator = new ArrayList<ConnectedBean>(0);
private List<ConnectedBean> currentConnections;
private List<ConnectedBean> 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() { public ManageProductBean() {
super(); super();
} }
public ManageProductBean(String semanticIdentifier, public ManageProductBean(
String catalogueIdentifier, String knowledgeBaseIdentifier, String semanticIdentifier,
String grsfType, String grsfDomain, String grsfName, String catalogueIdentifier,
String shortName, boolean traceabilityFlag, Status currentStatus, String knowledgeBaseIdentifier,
Status newStatus, String annotation, String grsfType,
Map<String, String> extrasIfAvailable, List<SourceRecord> sources, String grsfDomain,
List<SimilarGRSFRecord> similarGrsfRecords, List<ConnectedBean> connectedBeans, boolean mergesInvolved) { String grsfName,
String shortName,
boolean traceabilityFlag,
Status currentStatus,
String recordUrl,
Map<String, String> extrasIfAvailable,
List<SourceRecord> sources,
List<SimilarGRSFRecord> similarGrsfRecords,
List<ConnectedBean> currentConnections,
List<ConnectedBean> suggestedByKnowledgeBase
) {
super(); super();
this.semanticIdentifier = semanticIdentifier; this.semanticIdentifier = semanticIdentifier;
this.catalogueIdentifier = catalogueIdentifier; this.catalogueIdentifier = catalogueIdentifier;
@ -53,13 +68,12 @@ public class ManageProductBean implements Serializable{
this.shortNameUpdated = shortName; this.shortNameUpdated = shortName;
this.traceabilityFlag = traceabilityFlag; this.traceabilityFlag = traceabilityFlag;
this.currentStatus = currentStatus; this.currentStatus = currentStatus;
this.newStatus = newStatus;
this.annotation = annotation;
this.extrasIfAvailable = extrasIfAvailable; this.extrasIfAvailable = extrasIfAvailable;
this.sources = sources; this.sources = sources;
this.similarGrsfRecords = similarGrsfRecords; this.similarGrsfRecords = similarGrsfRecords;
this.connectTo = connectedBeans; this.currentConnections = currentConnections;
this.mergesInvolved = mergesInvolved; this.suggestedByKnowledgeBase = suggestedByKnowledgeBase;
this.recordUrl = recordUrl;
} }
public String getSemanticIdentifier() { public String getSemanticIdentifier() {
@ -184,12 +198,30 @@ public class ManageProductBean implements Serializable{
this.shortNameUpdated = shortNameUpdated; this.shortNameUpdated = shortNameUpdated;
} }
public List<ConnectedBean> getConnectTo() { public List<ConnectedBean> getSuggestedByKnowledgeBase() {
return connectTo; return suggestedByKnowledgeBase;
} }
public void setConnectTo(List<ConnectedBean> connectTo) { public void setSuggestedByKnowledgeBase(
this.connectTo = connectTo; List<ConnectedBean> suggestedByKnowledgeBase) {
this.suggestedByKnowledgeBase = suggestedByKnowledgeBase;
}
public List<ConnectedBean> getSuggestdByAdministrator() {
return suggestdByAdministrator;
}
public void setSuggestdByAdministrator(
List<ConnectedBean> suggestdByAdministrator) {
this.suggestdByAdministrator = suggestdByAdministrator;
}
public List<ConnectedBean> getCurrentConnections() {
return currentConnections;
}
public void setCurrentConnections(List<ConnectedBean> currentConnections) {
this.currentConnections = currentConnections;
} }
public boolean isMergesInvolved() { public boolean isMergesInvolved() {
@ -200,6 +232,22 @@ public class ManageProductBean implements Serializable{
this.mergesInvolved = mergesInvolved; this.mergesInvolved = mergesInvolved;
} }
public String getRecordUrl() {
return recordUrl;
}
public void setRecordUrl(String recordUrl) {
this.recordUrl = recordUrl;
}
public List<ConnectedBean> getConnections() {
return connections;
}
public void setConnections(List<ConnectedBean> connections) {
this.connections = connections;
}
@Override @Override
public String toString() { public String toString() {
return "ManageProductBean [semanticIdentifier=" + semanticIdentifier return "ManageProductBean [semanticIdentifier=" + semanticIdentifier
@ -212,7 +260,12 @@ public class ManageProductBean implements Serializable{
+ currentStatus + ", newStatus=" + newStatus + ", annotation=" + currentStatus + ", newStatus=" + newStatus + ", annotation="
+ annotation + ", extrasIfAvailable=" + extrasIfAvailable + annotation + ", extrasIfAvailable=" + extrasIfAvailable
+ ", sources=" + sources + ", similarGrsfRecords=" + ", sources=" + sources + ", similarGrsfRecords="
+ similarGrsfRecords + ", connectTo=" + connectTo + similarGrsfRecords + ", suggestedByKnowledgeBase="
+ ", mergesInvolved=" + mergesInvolved + "]"; + suggestedByKnowledgeBase + ", suggestdByAdministrator="
+ suggestdByAdministrator + ", currentConnections="
+ currentConnections + ", connections=" + connections
+ ", mergesInvolved=" + mergesInvolved + ", recordUrl="
+ recordUrl + "]";
} }
} }