added support for ticket #7207

git-svn-id: http://svn.d4science-ii.research-infrastructures.eu/gcube/trunk/portlets/widgets/ckan-metadata-publisher-widget@144145 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Costantino Perciante 2017-02-22 16:40:16 +00:00
parent ffe9c462e0
commit f7d30e099a
7 changed files with 120 additions and 95 deletions

View File

@ -32,11 +32,11 @@ public interface CKanPublisherService extends RemoteService {
List<MetaDataProfileBean> getProfiles(String orgName); List<MetaDataProfileBean> getProfiles(String orgName);
/** /**
* Retrieve a partially filled bean given a folder id and its owner. * Retrieve a partially filled bean given a folder id/file id and its owner.
* @param folderId * @param folderIdOrFileId the id of the folder of file to publish
* @return @return a DatasetMetadataBean on success, <b>null</b> on error. * @return @return a DatasetMetadataBean on success, <b>null</b> on error.
*/ */
DatasetMetadataBean getDatasetBean(String folderId); DatasetMetadataBean getDatasetBean(String folderIdOrFileId);
/** /**
* Try to create such dataset starting from the information contained into the toCreate bean. * Try to create such dataset starting from the information contained into the toCreate bean.

View File

@ -23,11 +23,11 @@ public interface CKanPublisherServiceAsync {
void getLicenses(AsyncCallback<LicensesBean> callback); void getLicenses(AsyncCallback<LicensesBean> callback);
/** /**
* Retrieve a partially filled bean given a folder id and its owner. * Retrieve a partially filled bean given a folder id/file id and its owner.
* @param folderId * @param folderIdOrFileId the id of the folder of file to publish
* @return @return a DatasetMetadataBean on success, <b>null</b> on error. * @return @return a DatasetMetadataBean on success, <b>null</b> on error.
*/ */
void getDatasetBean(String folderId, void getDatasetBean(String folderIdOrFileId,
AsyncCallback<DatasetMetadataBean> callback); AsyncCallback<DatasetMetadataBean> callback);
/** /**

View File

@ -222,8 +222,8 @@ public class CreateDatasetForm extends Composite{
* @param idFolderWorkspace * @param idFolderWorkspace
* @param eventBus the event bus * @param eventBus the event bus
*/ */
public CreateDatasetForm(String idFolderWorkspace, HandlerManager eventBus) { public CreateDatasetForm(String idFolderOrFileWorkspace, HandlerManager eventBus) {
createDatasetFormBody(true, idFolderWorkspace, eventBus); createDatasetFormBody(true, idFolderOrFileWorkspace, eventBus);
} }
/** /**
@ -256,7 +256,7 @@ public class CreateDatasetForm extends Composite{
* @param owner * @param owner
* @param eventBus * @param eventBus
*/ */
private void createDatasetFormBody(final boolean isWorkspaceRequest, final String idFolderWorkspace, final HandlerManager eventBus){ private void createDatasetFormBody(final boolean isWorkspaceRequest, final String idFolderOrFileWorkspace, final HandlerManager eventBus){
initWidget(uiBinder.createAndBindUi(this)); initWidget(uiBinder.createAndBindUi(this));
this.eventBus = eventBus; this.eventBus = eventBus;
@ -282,7 +282,7 @@ public class CreateDatasetForm extends Composite{
setAlertBlock("Retrieving information, please wait...", AlertType.INFO, true); setAlertBlock("Retrieving information, please wait...", AlertType.INFO, true);
// get back the licenses and the metadata information // get back the licenses and the metadata information
ckanServices.getDatasetBean(idFolderWorkspace, new AsyncCallback<DatasetMetadataBean>() { ckanServices.getDatasetBean(idFolderOrFileWorkspace, new AsyncCallback<DatasetMetadataBean>() {
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
@ -339,16 +339,12 @@ public class CreateDatasetForm extends Composite{
} }
if(isWorkspaceRequest){ if(isWorkspaceRequest){
boolean showAlertMissingResources = bean.getResources() == null || bean.getResources().isEmpty() || bean.getResources().get(0).getChildren() == null ||
bean.getResources().get(0).getChildren().isEmpty();
// if there are not resources, for now just checked it ( and hide so that the step will be skipped) // if there are not resources, for now just checked it ( and hide so that the step will be skipped)
if(showAlertMissingResources){ if(hideManageResources()){
alertNoResources.setType(AlertType.WARNING); alertNoResources.setType(AlertType.WARNING);
alertNoResources.setVisible(true); alertNoResources.setVisible(true);
}else }else
resourcesTwinPanel = new TwinColumnSelectionMainPanel(bean.getResources().get(0)); resourcesTwinPanel = new TwinColumnSelectionMainPanel(bean.getResourceRoot());
} }
// set organizations // set organizations
@ -731,9 +727,8 @@ public class CreateDatasetForm extends Composite{
// we need to show the page to handle resources one by one from the workspace // we need to show the page to handle resources one by one from the workspace
formFirstStep.setVisible(false); formFirstStep.setVisible(false);
boolean hideManageResources = receivedBean.getResources() == null || receivedBean.getResources().isEmpty(); formSecondStep.setVisible(!hideManageResources());
formSecondStep.setVisible(!hideManageResources); formThirdStep.setVisible(hideManageResources());
formThirdStep.setVisible(hideManageResources);
// add the resources to the container panel // add the resources to the container panel
if(workspaceResourcesContainer.getWidget() == null){ if(workspaceResourcesContainer.getWidget() == null){
@ -773,9 +768,8 @@ public class CreateDatasetForm extends Composite{
// swap forms // swap forms
if(isWorkspaceRequest){ if(isWorkspaceRequest){
boolean resourcesPresent = receivedBean.getResources() != null && receivedBean.getResources().size() > 0 ? true : false; formFirstStep.setVisible(hideManageResources());
formFirstStep.setVisible(!resourcesPresent); formSecondStep.setVisible(!hideManageResources());
formSecondStep.setVisible(resourcesPresent);
}else{ }else{
formFirstStep.setVisible(true); formFirstStep.setVisible(true);
formSecondStep.setVisible(false); formSecondStep.setVisible(false);
@ -853,7 +847,7 @@ public class CreateDatasetForm extends Composite{
receivedBean.setSelectedOrganization(chosenOrganization); receivedBean.setSelectedOrganization(chosenOrganization);
receivedBean.setGroups(groups); receivedBean.setGroups(groups);
if(resourcesTwinPanel != null) if(resourcesTwinPanel != null)
receivedBean.setResources(resourcesTwinPanel.getResourcesToPublish()); receivedBean.setResourceRoot(resourcesTwinPanel.getResourcesToPublish());
Map<String, List<String>> customFieldsMap = new HashMap<String, List<String>>(); Map<String, List<String>> customFieldsMap = new HashMap<String, List<String>>();
@ -1122,7 +1116,7 @@ public class CreateDatasetForm extends Composite{
focusPanelTitle, focusPanelTitle,
popupOpenedIds popupOpenedIds
); );
// description // description
InfoIconsLabels.preparePopupPanelAndPopover( InfoIconsLabels.preparePopupPanelAndPopover(
InfoIconsLabels.DESCRIPTION_INFO_ID_POPUP, InfoIconsLabels.DESCRIPTION_INFO_ID_POPUP,
@ -1279,7 +1273,7 @@ public class CreateDatasetForm extends Composite{
organizationsGroup.setType(ControlGroupType.ERROR); organizationsGroup.setType(ControlGroupType.ERROR);
return "You must select an organization in which you want to publish"; return "You must select an organization in which you want to publish";
} }
// at least one tag.. // at least one tag..
if(tagsPanel.getTags().isEmpty()){ if(tagsPanel.getTags().isEmpty()){
tagsPanel.setGroupPanelType(ControlGroupType.ERROR); tagsPanel.setGroupPanelType(ControlGroupType.ERROR);
@ -1425,4 +1419,15 @@ public class CreateDatasetForm extends Composite{
groupsControlGroup.setVisible(true); groupsControlGroup.setVisible(true);
} }
/**
* Check if resource(s) are missing
* @return
*/
private boolean hideManageResources(){
return receivedBean.getResourceRoot() == null || (receivedBean.getResourceRoot().isFolder() && (receivedBean.getResourceRoot().getChildren() == null ||
receivedBean.getResourceRoot().getChildren().isEmpty()));
}
} }

View File

@ -1,6 +1,7 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui.TwinColumnSelection; package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui.TwinColumnSelection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -145,8 +146,11 @@ public class TwinColumnSelectionMainPanel extends Composite{
// Set a key provider that provides a unique key for each object. // Set a key provider that provides a unique key for each object.
cellListLeft = new CellList<ResourceElementBean>(cell, ResourceElementBean.KEY_PROVIDER); cellListLeft = new CellList<ResourceElementBean>(cell, ResourceElementBean.KEY_PROVIDER);
cellListLeft.setPageSize(initialBean.getChildren().size());
cellListLeft.setKeyboardPagingPolicy(KeyboardPagingPolicy.INCREASE_RANGE); cellListLeft.setKeyboardPagingPolicy(KeyboardPagingPolicy.INCREASE_RANGE);
// set page size
int size = initialBean.isFolder() ? initialBean.getChildren().size() : 1;
cellListLeft.setPageSize(size);
// Add a selection model so we can select cells. // Add a selection model so we can select cells.
selectionModelLeft = new MultiSelectionModel<ResourceElementBean>(ResourceElementBean.KEY_PROVIDER); selectionModelLeft = new MultiSelectionModel<ResourceElementBean>(ResourceElementBean.KEY_PROVIDER);
@ -207,8 +211,11 @@ public class TwinColumnSelectionMainPanel extends Composite{
}); });
// set the list into the provider // set the list into the provider
Collections.sort(this.initialBean.getChildren()); if(initialBean.isFolder()){
dataProviderLeft.setList(this.initialBean.getChildren()); Collections.sort(this.initialBean.getChildren());
dataProviderLeft.setList(this.initialBean.getChildren());
}else
dataProviderLeft.setList(Arrays.asList(this.initialBean));
// add root to breadcrumb // add root to breadcrumb
final NavLink root = new NavLink(initialBean.getName()); final NavLink root = new NavLink(initialBean.getName());
@ -478,20 +485,25 @@ public class TwinColumnSelectionMainPanel extends Composite{
} }
/** /**
* Returns the list of files to save * Returns the root parent with the children as files to save
* @return the resources to save * @return the resources to save
*/ */
public List<ResourceElementBean> getResourcesToPublish(){ public ResourceElementBean getResourcesToPublish(){
List<ResourceElementBean> current = dataProviderRight.getList(); List<ResourceElementBean> current = dataProviderRight.getList();
List<ResourceElementBean> toReturn = new ArrayList<ResourceElementBean>();
ResourceElementBean toReturn = new ResourceElementBean();
List<ResourceElementBean> children = new ArrayList<ResourceElementBean>();
for (ResourceElementBean resource : current) { for (ResourceElementBean resource : current) {
if(resource.isToBeAdded() && !resource.isFolder()){ // be sure ... if(resource.isToBeAdded() && !resource.isFolder()){ // be sure ...
ResourceElementBean beanWithoutChildren = new ResourceElementBean(resource); ResourceElementBean beanWithoutChildren = new ResourceElementBean(resource);
beanWithoutChildren.setName(resource.getEditableName()); beanWithoutChildren.setName(resource.getEditableName());
toReturn.add(beanWithoutChildren); children.add(beanWithoutChildren);
} }
} }
toReturn.setChildren(children);
return toReturn; return toReturn;
} }

View File

@ -264,10 +264,10 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
// get the list of resources and convert to ResourceBean // get the list of resources and convert to ResourceBean
List<ResourceBean> resources = null; List<ResourceBean> resources = null;
List<ResourceElementBean> resourcesToAdd = toCreate.getResources(); ResourceElementBean resourcesToAdd = toCreate.getResourceRoot();
// we need to copy such resource in the .catalogue area of the user's ws // we need to copy such resource in the .catalogue area of the user's ws
if(resourcesToAdd != null && !resourcesToAdd.isEmpty()){ if(resourcesToAdd != null){
resources = WorkspaceUtils.copyResourcesToUserCatalogueArea(toCreate.getId(), userName, toCreate); resources = WorkspaceUtils.copyResourcesToUserCatalogueArea(toCreate.getId(), userName, toCreate);
} }
@ -521,7 +521,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
logger.warn("OUT FROM PORTAL DETECTED RETURNING TRUE"); logger.warn("OUT FROM PORTAL DETECTED RETURNING TRUE");
return false; return false;
} }
try{ try{
HttpSession httpSession = this.getThreadLocalRequest().getSession(); HttpSession httpSession = this.getThreadLocalRequest().getSession();
@ -570,7 +570,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
logger.info("Does the user have the right to publish on the catalogue? " + role); logger.info("Does the user have the right to publish on the catalogue? " + role);
return !role.equals(RolesCkanGroupOrOrg.MEMBER); return !role.equals(RolesCkanGroupOrOrg.MEMBER);
}catch(Exception e){ }catch(Exception e){
logger.error("Failed to check the user's role", e); logger.error("Failed to check the user's role", e);
} }

View File

@ -31,21 +31,22 @@ public class WorkspaceUtils {
private static final String RESOURCES_NAME_SEPARATOR = "_"; private static final String RESOURCES_NAME_SEPARATOR = "_";
/** /**
* Copy into the .catalogue area folder the checked resources * Copy into the .catalogue area folder the checked resources.
* There is no difference among a single-file-publish and a folder-publish.
* @param folderId * @param folderId
* @param userName * @param userName
* @param bean * @param bean
* @return * @return
*/ */
public static List<ResourceBean> copyResourcesToUserCatalogueArea(String folderId, String userName, DatasetMetadataBean bean) throws Exception{ public static List<ResourceBean> copyResourcesToUserCatalogueArea(String folderOrFileId, String userName, DatasetMetadataBean bean) throws Exception{
logger.debug("Request to copy onto catalogue area...."); logger.debug("Request to copy onto catalogue area....");
List<ResourceBean> resources = new ArrayList<ResourceBean>(); List<ResourceBean> resources = new ArrayList<ResourceBean>();
WorkspaceItem copiedFolder = null; WorkspaceItem copiedFolder = null;
WorkspaceCatalogue userCatalogue = null; WorkspaceCatalogue userCatalogue = null;
List<ResourceElementBean> resourcesToAdd = bean.getResources(); ResourceElementBean rootResource = bean.getResourceRoot();
// in to the .catalogue area of the user's workspace // into the .catalogue area of the user's workspace
Workspace ws = HomeLibrary Workspace ws = HomeLibrary
.getHomeManagerFactory() .getHomeManagerFactory()
.getHomeManager() .getHomeManager()
@ -55,16 +56,25 @@ public class WorkspaceUtils {
// Retrieve the catalogue of the user // Retrieve the catalogue of the user
userCatalogue = ws.getCatalogue(); userCatalogue = ws.getCatalogue();
// Create the folder in the catalogue // get workspace item (it could be a file or a folder)
copiedFolder = userCatalogue.addWorkspaceItem(folderId, userCatalogue.getId()); // add to .catalogue root area WorkspaceItem originalItem = ws.getItem(folderOrFileId);
// change description for the folder // copy the folder in the catalogue if it is a folder, or create a new folder
copiedFolder.setDescription(bean.getDescription()); long referenceTime = System.currentTimeMillis();
if(originalItem.isFolder()){
copiedFolder = userCatalogue.addWorkspaceItem(folderOrFileId, userCatalogue.getId()); // add to .catalogue root area
copiedFolder.setDescription(bean.getDescription());
}
else{
copiedFolder = userCatalogue.createFolder(UtilMethods.fromProductTitleToName(bean.getTitle()) + "_" + referenceTime, bean.getDescription());
}
// change name of the copied folder to match the title (append the timestamp to avoid ties) // change name of the copied folder to match the title (append the timestamp to avoid ties)
long referenceTime = System.currentTimeMillis();
((WorkspaceFolder)copiedFolder).rename(UtilMethods.fromProductTitleToName(bean.getTitle()) + "_" + referenceTime); ((WorkspaceFolder)copiedFolder).rename(UtilMethods.fromProductTitleToName(bean.getTitle()) + "_" + referenceTime);
// retrieve the children
List<ResourceElementBean> resourcesToAdd = rootResource.getChildren();
// copy only the selected ones // copy only the selected ones
for(ResourceElementBean resource : resourcesToAdd){ for(ResourceElementBean resource : resourcesToAdd){
@ -90,6 +100,7 @@ public class WorkspaceUtils {
// postpone rename operation // postpone rename operation
copiedFile.rename(resource.getName() + "_" + referenceTime); copiedFile.rename(resource.getName() + "_" + referenceTime);
} }
} }
return resources; return resources;
} }
@ -111,33 +122,45 @@ public class WorkspaceUtils {
.getHomeManager() .getHomeManager()
.getHome().getWorkspace(); .getHome().getWorkspace();
WorkspaceItem originalFolder = ws.getItem(folderId); WorkspaceItem originalFolderOrFile = ws.getItem(folderId);
// set some info if(!originalFolderOrFile.isFolder()){
String onlyAlphanumericTitle = originalFolder.getName().replaceAll("[^A-Za-z0-9.-_]", " "); // that is, remove characters different than the ones inside
// since it will (likely) be the name of the product
bean.setTitle(onlyAlphanumericTitle);
bean.setDescription(originalFolder.getDescription());
// Create the folder in the catalogue ResourceElementBean resource = new ResourceElementBean();
Map<String, String> folderItems = Utils.getGcubeItemProperties(originalFolder); resource.setDescription(originalFolderOrFile.getDescription());
resource.setFolder(false);
resource.setEditableName(originalFolderOrFile.getName());
resource.setName(originalFolderOrFile.getName());
resource.setOriginalIdInWorkspace(folderId);
bean.setResourceRoot(resource);
bean.setTitle(originalFolderOrFile.getName().replaceAll("[^A-Za-z0-9.-_]", " "));
bean.setDescription(originalFolderOrFile.getDescription());
if(folderItems != null){ }else{
// transform this properties
Map<String, List<String>> tempItems = new HashMap<String, List<String>>(folderItems.size());
Iterator<Entry<String, String>> iterator = folderItems.entrySet().iterator(); String onlyAlphanumericTitle = originalFolderOrFile.getName().replaceAll("[^A-Za-z0-9.-_]", " "); // that is, remove characters different than the ones inside
while (iterator.hasNext()) { bean.setTitle(onlyAlphanumericTitle);
Map.Entry<java.lang.String, java.lang.String> entry = (Map.Entry<java.lang.String, java.lang.String>) iterator bean.setDescription(originalFolderOrFile.getDescription());
.next();
tempItems.put(entry.getKey(), Arrays.asList(entry.getValue())); // Create the folder in the catalogue
Map<String, String> folderItems = Utils.getGcubeItemProperties(originalFolderOrFile);
if(folderItems != null){
// transform this properties
Map<String, List<String>> tempItems = new HashMap<String, List<String>>(folderItems.size());
Iterator<Entry<String, String>> iterator = folderItems.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<java.lang.String, java.lang.String> entry = (Map.Entry<java.lang.String, java.lang.String>) iterator
.next();
tempItems.put(entry.getKey(), Arrays.asList(entry.getValue()));
}
bean.setCustomFields(tempItems);
} }
bean.setCustomFields(tempItems);
}
// set them into the bean // set them into the bean
bean.setResources(Arrays.asList(WorkspaceUtils.getTreeFromFolder(folderId, ws))); bean.setResourceRoot(WorkspaceUtils.getTreeFromFolder(folderId, ws));
}
} }
@ -216,4 +239,4 @@ public class WorkspaceUtils {
rootElem.setEditableName(fullPath); rootElem.setEditableName(fullPath);
} }
} }

View File

@ -5,23 +5,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* This bean will contain during ckan metadata creation the following information * This bean will contain during ckan metadata creation information related to the future build.
* (related to the workspace folder that represents a dataset)
* <ul>
* <li> id -> the id that will be assigned by ckan
* <li> Title -> folder's name
* <li> Description -> folders' description
* <li> tags -> folder's custom fields keys' names
* <li> visibility -> as chosen by the creator (visible = true, not visible = false)
* <li> source -> url of the folder within the workspace
* <li> version -> during creation it is going to be 1.0
* <li> author, maintainer -> folder's owner
* <li> custom fields -> gcube items <key, value> couple
* <li> organizationsList -> list of organizations to which the user belong (and in which
* he wants to publish)
* <li> list of metadata, that is custom fields per vre
* <li> the name of the chosen profile used
* </ul>
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ -44,7 +28,7 @@ public class DatasetMetadataBean implements Serializable {
private long version; // version 1, 2 ... private long version; // version 1, 2 ...
private boolean visibility; // Private (false) or Public(true) private boolean visibility; // Private (false) or Public(true)
private List<OrganizationBean> organizationList; // list of organization in which the user is present and could create the dataset private List<OrganizationBean> organizationList; // list of organization in which the user is present and could create the dataset
private List<ResourceElementBean> resources; // in case of workspace, this is the list of children of the folder private ResourceElementBean resourceRoot; // in case of workspace, this is the directory root or the single file information
private List<MetaDataProfileBean> metadataList; private List<MetaDataProfileBean> metadataList;
private List<String> tags; // on retrieve, they are the keys of the custom fields private List<String> tags; // on retrieve, they are the keys of the custom fields
private Map<String, List<String>> customFields; private Map<String, List<String>> customFields;
@ -81,7 +65,7 @@ public class DatasetMetadataBean implements Serializable {
String authorName, String authorSurname, String authorEmail, String maintainer, String authorName, String authorSurname, String authorEmail, String maintainer,
String maintainerEmail, String ownerIdentifier, String maintainerEmail, String ownerIdentifier,
List<OrganizationBean> organizationList, String selectedOrganization, List<OrganizationBean> organizationList, String selectedOrganization,
List<ResourceElementBean> resources, ResourceElementBean resourceRoot,
List<MetaDataProfileBean> metadataList, List<GroupBean> groups) { List<MetaDataProfileBean> metadataList, List<GroupBean> groups) {
super(); super();
this.id = id; this.id = id;
@ -101,7 +85,7 @@ public class DatasetMetadataBean implements Serializable {
this.ownerIdentifier = ownerIdentifier; this.ownerIdentifier = ownerIdentifier;
this.organizationList = organizationList; this.organizationList = organizationList;
this.selectedOrganization = selectedOrganization; this.selectedOrganization = selectedOrganization;
this.resources = resources; this.resourceRoot = resourceRoot;
this.metadataList = metadataList; this.metadataList = metadataList;
this.groups = groups; this.groups = groups;
} }
@ -250,12 +234,12 @@ public class DatasetMetadataBean implements Serializable {
this.selectedOrganization = selectedOrganization; this.selectedOrganization = selectedOrganization;
} }
public List<ResourceElementBean> getResources() { public ResourceElementBean getResourceRoot() {
return resources; return resourceRoot;
} }
public void setResources(List<ResourceElementBean> resources) { public void setResourceRoot(ResourceElementBean resourceRoot) {
this.resources = resources; this.resourceRoot = resourceRoot;
} }
public String getAuthorFullName() { public String getAuthorFullName() {
@ -294,9 +278,10 @@ public class DatasetMetadataBean implements Serializable {
+ ", chosenProfile=" + chosenProfile + ", chosenProfile=" + chosenProfile
+ ", selectedOrganization=" + selectedOrganization + ", selectedOrganization=" + selectedOrganization
+ ", version=" + version + ", visibility=" + visibility + ", version=" + version + ", visibility=" + visibility
+ ", organizationList=" + organizationList + ", resources=" + ", organizationList=" + organizationList + ", resourceRoot="
+ resources + ", metadataList=" + metadataList + ", tags=" + resourceRoot + ", metadataList=" + metadataList + ", tags="
+ tags + ", customFields=" + customFields + ", groups=" + tags + ", customFields=" + customFields + ", groups="
+ groups + "]"; + groups + "]";
} }
}
}