diff --git a/pom.xml b/pom.xml index bc2d198..3ea0936 100644 --- a/pom.xml +++ b/pom.xml @@ -156,9 +156,9 @@ provided - org.gcube.dataaccess + org.gcube.data-catalogue ckan-util-library - 1.0.0-SNAPSHOT + [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT) org.gcube.core diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java index bfa32a4..94d62d9 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherService.java @@ -2,6 +2,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean; +import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @@ -31,8 +32,17 @@ public interface CKanPublisherService extends RemoteService { /** * Try to create such dataset starting from the information contained into the toCreate bean. * @param toCreate - * @return true on success, false otherwise + * @return the identifier of the created dataset or null on error */ - boolean createCKanDataset(DatasetMetadataBean toCreate); + String createCKanDataset(DatasetMetadataBean toCreate); + + /** + * Add this resource to the dataset whose id is datasetId + * @param resource + * @param datasetId + * @param owner of the dataset + * @param callback + */ + boolean addResourceToDataset(ResourceBean resource, String datasetId, String owner); } diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java index ba890a7..a2eb722 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/CKanPublisherServiceAsync.java @@ -2,6 +2,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean; +import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean; import com.google.gwt.user.client.rpc.AsyncCallback; @@ -33,6 +34,16 @@ public interface CKanPublisherServiceAsync { * @return true on success, false otherwise */ void createCKanDataset(DatasetMetadataBean toCreate, + AsyncCallback callback); + + /** + * Add this resource to the dataset whose id is datasetId + * @param resource + * @param datasetId + * @param owner of the dataset + * @param callback + */ + void addResourceToDataset(ResourceBean resource, String datasetId, String owner, AsyncCallback callback); } diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEvent.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEvent.java new file mode 100644 index 0000000..05a5f4f --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEvent.java @@ -0,0 +1,33 @@ +package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events; + +import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean; + +import com.google.gwt.event.shared.GwtEvent; + +/** + * Added resource event + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class AddResourceEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private ResourceBean resource; + + public AddResourceEvent(ResourceBean resource) { + this.resource = resource; + } + + public ResourceBean getResource() { + return resource; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(AddResourceEventHandler handler) { + handler.onAddedResource(this); + } +} diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEventHandler.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEventHandler.java new file mode 100644 index 0000000..5448594 --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/AddResourceEventHandler.java @@ -0,0 +1,11 @@ +package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Added resource handler interface + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public interface AddResourceEventHandler extends EventHandler { + void onAddedResource(AddResourceEvent addResourceEvent); +} diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java index 4374ac1..bf9287d 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEvent.java @@ -3,7 +3,10 @@ import org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui.CustomFieldE import com.google.gwt.event.shared.GwtEvent; - +/** + * Delete custom field event. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ public class DeleteCustomFieldEvent extends GwtEvent { public static Type TYPE = new Type(); diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java index 6cfb050..6e5b2b4 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/events/DeleteCustomFieldEventHandler.java @@ -2,6 +2,10 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events; import com.google.gwt.event.shared.EventHandler; +/** + * Handler associated to the DeleteCustomFieldEvent + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ public interface DeleteCustomFieldEventHandler extends EventHandler { void onRemoveEntry(DeleteCustomFieldEvent event); } diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.java new file mode 100644 index 0000000..453b12e --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.java @@ -0,0 +1,142 @@ +package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui; + +import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService; +import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync; +import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEvent; +import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean; + +import com.github.gwtbootstrap.client.ui.AlertBlock; +import com.github.gwtbootstrap.client.ui.Button; +import com.github.gwtbootstrap.client.ui.TextArea; +import com.github.gwtbootstrap.client.ui.TextBox; +import com.github.gwtbootstrap.client.ui.constants.AlertType; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +/** + * Form used to add resource(s) to a dataset + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class AddResourceToDataset extends Composite{ + + private static AddResourceToDatasetUiBinder uiBinder = GWT + .create(AddResourceToDatasetUiBinder.class); + + interface AddResourceToDatasetUiBinder extends + UiBinder { + } + + // bus to alert the dataset form about this new resource + private HandlerManager eventBus; + + // the dataset id + private String datasetId; + + // the owner + private String owner; + + //Create a remote service proxy to talk to the server-side ckan service. + private final CKanPublisherServiceAsync ckanServices = GWT.create(CKanPublisherService.class); + + @UiField TextBox resourceUrlTextBox; + @UiField TextBox resourceNameTextBox; + @UiField TextArea resourceDescriptionTextArea; + @UiField Button addResourceButton; + @UiField AlertBlock infoBlock; + + public AddResourceToDataset(HandlerManager eventBus, String datasetId, String owner) { + initWidget(uiBinder.createAndBindUi(this)); + + // save bus + this.eventBus = eventBus; + + // save dataset id (it is needed when we will add resources) + this.datasetId = datasetId; + + // the owner of the dataset/files + this.owner = owner; + } + + @UiHandler("addResourceButton") + void onAddButtonClick(ClickEvent e){ + + infoBlock.setVisible(false); + + // validation + if(resourceUrlTextBox.getText().isEmpty()){ + + infoBlock.setType(AlertType.ERROR); + infoBlock.setText("URL cannot be empty"); + infoBlock.setVisible(true); + return; + + } + + // remove html tags into description + //String description = convert(resourceDescriptionTextArea.getText()); TODO + + // collect data and build up the bean + final ResourceBean resource = new ResourceBean(resourceUrlTextBox.getText(), resourceNameTextBox.getText(), resourceDescriptionTextArea.getText()); + + // disable add button + addResourceButton.setEnabled(false); + + // try to create + ckanServices.addResourceToDataset(resource, datasetId, owner, new AsyncCallback() { + + @Override + public void onSuccess(Boolean result) { + + if(result){ + showAlert("Resource created correctly", AlertType.SUCCESS); + eventBus.fireEvent(new AddResourceEvent(resource)); + } + else + showAlert("Unable to add this resource. Check the url is correct", AlertType.ERROR); + + } + + @Override + public void onFailure(Throwable caught) { + + showAlert("Unable to add this resource, sorry", AlertType.ERROR); + + } + }); + + } + + /** + * Show error/success after resource creation attempt. + * @param text + * @param type + */ + protected void showAlert(String text, AlertType type) { + + infoBlock.setText(text); + infoBlock.setType(type); + infoBlock.setVisible(true); + addResourceButton.setEnabled(true); + + // hide after some seconds + Timer t = new Timer() { + + @Override + public void run() { + + infoBlock.setVisible(false); + + } + }; + + t.schedule(4000); + } +} diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.ui.xml b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.ui.xml new file mode 100644 index 0000000..15eb527 --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddResourceToDataset.ui.xml @@ -0,0 +1,124 @@ + + + + .form-main-style { + margin-left: 10px; + } + + .fieldset-border-style { + border: 1px groove #444; + -webkit-box-shadow: 0px 0px 0px 0px #000; + box-shadow: 0px 0px 0px 0px #000; + padding: 10px; + } + + .legend-style { + width: auto; + padding: 10px; + margin-bottom: 0px; + } + + @external .form-horizontal .input-large; + .form-horizontal .input-large { + width: 95%; + } + + .block-alert-style { + margin-top: 10px; + padding: 10px; + margin-bottom: 10px; + } + + .tagsPanelStyle { + display: inline-block; + } + + .add-resource-button { + float: right; + } + + .info-markdown { + width: 95%; + background-color: #ebebeb; + border-bottom: 1px thin; + border-left: 1px thin; + border-right: 1px thin; + padding: 3px; + } + + + + + + + Add Resource + + * + is required + + + + + + * + URL: + + + + + + + + + Name: + + + + + + + + + Description: + + + + + You can use + + Markdown formatting + + + + + + + + + + Add + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.java new file mode 100644 index 0000000..b338e85 --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.java @@ -0,0 +1,134 @@ +package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEvent; +import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEventHandler; +import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean; + +import com.github.gwtbootstrap.client.ui.Button; +import com.github.gwtbootstrap.client.ui.constants.ButtonType; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; + +/** + * A summary of the added resources by the user. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class AddedResourcesSummary extends Composite{ + + private static AddedResourcesSummaryUiBinder uiBinder = GWT + .create(AddedResourcesSummaryUiBinder.class); + + interface AddedResourcesSummaryUiBinder extends + UiBinder { + } + + // Event bus + private HandlerManager eventBus; + + // list of added resources + List addedResources; + + @UiField VerticalPanel addResourcesPanel; + + public AddedResourcesSummary(HandlerManager eventBus) { + initWidget(uiBinder.createAndBindUi(this)); + + // save bus + this.eventBus = eventBus; + + // bind on add resource event + bind(); + + // init list + addedResources = new ArrayList(); + } + + private void bind() { + + // when a new resource is added + eventBus.addHandler(AddResourceEvent.TYPE, new AddResourceEventHandler() { + + @Override + public void onAddedResource(AddResourceEvent addResourceEvent) { + + // get the resource + final ResourceBean justAddedResource = addResourceEvent.getResource(); + + // check if a resource with this id already exists + for (ResourceBean resource : addedResources){ + + if(resource.getId().equals(justAddedResource.getId())){ + + // clear list and rebuild + rebuildSummary(); + return; + } + } + + Button associatedButton = new Button(); + associatedButton.setType(ButtonType.LINK); + associatedButton.setText("-" + justAddedResource.getName()); + + // add to the list + addedResources.add(justAddedResource); + + // add to the panel + addResourcesPanel.add(associatedButton); + + // add handler to swap tab on click + associatedButton.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + + // TODO show information below this link and swap panel + + } + }); + + } + }); + } + + /** + * Rebuild the summary list + */ + protected void rebuildSummary() { + + addResourcesPanel.clear(); + + for (final ResourceBean resource : addedResources){ + + Button associatedButton = new Button(); + associatedButton.setType(ButtonType.LINK); + associatedButton.setText("-" + resource.getName()); + + // add to the list + addedResources.add(resource); + + // add to the panel + addResourcesPanel.add(associatedButton); + + // add handler to swap tab on click + associatedButton.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + + // TODO show information below this link and swap panel + + } + }); + } + } +} diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.ui.xml b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.ui.xml new file mode 100644 index 0000000..79ee8b8 --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/AddedResourcesSummary.ui.xml @@ -0,0 +1,11 @@ + + + + + + +

Added Resources

+ +
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java index 438587f..dc7e066 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.commons.lang.StringEscapeUtils; import org.gcube.portlets.user.gcubewidgets.client.elements.Span; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync; @@ -20,12 +19,14 @@ import com.github.gwtbootstrap.client.ui.AlertBlock; import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.CheckBox; import com.github.gwtbootstrap.client.ui.ControlGroup; -import com.github.gwtbootstrap.client.ui.HelpBlock; import com.github.gwtbootstrap.client.ui.ListBox; +import com.github.gwtbootstrap.client.ui.Tab; +import com.github.gwtbootstrap.client.ui.TabPanel; import com.github.gwtbootstrap.client.ui.TextArea; import com.github.gwtbootstrap.client.ui.TextBox; import com.github.gwtbootstrap.client.ui.base.ListItem; import com.github.gwtbootstrap.client.ui.constants.AlertType; +import com.github.gwtbootstrap.client.ui.resources.Bootstrap.Tabs; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -39,6 +40,7 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; /** @@ -65,7 +67,6 @@ public class EditMetadataForm extends Composite{ @UiField FlowPanel tagsPanel; @UiField ListBox licenseListbox; @UiField ListBox visibilityListbox; - @UiField ListBox searchableListbox; @UiField ListBox organizationsListbox; @UiField TextBox versionTextbox; @UiField TextBox authorTextbox; @@ -75,10 +76,19 @@ public class EditMetadataForm extends Composite{ @UiField ControlGroup customFields; @UiField Button addCustomFieldButton; @UiField CheckBox addResourcesCheckBox; + @UiField ControlGroup resourcesControlGroup; @UiField Button createButton; + @UiField Button addResourcesButton; @UiField Button resetButton; @UiField AlertBlock infoBlock; @UiField AlertBlock onCreateAlertBlock; + @UiField VerticalPanel resourcesPanel; + + // tab panel + private TabPanel tabPanel; + + // add resource form + AddResourceToDataset resourceForm; // tags list private List tagsList = new ArrayList(); @@ -95,14 +105,18 @@ public class EditMetadataForm extends Composite{ // dataset metadata bean private DatasetMetadataBean receivedBean; + // the owner + private String owner; /** * Invoked in the most general case * @param owner */ - public EditMetadataForm(String user) { + public EditMetadataForm(String owner) { initWidget(uiBinder.createAndBindUi(this)); + this.owner = owner; + // bind on events bind(); @@ -114,7 +128,7 @@ public class EditMetadataForm extends Composite{ resetButton.setEnabled(false); // get back the licenses and the metadata information - ckanServices.getDatasetBean(null, user, new AsyncCallback() { + ckanServices.getDatasetBean(null, owner, new AsyncCallback() { @Override public void onSuccess(DatasetMetadataBean bean) { @@ -187,6 +201,9 @@ public class EditMetadataForm extends Composite{ } }); + // hide the Add resources checkbox + resourcesControlGroup.setVisible(false); + } /** @@ -197,6 +214,8 @@ public class EditMetadataForm extends Composite{ public EditMetadataForm(String idFolderWorkspace, String owner) { initWidget(uiBinder.createAndBindUi(this)); + this.owner = owner; + // bind on events bind(); @@ -355,7 +374,6 @@ public class EditMetadataForm extends Composite{ String description = descriptionTextarea.getText(); String selectedLicense = licenseListbox.getSelectedItemText(); String visibility = visibilityListbox.getSelectedItemText(); - String searchable = searchableListbox.getSelectedItemText(); long version = Long.valueOf(versionTextbox.getValue()); String author = authorTextbox.getValue(); String authorEmail = authorEmailTextbox.getValue(); @@ -374,7 +392,6 @@ public class EditMetadataForm extends Composite{ receivedBean.setVersion(version); receivedBean.setVisibility(visibility.equals("Public")); receivedBean.setTitle(title); - receivedBean.setSearchable(searchable.equals("Yes")); receivedBean.setTags(tagsList); receivedBean.setSelectedOrganization(chosenOrganization); receivedBean.setAddResources(addResources); @@ -399,15 +416,62 @@ public class EditMetadataForm extends Composite{ onCreateAlertBlock.setText("Trying to create dataset, please wait"); onCreateAlertBlock.setVisible(true); - ckanServices.createCKanDataset(receivedBean, new AsyncCallback() { + ckanServices.createCKanDataset(receivedBean, new AsyncCallback() { @Override - public void onSuccess(Boolean result) { + public void onSuccess(final String datasetId) { - if(result){ + if(datasetId != null){ alertOnCreate("Dataset correctly created!", AlertType.SUCCESS); + // disable dataset fields + disableDatasetFields(); + + // if we are in the "general case" we need to show a form for adding resources + if(!resourcesControlGroup.isVisible()){ + + createButton.setVisible(false); + resetButton.setVisible(false); + + // show the add resources button + addResourcesButton.setVisible(true); + addResourcesButton.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + + // hide the button + addResourcesButton.setVisible(false); + + // TabPanel + tabPanel = new TabPanel(Tabs.ABOVE); + tabPanel.setWidth("100%"); + + // add the form + resourceForm = new AddResourceToDataset(eventBus, datasetId, owner); + + // tab for the form + Tab formContainer = new Tab(); + formContainer.add(resourceForm); + formContainer.setHeading("Add New Resource"); + tabPanel.add(formContainer); + + // tab for the added resources + Tab addedResources = new Tab(); + addedResources.add(new AddedResourcesSummary(eventBus)); + addedResources.setHeading("Added Resource"); + tabPanel.add(addedResources); + + // add tabs to resources panel + tabPanel.selectTab(0); + resourcesPanel.add(tabPanel); + resourcesPanel.setVisible(true); + + } + }); + } + }else{ alertOnCreate("Unable to create this dataset, please retry later", AlertType.ERROR); @@ -457,8 +521,8 @@ public class EditMetadataForm extends Composite{ * @return true on success */ private boolean validateData() { - + // TODO return true; } @@ -483,6 +547,24 @@ public class EditMetadataForm extends Composite{ } + /** + * Disable dataset editable fields + */ + protected void disableDatasetFields() { + titleTextBox.setEnabled(false); + descriptionTextarea.setEnabled(false); + versionTextbox.setEnabled(false); + authorTextbox.setEnabled(false); + authorEmailTextbox.setEnabled(false); + maintainerTextbox.setEnabled(false); + maintainerEmailTextbox.setEnabled(false); + visibilityListbox.setEnabled(false); + tagsEnterTextBox.setEnabled(false); + licenseListbox.setEnabled(false); + organizationsListbox.setEnabled(false); + addCustomFieldButton.setEnabled(false); + } + /** * change alert block behavior. * @param textToShow diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml index d6e1895..66d0cb7 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/client/ui/EditMetadataForm.ui.xml @@ -22,7 +22,6 @@ @external .form-horizontal .input-large; .form-horizontal .input-large { width: 95%; - margin-bottom: 15px; } .block-alert-style { @@ -34,6 +33,15 @@ .tagsPanelStyle { display: inline-block; } + + .info-markdown { + width: 95%; + background-color: #ebebeb; + border-bottom: 1px thin; + border-left: 1px thin; + border-right: 1px thin; + padding: 3px; + } + + You can use + + Markdown formatting + + @@ -135,23 +162,6 @@ - - Searchable: - - - Yes - No - - - - Searchable datasets can be searched by anyone, while - not-searchable datasets can only be accessed by entering directly - its URL. - - - - Publish in: @@ -233,7 +243,7 @@ - + + + Add Resources + + Create diff --git a/src/main/webapp/CKanMetadataPublisher.css b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/public/CKanMetadataPublisher.css similarity index 100% rename from src/main/webapp/CKanMetadataPublisher.css rename to src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/public/CKanMetadataPublisher.css diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java index d5fc0b5..a41f983 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/server/CKANPublisherServicesImpl.java @@ -1,5 +1,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.server; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; @@ -15,10 +17,11 @@ import org.gcube.common.homelibrary.home.workspace.WorkspaceItem; import org.gcube.common.homelibrary.home.workspace.folder.FolderItem; import org.gcube.common.homelibrary.home.workspace.folder.items.GCubeItem; import org.gcube.common.scope.api.ScopeProvider; -import org.gcube.dataaccess.ckanutillibrary.CKanUtilsFactory; +import org.gcube.datacatalogue.ckanutillibrary.CKanUtilsFactory; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean; +import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean; import org.gcube.vomanagement.usermanagement.UserManager; import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; import org.gcube.vomanagement.usermanagement.model.GCubeUser; @@ -53,7 +56,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C if(isWithinPortal()) return ScopeProvider.instance.get(); else - return "/gcube"; + return "/gcube"; // Development } @@ -67,7 +70,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C try{ String currentScope = getCurrentScope(); - return "https://" + CKanUtilsFactory.getInstance().getCkanUtilsForScope(currentScope).getCatalogueUrl(); + return CKanUtilsFactory.getInstance().getCkanUtilsForScope(currentScope).getCatalogueUrl(); }catch(Exception e){ logger.error("Failed to retrieve catalogue url information", e); } @@ -92,7 +95,6 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C } return null; - } /** @@ -300,7 +302,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C } @Override - public boolean createCKanDataset(DatasetMetadataBean toCreate) { + public String createCKanDataset(DatasetMetadataBean toCreate) { // retrieve ckan's catalog url String ckanPortalUrl = getCatalogueUrl(); @@ -334,10 +336,6 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C dataset.setMaintainerEmail(toCreate.getMaintainerEmail()); dataset.setVersion(String.valueOf(toCreate.getVersion())); dataset.setNotes(toCreate.getDescription()); - - logger.info("Searchable is " + toCreate.isSearchable() + " and visible is " + toCreate.getVisibility()); - - dataset.setPriv(false); dataset.setOpen(toCreate.getVisibility()); // iterate over the licenses to find the id of the chosen one @@ -431,10 +429,10 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C logger.debug("Dataset created/updated " + res.getId()); else{ logger.error("Dataset described by " + toCreate + " not created!"); - return false; + return null; } - return true; + return res.getId(); } /** @@ -450,4 +448,66 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C return convertedName; } + + /** + * Utility methods + * @param URLName + * @return + */ + private static boolean exists(String URLName){ + try { + HttpURLConnection.setFollowRedirects(true); + HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection(); + con.setRequestMethod("HEAD"); + logger.info("Return code is " + con.getResponseCode()); + return (con.getResponseCode() == HttpURLConnection.HTTP_OK); + } + catch (Exception e) { + logger.error("Exception while checking url", e); + return false; + } + } + + @Override + public boolean addResourceToDataset(ResourceBean resourceBean, String datasetId, String owner) { + + logger.info("Incoming request for creating new resource for dataset with id " + datasetId); + logger.info("Owner is " + owner + " and resource is " + resourceBean); + + // of course, if it exists + if(exists(resourceBean.getUrl())){ + + try{ + // retrieve ckan's catalog url + String ckanPortalUrl = getCatalogueUrl(); + + // retrieve the api key for this user + String apiKey = getCKANApikeyFromUser(owner); + + CkanResource resource = new CkanResource(ckanPortalUrl, datasetId); + resource.setName(resourceBean.getName()); + resource.setDescription(resourceBean.getDescription()); + resource.setUrl(resourceBean.getUrl()); + resource.setOwner(owner); + + // Checked client + CheckedCkanClient client = new CheckedCkanClient(ckanPortalUrl, apiKey); + CkanResource createdRes = client.createResource(resource); + + if(createdRes != null){ + + logger.info("Resource " + createdRes.getName() + " is now available"); + return true; + + } + + }catch(Exception e){ + logger.error("Unable to create new resource", e); + } + } + + logger.info("No resource created"); + + return false; + } } diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java index f8373d0..66084fb 100644 --- a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/DatasetMetadataBean.java @@ -13,7 +13,6 @@ import java.util.Map; *
  • Description -> folders' description *
  • tags -> folder's custom fields keys' names *
  • visibility -> as chosen by the creator (visible = true, not visible = false) - *
  • searchable -> as chosen by the creator *
  • source -> url of the folder within the workspace *
  • version -> during creation it is going to be 1.0 *
  • author, maintainer -> folder's owner @@ -34,7 +33,6 @@ public class DatasetMetadataBean implements Serializable { List tags; // on retrieve, they are the keys of the custom fields private String license; // chosen by the user private boolean visibility; // Private (false) or Public(true) - private boolean searchable; // true or false private String source; // url of the folder in the workspace private long version; // version 1, 2 ... private String author; // folder's owner fullname @@ -46,7 +44,7 @@ public class DatasetMetadataBean implements Serializable { private String selectedOrganization; private List resourcesIds; // in case of workspace, this is the list of children's ids private boolean addResources; // if true, add these files as resources to the dataset - + public DatasetMetadataBean(){ super(); } @@ -59,7 +57,6 @@ public class DatasetMetadataBean implements Serializable { * @param tags * @param license * @param visibility - * @param searchable * @param source * @param version * @param author @@ -72,7 +69,7 @@ public class DatasetMetadataBean implements Serializable { */ public DatasetMetadataBean(String id, String title, String description, Map customFields, List tags, - String license, boolean visibility, boolean searchable, + String license, boolean visibility, String source, long version, String author, String authorEmail, String maintainer, String maintainerEmail, String ownerFolderInWorkspace, List organizationList, @@ -85,7 +82,6 @@ public class DatasetMetadataBean implements Serializable { this.tags = tags; this.license = license; this.visibility = visibility; - this.searchable = searchable; this.source = source; this.version = version; this.author = author; @@ -161,14 +157,6 @@ public class DatasetMetadataBean implements Serializable { this.visibility = visibility; } - public boolean isSearchable() { - return searchable; - } - - public void setSearchable(boolean searchable) { - this.searchable = searchable; - } - public String getSource() { return source; } @@ -254,7 +242,7 @@ public class DatasetMetadataBean implements Serializable { return "DatasetMetadataBean [id=" + id + ", title=" + title + ", description=" + description + ", customFields=" + customFields + ", tags=" + tags + ", license=" + license - + ", visibility=" + visibility + ", searchable=" + searchable + + ", visibility=" + visibility + ", source=" + source + ", version=" + version + ", author=" + author + ", authorEmail=" + authorEmail + ", maintainer=" + maintainer + ", maintainerEmail=" + maintainerEmail diff --git a/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/ResourceBean.java b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/ResourceBean.java new file mode 100644 index 0000000..6ce3c1e --- /dev/null +++ b/src/main/java/org/gcube/portlets/widgets/ckandatapublisherwidget/shared/ResourceBean.java @@ -0,0 +1,108 @@ +package org.gcube.portlets.widgets.ckandatapublisherwidget.shared; + +import java.io.Serializable; + +/** + * A dataset's resource bean. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class ResourceBean implements Serializable{ + + private static final long serialVersionUID = -6542455246456049712L; + private String url; + private String name; + private String description; + private String id; + + public ResourceBean(){ + super(); + } + + /** + * @param url + * @param name + * @param description + */ + public ResourceBean(String url, String name, String description) { + super(); + this.url = url; + this.name = name; + this.description = description; + } + + /** + * @param url + * @param name + * @param description + * @param id + */ + public ResourceBean(String url, String name, String description, String id) { + super(); + this.url = url; + this.name = name; + this.description = description; + this.id = id; + } + + /** + * @return the url + */ + public String getUrl() { + return url; + } + + /** + * @param url the url to set + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the description + */ + public String getDescription() { + return description; + } + + /** + * @param description the description to set + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "ResourceBean [url=" + url + ", name=" + name + ", description=" + + description + ", id=" + id + "]"; + } +} diff --git a/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml b/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml index 2c54a6b..fa37e4f 100644 --- a/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml +++ b/src/main/resources/org/gcube/portlets/widgets/ckandatapublisherwidget/CKanMetadataPublisher.gwt.xml @@ -19,4 +19,7 @@ + + + diff --git a/src/main/webapp/CKanMetadataPublisher.html b/src/main/webapp/CKanMetadataPublisher.html index f0b17e1..7fa2fb4 100644 --- a/src/main/webapp/CKanMetadataPublisher.html +++ b/src/main/webapp/CKanMetadataPublisher.html @@ -5,6 +5,8 @@ + diff --git a/src/main/webapp/js/showdown.js b/src/main/webapp/js/showdown.js new file mode 100644 index 0000000..7f8d073 --- /dev/null +++ b/src/main/webapp/js/showdown.js @@ -0,0 +1,325 @@ +/** + * Created by Tivie on 06-01-2015. + */ + +// Private properties +var showdown = {}, + parsers = {}, + extensions = {}, + globalOptions = getDefaultOpts(true), + flavor = { + github: { + omitExtraWLInCodeBlocks: true, + prefixHeaderId: 'user-content-', + simplifiedAutoLink: true, + literalMidWordUnderscores: true, + strikethrough: true, + tables: true, + tablesHeaderId: true, + ghCodeBlocks: true, + tasklists: true + }, + vanilla: getDefaultOpts(true) + }; + +/** + * helper namespace + * @type {{}} + */ +showdown.helper = {}; + +/** + * TODO LEGACY SUPPORT CODE + * @type {{}} + */ +showdown.extensions = {}; + +/** + * Set a global option + * @static + * @param {string} key + * @param {*} value + * @returns {showdown} + */ +showdown.setOption = function (key, value) { + 'use strict'; + globalOptions[key] = value; + return this; +}; + +/** + * Get a global option + * @static + * @param {string} key + * @returns {*} + */ +showdown.getOption = function (key) { + 'use strict'; + return globalOptions[key]; +}; + +/** + * Get the global options + * @static + * @returns {{}} + */ +showdown.getOptions = function () { + 'use strict'; + return globalOptions; +}; + +/** + * Reset global options to the default values + * @static + */ +showdown.resetOptions = function () { + 'use strict'; + globalOptions = getDefaultOpts(true); +}; + +/** + * Set the flavor showdown should use as default + * @param {string} name + */ +showdown.setFlavor = function (name) { + 'use strict'; + if (flavor.hasOwnProperty(name)) { + var preset = flavor[name]; + for (var option in preset) { + if (preset.hasOwnProperty(option)) { + globalOptions[option] = preset[option]; + } + } + } +}; + +/** + * Get the default options + * @static + * @param {boolean} [simple=true] + * @returns {{}} + */ +showdown.getDefaultOptions = function (simple) { + 'use strict'; + return getDefaultOpts(simple); +}; + +/** + * Get or set a subParser + * + * subParser(name) - Get a registered subParser + * subParser(name, func) - Register a subParser + * @static + * @param {string} name + * @param {function} [func] + * @returns {*} + */ +showdown.subParser = function (name, func) { + 'use strict'; + if (showdown.helper.isString(name)) { + if (typeof func !== 'undefined') { + parsers[name] = func; + } else { + if (parsers.hasOwnProperty(name)) { + return parsers[name]; + } else { + throw Error('SubParser named ' + name + ' not registered!'); + } + } + } +}; + +/** + * Gets or registers an extension + * @static + * @param {string} name + * @param {object|function=} ext + * @returns {*} + */ +showdown.extension = function (name, ext) { + 'use strict'; + + if (!showdown.helper.isString(name)) { + throw Error('Extension \'name\' must be a string'); + } + + name = showdown.helper.stdExtName(name); + + // Getter + if (showdown.helper.isUndefined(ext)) { + if (!extensions.hasOwnProperty(name)) { + throw Error('Extension named ' + name + ' is not registered!'); + } + return extensions[name]; + + // Setter + } else { + // Expand extension if it's wrapped in a function + if (typeof ext === 'function') { + ext = ext(); + } + + // Ensure extension is an array + if (!showdown.helper.isArray(ext)) { + ext = [ext]; + } + + var validExtension = validate(ext, name); + + if (validExtension.valid) { + extensions[name] = ext; + } else { + throw Error(validExtension.error); + } + } +}; + +/** + * Gets all extensions registered + * @returns {{}} + */ +showdown.getAllExtensions = function () { + 'use strict'; + return extensions; +}; + +/** + * Remove an extension + * @param {string} name + */ +showdown.removeExtension = function (name) { + 'use strict'; + delete extensions[name]; +}; + +/** + * Removes all extensions + */ +showdown.resetExtensions = function () { + 'use strict'; + extensions = {}; +}; + +/** + * Validate extension + * @param {array} extension + * @param {string} name + * @returns {{valid: boolean, error: string}} + */ +function validate(extension, name) { + 'use strict'; + + var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension', + ret = { + valid: true, + error: '' + }; + + if (!showdown.helper.isArray(extension)) { + extension = [extension]; + } + + for (var i = 0; i < extension.length; ++i) { + var baseMsg = errMsg + ' sub-extension ' + i + ': ', + ext = extension[i]; + if (typeof ext !== 'object') { + ret.valid = false; + ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given'; + return ret; + } + + if (!showdown.helper.isString(ext.type)) { + ret.valid = false; + ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given'; + return ret; + } + + var type = ext.type = ext.type.toLowerCase(); + + // normalize extension type + if (type === 'language') { + type = ext.type = 'lang'; + } + + if (type === 'html') { + type = ext.type = 'output'; + } + + if (type !== 'lang' && type !== 'output' && type !== 'listener') { + ret.valid = false; + ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"'; + return ret; + } + + if (type === 'listener') { + if (showdown.helper.isUndefined(ext.listeners)) { + ret.valid = false; + ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"'; + return ret; + } + } else { + if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) { + ret.valid = false; + ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method'; + return ret; + } + } + + if (ext.listeners) { + if (typeof ext.listeners !== 'object') { + ret.valid = false; + ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given'; + return ret; + } + for (var ln in ext.listeners) { + if (ext.listeners.hasOwnProperty(ln)) { + if (typeof ext.listeners[ln] !== 'function') { + ret.valid = false; + ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln + + ' must be a function but ' + typeof ext.listeners[ln] + ' given'; + return ret; + } + } + } + } + + if (ext.filter) { + if (typeof ext.filter !== 'function') { + ret.valid = false; + ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given'; + return ret; + } + } else if (ext.regex) { + if (showdown.helper.isString(ext.regex)) { + ext.regex = new RegExp(ext.regex, 'g'); + } + if (!ext.regex instanceof RegExp) { + ret.valid = false; + ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given'; + return ret; + } + if (showdown.helper.isUndefined(ext.replace)) { + ret.valid = false; + ret.error = baseMsg + '"regex" extensions must implement a replace string or function'; + return ret; + } + } + } + return ret; +} + +/** + * Validate extension + * @param {object} ext + * @returns {boolean} + */ +showdown.validateExtension = function (ext) { + 'use strict'; + + var validateExtension = validate(ext, null); + if (!validateExtension.valid) { + console.warn(validateExtension.error); + return false; + } + return true; +};