diff --git a/doc/release-notes/7263-banner-messages.md b/doc/release-notes/7263-banner-messages.md new file mode 100644 index 00000000000..f53f6879199 --- /dev/null +++ b/doc/release-notes/7263-banner-messages.md @@ -0,0 +1,7 @@ +## Major Use Cases + +- New API for Banners.. + +## Notes to Admins + +The functionality previously provided by the DB settings :StatusMessageHeader and ::StatusMessageText is no longer supported and is now provided through the Manage Banner Messages API. Learn more in the [Native API Guide](https://guides.dataverse.org/en/5.x/api/). diff --git a/doc/release-notes/7275-aux-files.md b/doc/release-notes/7275-aux-files.md new file mode 100644 index 00000000000..24aa7f86f2d --- /dev/null +++ b/doc/release-notes/7275-aux-files.md @@ -0,0 +1,3 @@ +## Notes for Tool Developers and Integrators + +Experimental endpoints have been added to allow auxiliary files to be added to datafiles. These auxiliary files can be deposited and accessed via API. Later releases will include options for accessing these files through the UI. For more information, see the Auxiliary File Support section of the [Developer Guide](https://guides.dataverse.org/en/5.3/developers/). diff --git a/doc/release-notes/7418-datasourcedefinition.md b/doc/release-notes/7418-datasourcedefinition.md new file mode 100644 index 00000000000..b8c5561c2c2 --- /dev/null +++ b/doc/release-notes/7418-datasourcedefinition.md @@ -0,0 +1,88 @@ +## Release Highlights + +### Easier Configuration of Database Connections + +Dataverse now being able to use up-to-date Java technologies, transforms +the way how to configure the connection to your PostgreSQL database. + +In the past, the configuration of the connection has been quite static +and not very easy to update. This has been an issue especially for cloud +and container usage. + +Using MicroProfile Config API (#7000, #7418), you can much more easily specify configuration +details. For an overview of supported options, please see the +[installation guide](https://guides.dataverse.org/en/5.3/installation/config.html#jvm-options). + +Note that some settings have been moved from domain.xml to code such as min and max pool size. + +## Notes for Dataverse Installation Administrators + +### New JVM Options + +- dataverse.db.name +- dataverse.db.user +- dataverse.db.password +- dataverse.db.host +- dataverse.db.port + + + + + +🚨 THIS VERSION OF DATAVERSE **REQUIRES** UPGRADING TO PAYARA 5.2020.6. 🚨 + + + +## Upgrading from earlier releases + +ℹ️ You need to update the Payara Application Server before continuing here. See above. + +1. Undeploy the previous version. +``` +/asadmin list-applications +/asadmin undeploy dataverse- +``` + +(where `` is where Payara 5 is installed, for example: `/usr/local/payara5`) + +2. Update your database connection before updating. + +Please configure your connection details, replacing all the `${DB_...}`. +(If you are using a PostgreSQL server on `localhost:5432`, you can omit `dataverse.db.host` and `dataverse.db.port`.) + +``` +/asadmin create-system-properties "dataverse.db.user=${DB_USER}" +/asadmin create-system-properties "dataverse.db.host=${DB_HOST}" +/asadmin create-system-properties "dataverse.db.port=${DB_PORT}" +/asadmin create-system-properties "dataverse.db.name=${DB_NAME}" +echo "AS_ADMIN_ALIASPASSWORD=${DB_PASS}" > /tmp/password.txt +/asadmin create-password-alias --passwordfile /tmp/password.txt dataverse.db.password +rm /tmp/password.txt +``` + + + +Now you are safe to delete the old password alias and DB pool: +``` +/asadmin delete-jdbc-connection-pool --cascade=true dvnDbPool +/asadmin delete-password-alias db_password_alias +``` + +3. Stop payara and remove the generated directory, start. +``` +service payara stop +# remove the generated directory: +rm -rf /payara/domains/domain1/generated +service payara start +``` + +3. Deploy this version. +``` +/bin/asadmin deploy dataverse-5.3.war +``` + +4. Restart Payara +``` +service payara stop +service payara start +``` diff --git a/doc/sphinx-guides/source/api/dataaccess.rst b/doc/sphinx-guides/source/api/dataaccess.rst index 690445852b9..3c20233f6e6 100755 --- a/doc/sphinx-guides/source/api/dataaccess.rst +++ b/doc/sphinx-guides/source/api/dataaccess.rst @@ -40,13 +40,13 @@ A curl example using a DOI (no version): export SERVER_URL=https://demo.dataverse.org export PERSISTENT_ID=doi:10.70122/FK2/N2XGBJ - curl -O -J -H "X-Dataverse-key:$API_TOKEN" $SERVER_URL/api/access/dataset/:persistentId/?persistentId=$PERSISTENT_ID + curl -L -O -J -H "X-Dataverse-key:$API_TOKEN" $SERVER_URL/api/access/dataset/:persistentId/?persistentId=$PERSISTENT_ID The fully expanded example above (without environment variables) looks like this: .. code-block:: bash - curl -O -J -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx https://demo.dataverse.org/api/access/dataset/:persistentId/?persistentId=doi:10.70122/FK2/N2XGBJ + curl -L -O -J -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx https://demo.dataverse.org/api/access/dataset/:persistentId/?persistentId=doi:10.70122/FK2/N2XGBJ Download By Dataset By Version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 6d0176c094d..c9301ae5999 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -3,7 +3,7 @@ Native API Dataverse 4 exposes most of its GUI functionality via a REST-based API. This section describes that functionality. Most API endpoints require an API token that can be passed as the ``X-Dataverse-key`` HTTP header or in the URL as the ``key`` query parameter. -.. note:: |CORS| Some API endpoint allow CORS_ (cross-origin resource sharing), which makes them usable from scripts runing in web browsers. These endpoints are marked with a *CORS* badge. +.. note:: |CORS| Some API endpoint allow CORS_ (cross-origin resource sharing), which makes them usable from scripts running in web browsers. These endpoints are marked with a *CORS* badge. .. note:: Bash environment variables shown below. The idea is that you can "export" these environment variables before copying and pasting the commands that use them. For example, you can set ``$SERVER_URL`` by running ``export SERVER_URL="https://demo.dataverse.org"`` in your Bash shell. To check if the environment variable was set properly, you can "echo" it (e.g. ``echo $SERVER_URL``). See also :ref:`curl-examples-and-environment-variables`. @@ -2664,6 +2664,48 @@ Delete Database Setting Delete the setting under ``name``:: DELETE http://$SERVER/api/admin/settings/$name + +Manage Banner Messages +~~~~~~~~~~~~~~~~~~~~~~ + +Communications to users can be handled via banner messages that are displayed at the top of all pages within your Dataverse installation. Two types of banners can be configured: + +- A banner message where dismissibleByUser is set to false will be displayed to anyone viewing the application. These messages will be dismissible for a given session but will be displayed in any subsequent session until they are deleted by the Admin. This type of banner message is useful for situations such as upcoming maintenance windows and other downtime. +- A banner message where dismissibleByUser is set to true is intended to be used in situations where the Admin wants to make sure that all logged in users see a certain notification. These banner messages will only be displayed to users when they are logged in and can be dismissed by the logged in user. Once they have been dismissed by a user, that user will not see the message again. This type of banner message is useful for situations where a message needs to communicated once, such as a minor terms of use update or an update about a new workflow in your Dataverse installation. + +Note that HTML can be included in banner messages. + +Add a Banner Message:: + + curl -H "Content-type:application/json" -X POST http://$SERVER/api/admin/bannerMessage --upload-file messages.json + +Where ``messages.json`` looks like this:: + + { + "dismissibleByUser": "true", + "messageTexts": [ + { + "lang": "en", + "message": "Dismissible Banner Message added via API" + }, + { + "lang": "fr", + "message": "Message de bannière ajouté via l'API" + } + ] + } + +Get a list of active Banner Messages:: + + curl -X GET http://$SERVER/api/admin/bannerMessage + +Delete a Banner Message by its id:: + + curl -X DELETE http://$SERVER/api/admin/bannerMessage/$id + +Deactivate a Banner Message by its id (allows you to hide a message while retaining information about which users have dismissed the banner):: + + curl -X PUT http://$SERVER/api/admin/bannerMessage/$id/deactivate List Authentication Provider Factories ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/sphinx-guides/source/developers/aux-file-support.rst b/doc/sphinx-guides/source/developers/aux-file-support.rst new file mode 100644 index 00000000000..be21b56c245 --- /dev/null +++ b/doc/sphinx-guides/source/developers/aux-file-support.rst @@ -0,0 +1,36 @@ +Auxiliary File Support +====================== + +Auxiliary file support is experimental. Auxiliary files in Dataverse are being added to support depositing and downloading differentially private metadata, as part of the OpenDP project (OpenDP.io). In future versions, this approach may become more broadly used and supported. + +Adding an Auxiliary File to a Datafile +-------------------------------------- +To add an auxiliary file, specify the primary key of the datafile (FILE_ID), and the formatTag and formatVersion (if applicable) associated with the auxiliary file. There are two form parameters. "Origin" specifies the application/entity that created the auxiliary file, an "isPublic" controls access to downloading the file. If "isPublic" is true, any user can download the file, else, access authorization is based on the access rules as defined for the DataFile itself. + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export FILENAME='auxfile.txt' + export FILE_ID='12345' + export FORMAT_TAG='dpJson' + export FORMAT_VERSION='v1' + export SERVER_URL=https://demo.dataverse.org + + curl -H X-Dataverse-key:$API_TOKEN -X POST -F "file=@$FILENAME" -F 'origin=myApp' -F 'isPublic=true' "$SERVER_URL/api/access/datafile/$FILE_ID/metadata/$FORMAT_TAG/$FORMAT_VERSION" + +You should expect a 200 ("OK") response and JSON with information about your newly uploaded auxiliary file. + +Downloading an Auxiliary File that belongs to a Datafile +-------------------------------------------------------- +To download an auxiliary file, use the primary key of the datafile, and the +formatTag and formatVersion (if applicable) associated with the auxiliary file: + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export FILE_ID='12345' + export FORMAT_TAG='dpJson' + export FORMAT_VERSION='v1' + + curl "$SERVER_URL/api/access/datafile/$FILE_ID/$FORMAT_TAG/$FORMAT_VERSION" diff --git a/doc/sphinx-guides/source/developers/index.rst b/doc/sphinx-guides/source/developers/index.rst index 96595220e07..9c524571a39 100755 --- a/doc/sphinx-guides/source/developers/index.rst +++ b/doc/sphinx-guides/source/developers/index.rst @@ -32,4 +32,5 @@ Developer Guide geospatial selinux big-data-support + aux-file-support workflows diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index abf710e42e0..4a877eabff7 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -1032,6 +1032,57 @@ dataverse.auth.password-reset-timeout-in-minutes Users have 60 minutes to change their passwords by default. You can adjust this value here. +dataverse.db.name ++++++++++++++++++ + +The PostgreSQL database name to use for Dataverse. + +Defaults to ``dataverse`` (but the installer sets it to ``dvndb``). + +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_DB_NAME``. + +dataverse.db.user ++++++++++++++++++ + +The PostgreSQL user name to connect with. + +Defaults to ``dataverse`` (but the installer sets it to ``dvnapp``). + +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_DB_USER``. + +dataverse.db.password ++++++++++++++++++++++ + +The PostgreSQL users password to connect with. + +Preferrably use a JVM alias, as passwords in environment variables aren't safe. + +.. code-block:: shell + + echo "AS_ADMIN_ALIASPASSWORD=change-me-super-secret" > /tmp/password.txt + asadmin create-password-alias --passwordfile /tmp/password.txt dataverse.db.password + rm /tmp/password.txt + +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_DB_PASSWORD``. + +dataverse.db.host ++++++++++++++++++ + +The PostgreSQL server to connect to. + +Defaults to ``localhost``. + +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_DB_HOST``. + +dataverse.db.port ++++++++++++++++++ + +The PostgreSQL server port to connect to. + +Defaults to ``5432``, the default PostgreSQL port. + +Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_DB_PORT``. + dataverse.rserve.host +++++++++++++++++++++ @@ -1168,7 +1219,7 @@ This JVM setting is also part of **handles** configuration. The Handle.Net insta .. _dataverse.handlenet.index: dataverse.handlenet.index -+++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++ If you want to use different index than the default 300 .. _dataverse.timerServer: @@ -1594,22 +1645,6 @@ Make the metrics component on the root dataverse a clickable link to a website w ``curl -X PUT -d http://metrics.dataverse.example.edu http://localhost:8080/api/admin/settings/:MetricsUrl`` -:StatusMessageHeader -++++++++++++++++++++ - -For dynamically adding an informational header to the top of every page. StatusMessageText must also be set for a message to show. For example, "For testing only..." at the top of https://demo.dataverse.org is set with this: - -``curl -X PUT -d "For testing only..." http://localhost:8080/api/admin/settings/:StatusMessageHeader`` - -You can make the text clickable and include an additional message in a pop up by setting ``:StatusMessageText``. - -:StatusMessageText -++++++++++++++++++ - -Alongside the ``:StatusMessageHeader`` you need to add StatusMessageText for the message to show.: - -``curl -X PUT -d "This appears in a popup." http://localhost:8080/api/admin/settings/:StatusMessageText`` - .. _:MaxFileUploadSizeInBytes: :MaxFileUploadSizeInBytes @@ -1666,7 +1701,9 @@ For example, if you want your installation of Dataverse to not attempt to ingest :ZipUploadFilesLimit ++++++++++++++++++++ -Limit the number of files in a zip that Dataverse will accept. +Limit the number of files in a zip that Dataverse will accept. In the absence of this setting, Dataverse defaults to a limit of 1,000 files per zipfile. + +``curl -X PUT -d 2048 http://localhost:8080/api/admin/settings/:ZipUploadFilesLimit`` :SolrHostColonPort ++++++++++++++++++ diff --git a/pom.xml b/pom.xml index bdd60965137..6d7378a7ac2 100644 --- a/pom.xml +++ b/pom.xml @@ -685,6 +685,7 @@ **/*.xml **/firstNames/*.* **/*.xsl + **/*.properties diff --git a/scripts/api/data/bannerMessageError.json b/scripts/api/data/bannerMessageError.json new file mode 100644 index 00000000000..9c9abfe3c27 --- /dev/null +++ b/scripts/api/data/bannerMessageError.json @@ -0,0 +1,9 @@ +{ + "dismissible": "false", + "messageTexts": [ + { + "lang": "en", + "text": "Invalid json" + } + ] +} diff --git a/scripts/api/data/bannerMessageSample.json b/scripts/api/data/bannerMessageSample.json new file mode 100644 index 00000000000..7b2c7cc39e2 --- /dev/null +++ b/scripts/api/data/bannerMessageSample.json @@ -0,0 +1,13 @@ +{ + "dismissibleByUser": "false", + "messageTexts": [ + { + "lang": "en", + "message": "Banner Message added via API" + }, + { + "lang": "fr", + "message": "Message de bannière ajouté via l'API" + } + ] +} diff --git a/scripts/api/data/bannerMessageTest.json b/scripts/api/data/bannerMessageTest.json new file mode 100644 index 00000000000..e089aead5f3 --- /dev/null +++ b/scripts/api/data/bannerMessageTest.json @@ -0,0 +1,13 @@ +{ + "dismissibleByUser": "false", + "messageTexts": [ + { + "lang": "en", + "message": "Banner Message For Deletion" + }, + { + "lang": "fr", + "message": "Banner Message For Deletion" + } + ] +} \ No newline at end of file diff --git a/scripts/installer/as-setup.sh b/scripts/installer/as-setup.sh index f80193df46d..853db77f471 100755 --- a/scripts/installer/as-setup.sh +++ b/scripts/installer/as-setup.sh @@ -73,7 +73,7 @@ function preliminary_setup() ./asadmin $ASADMIN_OPTS create-jvm-options "-XX\:+DisableExplicitGC" # alias passwords - for alias in "rserve_password_alias ${RSERVE_PASS}" "doi_password_alias ${DOI_PASSWORD}" "db_password_alias ${DB_PASS}" + for alias in "rserve_password_alias ${RSERVE_PASS}" "doi_password_alias ${DOI_PASSWORD}" "dataverse.db.password ${DB_PASS}" do set -- $alias echo "AS_ADMIN_ALIASPASSWORD=$2" > /tmp/$1.txt @@ -130,17 +130,11 @@ function final_setup(){ ./asadmin $ASADMIN_OPTS delete-jvm-options -Xmx512m ./asadmin $ASADMIN_OPTS create-jvm-options "-Xmx${MEM_HEAP_SIZE}m" - - ./asadmin $ASADMIN_OPTS create-jdbc-connection-pool --restype javax.sql.DataSource \ - --datasourceclassname org.postgresql.ds.PGPoolingDataSource \ - --property create=true:User=$DB_USER:PortNumber=$DB_PORT:databaseName=$DB_NAME:ServerName=$DB_HOST \ - dvnDbPool - - ./asadmin $ASADMIN_OPTS set resources.jdbc-connection-pool.dvnDbPool.property.password='${ALIAS=db_password_alias}' - - ### - # Create data sources - ./asadmin $ASADMIN_OPTS create-jdbc-resource --connectionpoolid dvnDbPool jdbc/VDCNetDS + # Set up the database connection properties + ./asadmin $ASADMIN_OPTS create-system-properties "dataverse.db.user=${DB_USER}" + ./asadmin $ASADMIN_OPTS create-system-properties "dataverse.db.host=${DB_HOST}" + ./asadmin $ASADMIN_OPTS create-system-properties "dataverse.db.port=${DB_PORT}" + ./asadmin $ASADMIN_OPTS create-system-properties "dataverse.db.name=${DB_NAME}" ./asadmin $ASADMIN_OPTS create-jvm-options "\-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" diff --git a/src/main/java/edu/harvard/iq/dataverse/AuxiliaryFile.java b/src/main/java/edu/harvard/iq/dataverse/AuxiliaryFile.java new file mode 100644 index 00000000000..957a7cc93bf --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/AuxiliaryFile.java @@ -0,0 +1,120 @@ + +package edu.harvard.iq.dataverse; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * + * @author ekraffmiller + * Represents a generic file that is associated with a dataFile. + * This is a data representation of a physical file in StorageIO + */ +@Entity +public class AuxiliaryFile implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * The data file that this AuxiliaryFile belongs to + * a data file may have many auxiliaryFiles + */ + @ManyToOne + @JoinColumn(nullable=false) + private DataFile dataFile; + + private String formatTag; + + private String formatVersion; + + private String origin; + + private boolean isPublic; + + private String contentType; + + private Long fileSize; + + private String checksum; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public DataFile getDataFile() { + return dataFile; + } + + public void setDataFile(DataFile dataFile) { + this.dataFile = dataFile; + } + + public String getFormatTag() { + return formatTag; + } + + public void setFormatTag(String formatTag) { + this.formatTag = formatTag; + } + + public String getFormatVersion() { + return formatVersion; + } + + public void setFormatVersion(String formatVersion) { + this.formatVersion = formatVersion; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public boolean getIsPublic() { + return isPublic; + } + + public void setIsPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + public String getContentType() { + return this.contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public Long getFileSize() { + return fileSize; + } + + public void setFileSize(long fileSize) { + this.fileSize = fileSize; + } + + public String getChecksum() { + return checksum; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/AuxiliaryFileServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/AuxiliaryFileServiceBean.java new file mode 100644 index 00000000000..4f97c146e7b --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/AuxiliaryFileServiceBean.java @@ -0,0 +1,117 @@ + +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.dataaccess.StorageIO; +import edu.harvard.iq.dataverse.util.FileUtil; +import edu.harvard.iq.dataverse.util.SystemConfig; +import java.io.IOException; +import java.io.InputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.inject.Named; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import org.apache.tika.Tika; + +/** + * + * @author ekraffmiller + * Methods related to the AuxiliaryFile Entity. + */ +@Stateless +@Named +public class AuxiliaryFileServiceBean implements java.io.Serializable { + private static final Logger logger = Logger.getLogger(AuxiliaryFileServiceBean.class.getCanonicalName()); + + @PersistenceContext(unitName = "VDCNet-ejbPU") + private EntityManager em; + + @EJB + private SystemConfig systemConfig; + + + public AuxiliaryFile find(Object pk) { + return em.find(AuxiliaryFile.class, pk); + } + + public AuxiliaryFile save(AuxiliaryFile auxiliaryFile) { + AuxiliaryFile savedFile = em.merge(auxiliaryFile); + return savedFile; + + } + + /** + * Save the physical file to storageIO, and save the AuxiliaryFile entity + * to the database. This should be an all or nothing transaction - if either + * process fails, than nothing will be saved + * @param fileInputStream - auxiliary file data to be saved + * @param dataFile - the dataFile entity this will be added to + * @param formatTag - type of file being saved + * @param formatVersion - to distinguish between multiple versions of a file + * @param origin - name of the tool/system that created the file + * @param isPublic boolean - is this file available to any user? + * @return success boolean - returns whether the save was successful + */ + public AuxiliaryFile processAuxiliaryFile(InputStream fileInputStream, DataFile dataFile, String formatTag, String formatVersion, String origin, boolean isPublic) { + + StorageIO storageIO =null; + AuxiliaryFile auxFile = new AuxiliaryFile(); + String auxExtension = formatTag + "_" + formatVersion; + try { + // Save to storage first. + // If that is successful (does not throw exception), + // then save to db. + // If the db fails for any reason, then rollback + // by removing the auxfile from storage. + storageIO = dataFile.getStorageIO(); + MessageDigest md = MessageDigest.getInstance(systemConfig.getFileFixityChecksumAlgorithm().toString()); + DigestInputStream di + = new DigestInputStream(fileInputStream, md); + + storageIO.saveInputStreamAsAux(fileInputStream, auxExtension); + auxFile.setChecksum(FileUtil.checksumDigestToString(di.getMessageDigest().digest()) ); + + Tika tika = new Tika(); + auxFile.setContentType(tika.detect(storageIO.getAuxFileAsInputStream(auxExtension))); + auxFile.setFormatTag(formatTag); + auxFile.setFormatVersion(formatVersion); + auxFile.setOrigin(origin); + auxFile.setIsPublic(isPublic); + auxFile.setDataFile(dataFile); + auxFile.setFileSize(storageIO.getAuxObjectSize(auxExtension)); + auxFile = save(auxFile); + } catch (IOException ioex) { + logger.info("IO Exception trying to save auxiliary file: " + ioex.getMessage()); + return null; + } catch (Exception e) { + // If anything fails during database insert, remove file from storage + try { + storageIO.deleteAuxObject(auxExtension); + } catch(IOException ioex) { + logger.info("IO Exception trying remove auxiliary file in exception handler: " + ioex.getMessage()); + return null; + } + } + return auxFile; + } + + public AuxiliaryFile lookupAuxiliaryFile(DataFile dataFile, String formatTag, String formatVersion) { + + Query query = em.createQuery("select object(o) from AuxiliaryFile as o where o.dataFile.id = :dataFileId and o.formatTag = :formatTag and o.formatVersion = :formatVersion"); + + query.setParameter("dataFileId", dataFile.getId()); + query.setParameter("formatTag", formatTag); + query.setParameter("formatVersion", formatVersion); + try { + AuxiliaryFile retVal = (AuxiliaryFile)query.getSingleResult(); + return retVal; + } catch(Exception ex) { + return null; + } + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/BannerMessage.java b/src/main/java/edu/harvard/iq/dataverse/BannerMessage.java new file mode 100644 index 00000000000..4f465168580 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/BannerMessage.java @@ -0,0 +1,107 @@ + +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.util.BundleUtil; +import java.io.Serializable; +import java.util.Collection; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + + +/** + * + * @author skraffmi + */ +@Entity +public class BannerMessage implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column + private boolean dismissibleByUser; + + @Column + private boolean active; + + @OneToMany(mappedBy = "bannerMessage", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}) + private Collection bannerMessageTexts; + + @OneToMany(mappedBy = "bannerMessage", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}) + private Collection userBannerMessages; + + public Collection getBannerMessageTexts() { + return this.bannerMessageTexts; + } + + public void setBannerMessageTexts(Collection bannerMessageTexts) { + this.bannerMessageTexts = bannerMessageTexts; + } + + + public String getDisplayValue(){ + String retVal = ""; + for (BannerMessageText msgTxt : this.getBannerMessageTexts()) { + if (msgTxt.getLang().equals(BundleUtil.getCurrentLocale().getLanguage())) { + retVal = msgTxt.getMessage(); + } + } + return retVal; + } + + public boolean isDismissibleByUser() { + return dismissibleByUser; + } + + public void setDismissibleByUser(boolean dismissibleByUser) { + this.dismissibleByUser = dismissibleByUser; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (id != null ? id.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof BannerMessage)) { + return false; + } + BannerMessage other = (BannerMessage) object; + if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { + return false; + } + return true; + } + + @Override + public String toString() { + return "edu.harvard.iq.dataverse.BannerMessage[ id=" + id + " ]"; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/BannerMessageServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/BannerMessageServiceBean.java new file mode 100644 index 00000000000..91b4128c545 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/BannerMessageServiceBean.java @@ -0,0 +1,79 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; +import javax.ejb.Stateless; +import javax.inject.Named; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +/** + * + * @author skraffmi + */ +@Stateless +@Named +public class BannerMessageServiceBean implements java.io.Serializable { + private static final Logger logger = Logger.getLogger(BannerMessageServiceBean.class.getCanonicalName()); + + @PersistenceContext(unitName = "VDCNet-ejbPU") + private EntityManager em; + + public List findBannerMessages() { + return em.createQuery("select object(o) from BannerMessage as o where o.active = 'true' and o.dismissibleByUser = 'false'", BannerMessage.class) + .getResultList(); + } + + public List findBannerMessages(Long auId) { + return em.createQuery("select object(o) from BannerMessage as o where (o.active = 'true' and o.dismissibleByUser = 'false') " + + " or (o.active = 'true' and o.dismissibleByUser = 'true' and o.id not in (select ubm.bannerMessage.id from UserBannerMessage as ubm where ubm.user.id =:authenticatedUserId))", BannerMessage.class) + .setParameter("authenticatedUserId", auId) + .getResultList(); + } + + public List findAllBannerMessages() { + return em.createQuery("select o from BannerMessage o where o.active = 'true' ") + .getResultList(); + } + + public void save( BannerMessage message ) { + em.persist(message); + } + + public void deleteBannerMessage(Object pk) { + BannerMessage message = em.find(BannerMessage.class, pk); + + if (message != null) { + em.remove(message); + } + } + + public void deactivateBannerMessage(Object pk) { + BannerMessage message = em.find(BannerMessage.class, pk); + + if (message != null) { + message.setActive(false); + em.merge(message); + } + } + + public void dismissMessageByUser(BannerMessage message, AuthenticatedUser user) { + + UserBannerMessage ubm = new UserBannerMessage(); + ubm.setUser(user); + ubm.setBannerMessage(message); + ubm.setBannerDismissalTime(new Timestamp(new Date().getTime())); + em.persist(ubm); + em.flush(); + + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/BannerMessageText.java b/src/main/java/edu/harvard/iq/dataverse/BannerMessageText.java new file mode 100644 index 00000000000..dbae9a6dc27 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/BannerMessageText.java @@ -0,0 +1,94 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package edu.harvard.iq.dataverse; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * + * @author skraffmi + */ +@Entity +public class BannerMessageText implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(columnDefinition = "TEXT") + private String message; + + @Column(columnDefinition = "TEXT") + private String lang; + + @ManyToOne + @JoinColumn( nullable = false ) + private BannerMessage bannerMessage; + + public BannerMessage getBannerMessage() { + return bannerMessage; + } + public void setBannerMessage(BannerMessage bannerMessage) { + this.bannerMessage = bannerMessage; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (id != null ? id.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof BannerMessageText)) { + return false; + } + BannerMessageText other = (BannerMessageText) object; + if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { + return false; + } + return true; + } + + @Override + public String toString() { + return "edu.harvard.iq.dataverse.BannerMessageText[ id=" + id + " ]"; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/DataFile.java b/src/main/java/edu/harvard/iq/dataverse/DataFile.java index 560048db9ca..2f0981c80af 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataFile.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataFile.java @@ -192,6 +192,9 @@ public String toString() { @OneToMany(mappedBy = "dataFile", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}) private List dataTables; + @OneToMany(mappedBy = "dataFile", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}) + private List auxiliaryFiles; + @OneToMany(mappedBy = "dataFile", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST}) private List ingestReports; @@ -281,6 +284,14 @@ public String getDuplicateFilename() { public void setDuplicateFilename(String duplicateFilename) { this.duplicateFilename = duplicateFilename; } + + public List getAuxiliaryFiles() { + return auxiliaryFiles; + } + + public void setAuxiliaryFiles(List auxiliaryFiles) { + this.auxiliaryFiles = auxiliaryFiles; + } diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java index f6e7759bf1d..8f8e9b103c1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java @@ -9,6 +9,8 @@ import javax.persistence.Id; import javax.persistence.Index; import javax.persistence.JoinColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Temporal; @@ -23,6 +25,16 @@ @Index(columnList = "dataset_id"), @Index(columnList = "linkingDataverse_id") }) +@NamedQueries({ + @NamedQuery(name = "DatasetLinkingDataverse.findByDatasetId", + query = "select object(o) from DatasetLinkingDataverse as o where o.dataset.id =:datasetId order by o.id"), + @NamedQuery(name = "DatasetLinkingDataverse.findByLinkingDataverseId", + query = "SELECT OBJECT(o) FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId order by o.id"), + @NamedQuery(name = "DatasetLinkingDataverse.findByDatasetIdAndLinkingDataverseId", + query = "SELECT OBJECT(o) FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId AND o.dataset.id = :datasetId"), + @NamedQuery(name = "DatasetLinkingDataverse.findIdsByLinkingDataverseId", + query = "SELECT o.dataset.id FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId") +}) public class DatasetLinkingDataverse implements Serializable { private static final long serialVersionUID = 1L; @Id diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java index a73fd6f0911..3789efcd443 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java @@ -27,32 +27,13 @@ public class DatasetLinkingServiceBean implements java.io.Serializable { @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; - /** - * @param linkingDataverseId - * @return - * @todo Should this method simply be deleted? It isn't used anywhere and is - * throwing exceptions: Syntax error parsing [select object(o.dataverse.id) - * from DatasetLinkingDataverse as o where o.linkingDataverse.id - * =:linkingDataverseId order by o.id] - */ - @Deprecated - public List findLinkedDataverses(Long linkingDataverseId) { - List retList = new ArrayList<>(); - Query query = em.createQuery("select object(o.dataverse.id) from DatasetLinkingDataverse as o where o.linkingDataverse.id =:linkingDataverseId order by o.id"); - query.setParameter("linkingDataverseId", linkingDataverseId); - for (Object o : query.getResultList()) { - DatasetLinkingDataverse convterted = (DatasetLinkingDataverse) o; - retList.add(convterted.getDataset()); - } - return retList; - } - public List findDatasetsThisDataverseIdHasLinkedTo(Long dataverseId) { + + public List findLinkedDatasets(Long dataverseId) { List datasets = new ArrayList<>(); - TypedQuery typedQuery = em.createQuery("SELECT OBJECT(o) FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :dataverseId", DatasetLinkingDataverse.class); - typedQuery.setParameter("dataverseId", dataverseId); - List datasetLinkingDataverses = typedQuery.getResultList(); - for (DatasetLinkingDataverse datasetLinkingDataverse : datasetLinkingDataverses) { + TypedQuery typedQuery = em.createNamedQuery("DatasetLinkingDataverse.findByLinkingDataverseId", DatasetLinkingDataverse.class) + .setParameter("linkingDataverseId", dataverseId); + for (DatasetLinkingDataverse datasetLinkingDataverse : typedQuery.getResultList()) { datasets.add(datasetLinkingDataverse.getDataset()); } return datasets; @@ -60,31 +41,14 @@ public List findDatasetsThisDataverseIdHasLinkedTo(Long dataverseId) { public List findLinkingDataverses(Long datasetId) { List retList = new ArrayList<>(); - for (DatasetLinkingDataverse dld : findDatasetLinkingDataverses(datasetId)) { - retList.add(dld.getLinkingDataverse()); + TypedQuery typedQuery = em.createNamedQuery("DatasetLinkingDataverse.findByDatasetId", DatasetLinkingDataverse.class) + .setParameter("datasetId", datasetId); + for (DatasetLinkingDataverse datasetLinkingDataverse : typedQuery.getResultList()) { + retList.add(datasetLinkingDataverse.getLinkingDataverse()); } return retList; } - public DatasetLinkingDataverse findDatasetLinkingDataverse(Long datasetId, Long linkingDataverseId) { - DatasetLinkingDataverse foundDatasetLinkingDataverse = null; - try { - foundDatasetLinkingDataverse = em.createQuery("SELECT OBJECT(o) FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :dataverseId AND o.dataset.id = :datasetId", DatasetLinkingDataverse.class) - .setParameter("datasetId", datasetId) - .setParameter("dataverseId", linkingDataverseId) - .getSingleResult(); - } catch (javax.persistence.NoResultException e) { - logger.fine("no datasetLinkingDataverse found for datasetId " + datasetId + " and linkingDataverseId " + linkingDataverseId); - } - return foundDatasetLinkingDataverse; - } - - public List findDatasetLinkingDataverses(Long datasetId) { - return em.createQuery("select object(o) from DatasetLinkingDataverse as o where o.dataset.id =:datasetId order by o.id", DatasetLinkingDataverse.class) - .setParameter("datasetId", datasetId) - .getResultList(); - } - public void save(DatasetLinkingDataverse datasetLinkingDataverse) { if (datasetLinkingDataverse.getId() == null) { em.persist(datasetLinkingDataverse); @@ -93,11 +57,21 @@ public void save(DatasetLinkingDataverse datasetLinkingDataverse) { } } + public DatasetLinkingDataverse findDatasetLinkingDataverse(Long datasetId, Long linkingDataverseId) { + try { + return em.createNamedQuery("DatasetLinkingDataverse.findByDatasetIdAndLinkingDataverseId",DatasetLinkingDataverse.class) + .setParameter("datasetId", datasetId) + .setParameter("linkingDataverseId", linkingDataverseId) + .getSingleResult(); + } catch (javax.persistence.NoResultException e) { + logger.fine("no datasetLinkingDataverse found for datasetId " + datasetId + " and linkingDataverseId " + linkingDataverseId); + return null; + } + } + + public boolean alreadyLinked(Dataverse dataverse, Dataset dataset) { - TypedQuery typedQuery = em.createQuery("SELECT OBJECT(o) FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :dataverseId AND o.dataset.id = :datasetId", DatasetLinkingDataverse.class); - typedQuery.setParameter("dataverseId", dataverse.getId()); - typedQuery.setParameter("datasetId", dataset.getId()); - return !typedQuery.getResultList().isEmpty(); + return findDatasetLinkingDataverse(dataset.getId(), dataverse.getId()) != null; } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java b/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java index 25c564e777d..7e9655b3970 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseHeaderFragment.java @@ -6,6 +6,8 @@ package edu.harvard.iq.dataverse; import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import static edu.harvard.iq.dataverse.util.JsfHelper.JH; @@ -14,6 +16,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -54,6 +57,9 @@ public class DataverseHeaderFragment implements java.io.Serializable { @EJB DataFileServiceBean datafileService; + + @EJB + BannerMessageServiceBean bannerMessageService; @Inject DataverseSession dataverseSession; @@ -68,6 +74,8 @@ public class DataverseHeaderFragment implements java.io.Serializable { UserNotificationServiceBean userNotificationService; List breadcrumbs = new ArrayList<>(); + + private List bannerMessages = new ArrayList<>(); private Long unreadNotificationCount = null; @@ -276,6 +284,39 @@ public boolean isRootDataverseThemeDisabled(Dataverse dataverse) { return false; } } + + + public List getBannerMessages() { + User user = dataverseSession.getUser(); + AuthenticatedUser au = null; + if (user.isAuthenticated()) { + au = (AuthenticatedUser) user; + } + + if(au == null){ + bannerMessages = bannerMessageService.findBannerMessages(); + } else{ + bannerMessages = bannerMessageService.findBannerMessages(au.getId()); + } + + if (!dataverseSession.getDismissedMessages().isEmpty()) { + for (BannerMessage dismissed : dataverseSession.getDismissedMessages()) { + Iterator itr = bannerMessages.iterator(); + while (itr.hasNext()) { + BannerMessage test = itr.next(); + if (test.equals(dismissed)) { + itr.remove(); + } + } + } + } + + return bannerMessages; + } + + public void setBannerMessages(List bannerMessages) { + this.bannerMessages = bannerMessages; + } public String getSignupUrl(String loginRedirect) { String nonNullDefaultIfKeyNotFound = ""; diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java index 3d44173cf4c..788308dce1e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java @@ -13,6 +13,8 @@ import javax.persistence.Id; import javax.persistence.Index; import javax.persistence.JoinColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Temporal; @@ -27,6 +29,16 @@ @Index(columnList = "dataverse_id"), @Index(columnList = "linkingDataverse_id") }) +@NamedQueries({ + @NamedQuery(name = "DataverseLinkingDataverse.findByDataverseId", + query = "select object(o) from DataverseLinkingDataverse as o where o.dataverse.id =:dataverseId order by o.id"), + @NamedQuery(name = "DataverseLinkingDataverse.findByLinkingDataverseId", + query = "select object(o) from DataverseLinkingDataverse as o where o.linkingDataverse.id =:linkingDataverseId order by o.id"), + @NamedQuery(name = "DataverseLinkingDataverse.findByDataverseIdAndLinkingDataverseId", + query = "SELECT OBJECT(o) FROM DataverseLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId AND o.dataverse.id = :dataverseId"), + @NamedQuery(name = "DataverseLinkingDataverse.findIdsByLinkingDataverseId", + query = "SELECT o.dataverse.id FROM DataverseLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId") +}) public class DataverseLinkingDataverse implements Serializable { private static final long serialVersionUID = 1L; @Id diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java index e106272e4ec..c823deddb64 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java @@ -34,22 +34,20 @@ public class DataverseLinkingServiceBean implements java.io.Serializable { public List findLinkedDataverses(Long linkingDataverseId) { List retList = new ArrayList<>(); - Query query = em.createQuery("select object(o) from DataverseLinkingDataverse as o where o.linkingDataverse.id =:linkingDataverseId order by o.id"); - query.setParameter("linkingDataverseId", linkingDataverseId); - for (Object o : query.getResultList()) { - DataverseLinkingDataverse convterted = (DataverseLinkingDataverse) o; - retList.add(convterted.getDataverse()); + TypedQuery typedQuery = em.createNamedQuery("DataverseLinkingDataverse.findByLinkingDataverseId", DataverseLinkingDataverse.class) + .setParameter("linkingDataverseId", linkingDataverseId); + for (DataverseLinkingDataverse dataverseLinkingDataverse : typedQuery.getResultList()) { + retList.add(dataverseLinkingDataverse.getDataverse()); } return retList; } public List findLinkingDataverses(Long dataverseId) { List retList = new ArrayList<>(); - Query query = em.createQuery("select object(o) from DataverseLinkingDataverse as o where o.dataverse.id =:dataverseId order by o.id"); - query.setParameter("dataverseId", dataverseId); - for (Object o : query.getResultList()) { - DataverseLinkingDataverse convterted = (DataverseLinkingDataverse) o; - retList.add(convterted.getLinkingDataverse()); + TypedQuery typedQuery = em.createNamedQuery("DataverseLinkingDataverse.findByDataverseId", DataverseLinkingDataverse.class) + .setParameter("dataverseId", dataverseId); + for (DataverseLinkingDataverse dataverseLinkingDataverse : typedQuery.getResultList()) { + retList.add(dataverseLinkingDataverse.getLinkingDataverse()); } return retList; } @@ -63,22 +61,18 @@ public void save(DataverseLinkingDataverse dataverseLinkingDataverse) { } public DataverseLinkingDataverse findDataverseLinkingDataverse(Long dataverseId, Long linkingDataverseId) { - DataverseLinkingDataverse foundDataverseLinkingDataverse = null; try { - foundDataverseLinkingDataverse = em.createQuery("SELECT OBJECT(o) FROM DataverseLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId AND o.dataverse.id = :dataverseId", DataverseLinkingDataverse.class) - .setParameter("dataverseId", dataverseId) - .setParameter("linkingDataverseId", linkingDataverseId) - .getSingleResult(); + return em.createNamedQuery("DataverseLinkingDataverse.findByDataverseIdAndLinkingDataverseId", DataverseLinkingDataverse.class) + .setParameter("dataverseId", dataverseId) + .setParameter("linkingDataverseId", linkingDataverseId) + .getSingleResult(); } catch (javax.persistence.NoResultException e) { logger.fine("No DataverseLinkingDataverse found for dataverseId " + dataverseId + " and linkedDataverseId " + linkingDataverseId); + return null; } - return foundDataverseLinkingDataverse; } public boolean alreadyLinked(Dataverse definitionPoint, Dataverse dataverseToLinkTo) { - TypedQuery typedQuery = em.createQuery("SELECT OBJECT(o) FROM DataverseLinkingDataverse AS o WHERE o.linkingDataverse.id = :dataverseId AND o.dataverse.id = :dataverseToLinkTo", DataverseLinkingDataverse.class); - typedQuery.setParameter("dataverseId", definitionPoint.getId()); - typedQuery.setParameter("dataverseToLinkTo", dataverseToLinkTo.getId()); - return !typedQuery.getResultList().isEmpty(); + return findDataverseLinkingDataverse(dataverseToLinkTo.getId(), definitionPoint.getId()) != null; } } diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index 0e13936f0f7..96963bb9cb4 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -468,7 +468,7 @@ public List findDataversesThatLinkToThisDvId(long dataverseId) { } public List findDatasetsThisIdHasLinkedTo(long dataverseId) { - return datasetLinkingService.findDatasetsThisDataverseIdHasLinkedTo(dataverseId); + return datasetLinkingService.findLinkedDatasets(dataverseId); } public List findDataversesThatLinkToThisDatasetId(long datasetId) { diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseSession.java b/src/main/java/edu/harvard/iq/dataverse/DataverseSession.java index d8d73ceaf3e..2a2b02c5b18 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseSession.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseSession.java @@ -4,12 +4,15 @@ import edu.harvard.iq.dataverse.PermissionServiceBean.StaticPermissionQuery; import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord; import edu.harvard.iq.dataverse.actionlogging.ActionLogServiceBean; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.GuestUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.util.SessionUtil; import edu.harvard.iq.dataverse.util.SystemConfig; import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.logging.Logger; import javax.ejb.EJB; @@ -43,12 +46,28 @@ public class DataverseSession implements Serializable{ @Inject SettingsWrapper settingsWrapper; + @Inject + DataverseHeaderFragment headerFragment; + @EJB SystemConfig systemConfig; + @EJB + BannerMessageServiceBean bannerMessageService; + private static final Logger logger = Logger.getLogger(DataverseSession.class.getCanonicalName()); private boolean statusDismissed = false; + + private List dismissedMessages = new ArrayList<>(); + + public List getDismissedMessages() { + return dismissedMessages; + } + + public void setDismissedMessages(List dismissedMessages) { + this.dismissedMessages = dismissedMessages; + } /** * If debug is set to true, some pages show extra debugging information to @@ -177,6 +196,19 @@ public void updateLocaleInViewRoot() { } } + public void dismissMessage(BannerMessage message){ + + if (message.isDismissibleByUser()){ + if (user.isAuthenticated()){ + bannerMessageService.dismissMessageByUser(message, (AuthenticatedUser) user); + } + + } else { + dismissedMessages.add(message); + } + + } + public void configureSessionTimeout() { HttpSession httpSession = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false); diff --git a/src/main/java/edu/harvard/iq/dataverse/UserBannerMessage.java b/src/main/java/edu/harvard/iq/dataverse/UserBannerMessage.java new file mode 100644 index 00000000000..7bd4f2d898f --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/UserBannerMessage.java @@ -0,0 +1,98 @@ + +package edu.harvard.iq.dataverse; + +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import java.io.Serializable; +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * + * @author skraffmi + */ +@Entity +public class UserBannerMessage implements Serializable { + + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne + @JoinColumn(nullable = false) + private AuthenticatedUser user; + + @OneToOne + @JoinColumn(nullable = false) + private BannerMessage bannerMessage; + + @Temporal(value = TemporalType.TIMESTAMP) + @Column( nullable=false ) + private Date bannerDismissalTime; + + public Long getId() { + return id; + } + + public AuthenticatedUser getUser() { + return user; + } + + public void setUser(AuthenticatedUser user) { + this.user = user; + } + + public BannerMessage getBannerMessage() { + return bannerMessage; + } + + public void setBannerMessage(BannerMessage bannerMessage) { + this.bannerMessage = bannerMessage; + } + + public Date getBannerDismissalTime() { + return bannerDismissalTime; + } + + public void setBannerDismissalTime(Date bannerDismissalTime) { + this.bannerDismissalTime = bannerDismissalTime; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (id != null ? id.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof UserBannerMessage)) { + return false; + } + UserBannerMessage other = (UserBannerMessage) object; + if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { + return false; + } + return true; + } + + @Override + public String toString() { + return "edu.harvard.iq.dataverse.UserBannerMessage[ id=" + id + " ]"; + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java index 8f913ea5f1b..0c7a4224648 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java @@ -6,6 +6,8 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.AuxiliaryFile; +import edu.harvard.iq.dataverse.AuxiliaryFileServiceBean; import edu.harvard.iq.dataverse.DataCitation; import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.FileMetadata; @@ -43,14 +45,12 @@ import edu.harvard.iq.dataverse.dataaccess.DataFileZipper; import edu.harvard.iq.dataverse.dataaccess.OptionalAccessService; import edu.harvard.iq.dataverse.dataaccess.ImageThumbConverter; -import edu.harvard.iq.dataverse.dataaccess.StoredOriginalFile; import edu.harvard.iq.dataverse.datavariable.DataVariable; import edu.harvard.iq.dataverse.datavariable.VariableServiceBean; import edu.harvard.iq.dataverse.engine.command.Command; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; import edu.harvard.iq.dataverse.engine.command.impl.AssignRoleCommand; -import edu.harvard.iq.dataverse.engine.command.impl.CreateExplicitGroupCommand; import edu.harvard.iq.dataverse.engine.command.impl.GetDatasetCommand; import edu.harvard.iq.dataverse.engine.command.impl.GetDraftDatasetVersionCommand; import edu.harvard.iq.dataverse.engine.command.impl.GetLatestAccessibleDatasetVersionCommand; @@ -62,13 +62,12 @@ import edu.harvard.iq.dataverse.export.DDIExportServiceBean; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean.MakeDataCountEntry; -import edu.harvard.iq.dataverse.makedatacount.MakeDataCountUtil; import edu.harvard.iq.dataverse.settings.SettingsServiceBean; import edu.harvard.iq.dataverse.util.BundleUtil; import edu.harvard.iq.dataverse.util.FileUtil; import edu.harvard.iq.dataverse.util.StringUtil; import edu.harvard.iq.dataverse.util.SystemConfig; -import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; +import edu.harvard.iq.dataverse.util.json.JsonPrinter; import edu.harvard.iq.dataverse.worldmapauth.WorldMapTokenServiceBean; import java.util.logging.Logger; @@ -88,16 +87,9 @@ import java.util.logging.Level; import javax.inject.Inject; import javax.json.Json; -import javax.json.JsonObjectBuilder; -import java.math.BigDecimal; import java.net.URI; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; -import javax.faces.context.FacesContext; import javax.json.JsonArrayBuilder; import javax.persistence.TypedQuery; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -110,7 +102,6 @@ import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -125,10 +116,13 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import javax.ws.rs.core.StreamingOutput; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; -import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; -import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json; import java.net.URISyntaxException; import javax.ws.rs.RedirectionException; +import javax.ws.rs.core.MediaType; +import static javax.ws.rs.core.Response.Status.FORBIDDEN; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; /* Custom API exceptions [NOT YET IMPLEMENTED] @@ -184,6 +178,8 @@ public class Access extends AbstractApiBean { UserNotificationServiceBean userNotificationService; @EJB FileDownloadServiceBean fileDownloadService; + @EJB + AuxiliaryFileServiceBean auxiliaryFileService; @Inject PermissionsWrapper permissionsWrapper; @Inject @@ -505,16 +501,19 @@ public String dataVariableMetadataDDI(@PathParam("varId") Long varId, @QueryPara } /* - * "Preprocessed data" metadata format: - * (this was previously provided as a "format conversion" option of the - * file download form of the access API call) + * GET method for retrieving various auxiliary files associated with + * a tabular datafile. */ - @Path("datafile/{fileId}/metadata/preprocessed") - @GET - @Produces({"text/xml"}) - - public DownloadInstance tabularDatafileMetadataPreprocessed(@PathParam("fileId") String fileId, @QueryParam("key") String apiToken, @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context HttpServletResponse response) throws ServiceUnavailableException { + @Path("datafile/{fileId}/metadata/{formatTag}/{formatVersion}") + @GET + public DownloadInstance tabularDatafileMetadataAux(@PathParam("fileId") String fileId, + @PathParam("formatTag") String formatTag, + @PathParam("formatVersion") String formatVersion, + @QueryParam("key") String apiToken, + @Context UriInfo uriInfo, + @Context HttpHeaders headers, + @Context HttpServletResponse response) throws ServiceUnavailableException { DataFile df = findDataFileOrDieWrapper(fileId); @@ -522,18 +521,48 @@ public DownloadInstance tabularDatafileMetadataPreprocessed(@PathParam("fileId") apiToken = headers.getHeaderString(API_KEY_HEADER); } - // This will throw a ForbiddenException if access isn't authorized: - checkAuthorization(df, apiToken); DownloadInfo dInfo = new DownloadInfo(df); + boolean publiclyAvailable = false; - if (df.isTabularData()) { + if (!df.isTabularData()) { + throw new BadRequestException("tabular data required"); + } + + DownloadInstance downloadInstance; + AuxiliaryFile auxFile = null; + + // formatTag=preprocessed is handled as a special case. + // This is (as of now) the only aux. tabular metadata format that Dataverse + // can generate (and cache) itself. (All the other formats served have + // to be deposited first, by the @POST version of this API). + + if ("preprocessed".equals(formatTag)) { dInfo.addServiceAvailable(new OptionalAccessService("preprocessed", "application/json", "format=prep", "Preprocessed data in JSON")); + downloadInstance = new DownloadInstance(dInfo); + if (downloadInstance.checkIfServiceSupportedAndSetConverter("format", "prep")) { + logger.fine("Preprocessed data for tabular file "+fileId); + } } else { - throw new BadRequestException("tabular data required"); + // All other (deposited) formats: + auxFile = auxiliaryFileService.lookupAuxiliaryFile(df, formatTag, formatVersion); + + if (auxFile == null) { + throw new NotFoundException("Auxiliary metadata format "+formatTag+" is not available for datafile "+fileId); + } + + if (auxFile.getIsPublic()) { + publiclyAvailable = true; + } + downloadInstance = new DownloadInstance(dInfo); + downloadInstance.setAuxiliaryFile(auxFile); } - DownloadInstance downloadInstance = new DownloadInstance(dInfo); - if (downloadInstance.checkIfServiceSupportedAndSetConverter("format", "prep")) { - logger.fine("Preprocessed data for tabular file "+fileId); + + // Unless this format is explicitly authorized to be publicly available, + // the following will check access authorization (based on the access rules + // as defined for the DataFile itself), and will throw a ForbiddenException + // if access is denied: + if (!publiclyAvailable) { + checkAuthorization(df, apiToken); } return downloadInstance; @@ -1084,6 +1113,64 @@ private String getWebappImageResource(String imageName) { } */ + /** + * + * @param fileId + * @param formatTag + * @param formatVersion + * @param origin + * @param isPublic + * @param fileInputStream + * @param contentDispositionHeader + * @param formDataBodyPart + * @return + */ + @Path("datafile/{fileId}/metadata/{formatTag}/{formatVersion}") + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + + public Response saveAuxiliaryFileWithVersion(@PathParam("fileId") Long fileId, + @PathParam("formatTag") String formatTag, + @PathParam("formatVersion") String formatVersion, + @FormDataParam("origin") String origin, + @FormDataParam("isPublic") boolean isPublic, + @FormDataParam("file") InputStream fileInputStream + + ) { + AuthenticatedUser authenticatedUser; + try { + authenticatedUser = findAuthenticatedUserOrDie(); + } catch (WrappedResponse ex) { + return error(FORBIDDEN, "Authorized users only."); + } + + DataFile dataFile = dataFileService.find(fileId); + if (dataFile == null) { + return error(BAD_REQUEST, "File not found based on id " + fileId + "."); + } + + if (!permissionService.userOn(authenticatedUser, dataFile.getOwner()).has(Permission.EditDataset)) { + return error(FORBIDDEN, "User not authorized to edit the dataset."); + } + + if (!dataFile.isTabularData()) { + return error(BAD_REQUEST, "Not a tabular DataFile (db id=" + fileId + ")"); + } + + + AuxiliaryFile saved = auxiliaryFileService.processAuxiliaryFile(fileInputStream, dataFile, formatTag, formatVersion, origin, isPublic); + + if (saved!=null) { + return ok(json(saved)); + } else { + return error(BAD_REQUEST, "Error saving Auxiliary file."); + } + } + + + + + /** * Allow (or disallow) access requests to Dataset * @@ -1835,5 +1922,5 @@ private URI handleCustomZipDownload(String customZipServiceUrl, String fileIds, throw new BadRequestException(); } return redirectUri; - } + } } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java index e0165f45aa2..b52665a7747 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java @@ -1,5 +1,8 @@ package edu.harvard.iq.dataverse.api; +import edu.harvard.iq.dataverse.BannerMessage; +import edu.harvard.iq.dataverse.BannerMessageServiceBean; +import edu.harvard.iq.dataverse.BannerMessageText; import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.DataFileServiceBean; import edu.harvard.iq.dataverse.Dataset; @@ -98,7 +101,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.function.Consumer; import javax.inject.Inject; +import javax.json.JsonArray; import javax.persistence.Query; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; @@ -145,6 +150,8 @@ public class Admin extends AbstractApiBean { DatasetVersionServiceBean datasetVersionService; @EJB ExplicitGroupServiceBean explicitGroupService; + @EJB + BannerMessageServiceBean bannerMessageService; // Make the session available @@ -1839,4 +1846,78 @@ public Response listStorageDrivers() throws WrappedResponse { DataAccess.getStorageDriverLabels().entrySet().forEach(s -> bld.add(s.getKey(), s.getValue())); return ok(bld); } + + @POST + @Path("/bannerMessage") + public Response addBannerMessage(JsonObject jsonObject) throws WrappedResponse { + + BannerMessage toAdd = new BannerMessage(); + try { + String dismissible = jsonObject.getString("dismissibleByUser"); + + boolean dismissibleByUser = false; + if (dismissible.equals("true")) { + dismissibleByUser = true; + } + toAdd.setDismissibleByUser(dismissibleByUser); + toAdd.setBannerMessageTexts(new ArrayList()); + toAdd.setActive(true); + JsonArray jsonArray = jsonObject.getJsonArray("messageTexts"); + for (int i = 0; i < jsonArray.size(); i++) { + JsonObject obj = (JsonObject) jsonArray.get(i); + String message = obj.getString("message"); + String lang = obj.getString("lang"); + BannerMessageText messageText = new BannerMessageText(); + messageText.setMessage(message); + messageText.setLang(lang); + messageText.setBannerMessage(toAdd); + toAdd.getBannerMessageTexts().add(messageText); + } + bannerMessageService.save(toAdd); + return ok("Banner Message added successfully."); + + } catch (Exception e) { + logger.warning("Unexpected Exception: " + e.getMessage()); + return error(Status.BAD_REQUEST, "Add Banner Message unexpected exception: " + e.getMessage()); + } + + } + + @DELETE + @Path("/bannerMessage/{id}") + public Response deleteBannerMessage(@PathParam("id") Long id) throws WrappedResponse { + + BannerMessage message = em.find(BannerMessage.class, id); + if (message == null){ + return error(Response.Status.NOT_FOUND, "Message id = " + id + " not found."); + } + bannerMessageService.deleteBannerMessage(id); + + return ok("Message id = " + id + " deleted."); + + } + + @PUT + @Path("/bannerMessage/{id}/deactivate") + public Response deactivateBannerMessage(@PathParam("id") Long id) throws WrappedResponse { + BannerMessage message = em.find(BannerMessage.class, id); + if (message == null){ + return error(Response.Status.NOT_FOUND, "Message id = " + id + " not found."); + } + bannerMessageService.deactivateBannerMessage(id); + + return ok("Message id = " + id + " deactivated."); + + } + + @GET + @Path("/bannerMessage") + public Response getBannerMessages(@PathParam("id") Long id) throws WrappedResponse { + + return ok(bannerMessageService.findAllBannerMessages().stream() + .map(m -> jsonObjectBuilder().add("id", m.getId()).add("displayValue", m.getDisplayValue())) + .collect(toJsonArray())); + + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstance.java b/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstance.java index 7e354bea24b..07215cb919e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstance.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstance.java @@ -6,6 +6,7 @@ package edu.harvard.iq.dataverse.api; //import java.io.ByteArrayOutputStream; +import edu.harvard.iq.dataverse.AuxiliaryFile; import edu.harvard.iq.dataverse.DataverseRequestServiceBean; import edu.harvard.iq.dataverse.EjbDataverseEngine; import edu.harvard.iq.dataverse.GuestbookResponse; @@ -47,6 +48,12 @@ public void setExtraArguments(List extraArguments) { private String conversionParam = null; private String conversionParamValue = null; + // This download instance is for an auxiliary file associated with + // the DataFile. Unlike "conversions" (above) this is used for files + // that Dataverse has no way of producing/deriving from the parent Datafile + // itself, that have to be deposited externally. + private AuxiliaryFile auxiliaryFile = null; + private EjbDataverseEngine command; private DataverseRequestServiceBean dataverseRequestService; @@ -210,4 +217,12 @@ public void setDataverseRequestService(DataverseRequestServiceBean dataverseRequ this.dataverseRequestService = dataverseRequestService; } + public AuxiliaryFile getAuxiliaryFile() { + return auxiliaryFile; + } + + public void setAuxiliaryFile(AuxiliaryFile auxiliaryFile) { + this.auxiliaryFile = auxiliaryFile; + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstanceWriter.java b/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstanceWriter.java index b10412a577d..1624e9932e2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstanceWriter.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/DownloadInstanceWriter.java @@ -227,6 +227,20 @@ public void writeTo(DownloadInstance di, Class clazz, Type type, Annotation[] // (similarly to what the Access API returns when a thumbnail is requested on a text file, etc.) throw new NotFoundException("datafile access error: requested optional service (image scaling, format conversion, etc.) could not be performed on this datafile."); } + } else if (di.getAuxiliaryFile() != null) { + // Make sure to close the InputStream for the main datafile: + try {storageIO.getInputStream().close();} catch (IOException ioex) {} + String auxTag = di.getAuxiliaryFile().getFormatTag(); + String auxVersion = di.getAuxiliaryFile().getFormatVersion(); + if (auxVersion != null) { + auxTag = auxTag + "_" + auxVersion; + } + long auxFileSize = di.getAuxiliaryFile().getFileSize(); + InputStreamIO auxStreamIO = new InputStreamIO(storageIO.getAuxFileAsInputStream(auxTag), auxFileSize); + auxStreamIO.setFileName(storageIO.getFileName() + "." + auxTag); + auxStreamIO.setMimeType(di.getAuxiliaryFile().getContentType()); + storageIO = auxStreamIO; + } else { if (storageIO instanceof S3AccessIO && !(dataFile.isTabularData()) && ((S3AccessIO) storageIO).downloadRedirectEnabled()) { // definitely close the (still open) S3 input stream, diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java index 593e2ef1e0e..af644f8a4de 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/AuthenticationServiceBean.java @@ -496,7 +496,8 @@ public void removeAuthentictedUserItems(AuthenticatedUser au){ deletePendingAccessRequests(au); - + deleteBannerMessages(au); + if (!explicitGroupService.findGroups(au).isEmpty()) { for(ExplicitGroup explicitGroup: explicitGroupService.findGroups(au)){ explicitGroup.removeByRoleAssgineeIdentifier(au.getIdentifier()); @@ -505,6 +506,12 @@ public void removeAuthentictedUserItems(AuthenticatedUser au){ } + private void deleteBannerMessages(AuthenticatedUser au){ + + em.createNativeQuery("delete from userbannermessage where user_id = "+au.getId()).executeUpdate(); + + } + private void deletePendingAccessRequests(AuthenticatedUser au){ em.createNativeQuery("delete from fileaccessrequests where authenticated_user_id = "+au.getId()).executeUpdate(); diff --git a/src/main/java/edu/harvard/iq/dataverse/flyway/StartupFlywayMigrator.java b/src/main/java/edu/harvard/iq/dataverse/flyway/StartupFlywayMigrator.java index d5f4f8520a7..71b53bd43f2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/flyway/StartupFlywayMigrator.java +++ b/src/main/java/edu/harvard/iq/dataverse/flyway/StartupFlywayMigrator.java @@ -15,7 +15,7 @@ @TransactionManagement(value = TransactionManagementType.BEAN) public class StartupFlywayMigrator { - @Resource(lookup = "jdbc/VDCNetDS") + @Resource(lookup = "java:app/jdbc/dataverse") private DataSource dataSource; @PostConstruct diff --git a/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java index 3af9a655b35..20cb1a8629a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/search/savedsearch/SavedSearchServiceBean.java @@ -2,10 +2,8 @@ import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetLinkingDataverse; -import edu.harvard.iq.dataverse.DatasetLinkingServiceBean; import edu.harvard.iq.dataverse.Dataverse; import edu.harvard.iq.dataverse.DataverseLinkingDataverse; -import edu.harvard.iq.dataverse.DataverseLinkingServiceBean; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.DvObjectServiceBean; import edu.harvard.iq.dataverse.EjbDataverseEngine; @@ -22,6 +20,7 @@ import edu.harvard.iq.dataverse.search.SortBy; import edu.harvard.iq.dataverse.util.SystemConfig; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,10 +49,6 @@ public class SavedSearchServiceBean { @EJB DvObjectServiceBean dvObjectService; @EJB - DatasetLinkingServiceBean datasetLinkingService; - @EJB - DataverseLinkingServiceBean dataverseLinkingService; - @EJB EjbDataverseEngine commandEngine; @EJB SystemConfig systemConfig; @@ -137,7 +132,7 @@ public void makeLinksForAllSavedSearchesTimer() { JsonObjectBuilder makeLinksForAllSavedSearches = makeLinksForAllSavedSearches(false); } catch (SearchException | CommandException ex) { Logger.getLogger(SavedSearchServiceBean.class.getName()).log(Level.SEVERE, null, ex); - } + } } } @@ -170,10 +165,22 @@ public JsonObjectBuilder makeLinksForAllSavedSearches(boolean debugFlag) throws * more structured that a simple String. */ public JsonObjectBuilder makeLinksForSingleSavedSearch(DataverseRequest dvReq, SavedSearch savedSearch, boolean debugFlag) throws SearchException, CommandException { + logger.info("SAVED SEARCH (" + savedSearch.getId() + ") START search and link process"); + Date start = new Date(); JsonObjectBuilder response = Json.createObjectBuilder(); JsonArrayBuilder savedSearchArrayBuilder = Json.createArrayBuilder(); JsonArrayBuilder infoPerHit = Json.createArrayBuilder(); SolrQueryResponse queryResponse = findHits(savedSearch); + + // get linked objects and add to a list + TypedQuery typedQuery = em.createNamedQuery("DataverseLinkingDataverse.findIdsByLinkingDataverseId", Long.class) + .setParameter("linkingDataverseId", savedSearch.getDefinitionPoint().getId()); + List alreadyLinkedObjectIds = typedQuery.getResultList(); + + typedQuery = em.createNamedQuery("DatasetLinkingDataverse.findIdsByLinkingDataverseId", Long.class) + .setParameter("linkingDataverseId", savedSearch.getDefinitionPoint().getId()); + alreadyLinkedObjectIds.addAll(typedQuery.getResultList()); + for (SolrSearchResult solrSearchResult : queryResponse.getSolrSearchResults()) { JsonObjectBuilder hitInfo = Json.createObjectBuilder(); @@ -190,7 +197,7 @@ public JsonObjectBuilder makeLinksForSingleSavedSearch(DataverseRequest dvReq, S Dataverse dataverseToLinkTo = (Dataverse) dvObjectThatDefinitionPointWillLinkTo; if (wouldResultInLinkingToItself(savedSearch.getDefinitionPoint(), dataverseToLinkTo)) { hitInfo.add(resultString, "Skipping because dataverse id " + dataverseToLinkTo.getId() + " would link to itself."); - } else if (alreadyLinkedToTheDataverse(savedSearch.getDefinitionPoint(), dataverseToLinkTo)) { + } else if (alreadyLinkedToTheDataverse(alreadyLinkedObjectIds, dataverseToLinkTo)) { hitInfo.add(resultString, "Skipping because dataverse " + savedSearch.getDefinitionPoint().getId() + " already links to dataverse " + dataverseToLinkTo.getId() + "."); } else if (dataverseToLinkToIsAlreadyPartOfTheSubtree(savedSearch.getDefinitionPoint(), dataverseToLinkTo)) { hitInfo.add(resultString, "Skipping because " + dataverseToLinkTo + " is already part of the subtree for " + savedSearch.getDefinitionPoint()); @@ -200,7 +207,7 @@ public JsonObjectBuilder makeLinksForSingleSavedSearch(DataverseRequest dvReq, S } } else if (dvObjectThatDefinitionPointWillLinkTo.isInstanceofDataset()) { Dataset datasetToLinkTo = (Dataset) dvObjectThatDefinitionPointWillLinkTo; - if (alreadyLinkedToTheDataset(savedSearch.getDefinitionPoint(), datasetToLinkTo)) { + if (alreadyLinkedToTheDataset(alreadyLinkedObjectIds, datasetToLinkTo)) { hitInfo.add(resultString, "Skipping because dataverse " + savedSearch.getDefinitionPoint() + " already links to dataset " + datasetToLinkTo + "."); } else if (datasetToLinkToIsAlreadyPartOfTheSubtree(savedSearch.getDefinitionPoint(), datasetToLinkTo)) { // already there from normal search/browse @@ -212,6 +219,7 @@ public JsonObjectBuilder makeLinksForSingleSavedSearch(DataverseRequest dvReq, S } else { DatasetLinkingDataverse link = commandEngine.submitInNewTransaction(new LinkDatasetCommand(dvReq, savedSearch.getDefinitionPoint(), datasetToLinkTo)); + alreadyLinkedObjectIds.add(datasetToLinkTo.getId()); // because search results could produce two hits (published and draft) hitInfo.add(resultString, "Persisted DatasetLinkingDataverse id " + link.getId() + " link of " + link.getDataset() + " to " + link.getLinkingDataverse()); } } else if (dvObjectThatDefinitionPointWillLinkTo.isInstanceofDataFile()) { @@ -228,6 +236,9 @@ public JsonObjectBuilder makeLinksForSingleSavedSearch(DataverseRequest dvReq, S } savedSearchArrayBuilder.add(info); response.add("hits for saved search id " + savedSearch.getId(), savedSearchArrayBuilder); + + Date end = new Date(); + logger.info("SAVED SEARCH (" + savedSearch.getId() + ") total time in ms: " + (end.getTime() - start.getTime())); return response; } @@ -278,12 +289,12 @@ private JsonArrayBuilder getFilterQueries(SavedSearch savedSearch) { return filterQueriesArrayBuilder; } - private boolean alreadyLinkedToTheDataverse(Dataverse definitionPoint, Dataverse dataverseToLinkTo) { - return dataverseLinkingService.alreadyLinked(definitionPoint, dataverseToLinkTo); + private boolean alreadyLinkedToTheDataverse(List alreadyLinkedObjectIds, Dataverse dataverseToLinkTo) { + return alreadyLinkedObjectIds.contains(dataverseToLinkTo.getId()); } - private boolean alreadyLinkedToTheDataset(Dataverse definitionPoint, Dataset linkToThisDataset) { - return datasetLinkingService.alreadyLinked(definitionPoint, linkToThisDataset); + private boolean alreadyLinkedToTheDataset(List alreadyLinkedObjectIds, Dataset linkToThisDataset) { + return alreadyLinkedObjectIds.contains(linkToThisDataset.getId()); } private static boolean wouldResultInLinkingToItself(Dataverse savedSearchDefinitionPoint, Dataverse dataverseToLinkTo) { diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index b2e82d92dc3..e292ee39722 100644 --- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -218,10 +218,6 @@ public enum Key { */ ThumbnailSizeLimitImage, ThumbnailSizeLimitPDF, - /* status message that will appear on the home page */ - StatusMessageHeader, - /* full text of status message, to appear in popup */ - StatusMessageText, /* return email address for system emails such as notifications */ SystemEmail, /* size limit for Tabular data file ingests */ diff --git a/src/main/java/edu/harvard/iq/dataverse/util/DataSourceProducer.java b/src/main/java/edu/harvard/iq/dataverse/util/DataSourceProducer.java new file mode 100644 index 00000000000..02ba331cdd5 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/util/DataSourceProducer.java @@ -0,0 +1,54 @@ +package edu.harvard.iq.dataverse.util; + +import javax.annotation.Resource; +import javax.annotation.sql.DataSourceDefinition; +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; +import javax.sql.DataSource; + +// Find docs here: https://javaee.github.io/javaee-spec/javadocs/javax/annotation/sql/DataSourceDefinition.html +@Singleton +@DataSourceDefinition( + name = "java:app/jdbc/dataverse", + // The app server (Payara) deploys a managed pool for this data source for us. + // We don't need to deal with this on our own. + // + // HINT: PGSimpleDataSource would work too, but as we use a connection pool, go with a javax.sql.ConnectionPoolDataSource + // HINT: PGXADataSource is unnecessary (no distributed transactions used) and breaks ingest. + className = "org.postgresql.ds.PGConnectionPoolDataSource", + user = "${MPCONFIG=dataverse.db.user}", + password = "${MPCONFIG=dataverse.db.password}", + url = "jdbc:postgresql://${MPCONFIG=dataverse.db.host}:${MPCONFIG=dataverse.db.port}/${MPCONFIG=dataverse.db.name}", + // If we ever need to change these pool settings, we need to remove this class and create the resource + // from web.xml. We can use MicroProfile Config in there for these values, impossible to do in the annotation. + // + // See also https://blog.payara.fish/an-intro-to-connection-pools-in-payara-server-5 + // Payara DataSourceDefinitionDeployer default value = 8 + minPoolSize = 10, + // HINT: Payara DataSourceDefinitionDeployer default value = 32 + // HINT: Harvard Dataverse is fine for a while with 64 + maxPoolSize = 100, + // "The number of seconds that a physical connection should remain unused in the pool before the connection is closed for a connection pool. " + // Payara DataSourceDefinitionDeployer default value = 300 (seconds) + maxIdleTime = 300) +// It's possible to add additional properties like this... +// +//properties = { +// "fish.payara.log-jdbc-calls=true" +//}) +// +// ... but at this time we don't think we need any. The full list +// of properties can be found at https://docs.payara.fish/community/docs/5.2020.6/documentation/payara-server/jdbc/advanced-connection-pool-properties.html#full-list-of-properties +// +// All these properties cannot be configured via MPCONFIG as Payara doesn't support this (yet). To be enhanced. +// See also https://github.com/payara/Payara/issues/5024 +public class DataSourceProducer { + + @Resource(lookup = "java:app/jdbc/dataverse") + DataSource ds; + + @Produces + public DataSource getDatasource() { + return ds; + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/util/FileUtil.java b/src/main/java/edu/harvard/iq/dataverse/util/FileUtil.java index 4b7dce7d8c7..c30927281f6 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/FileUtil.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/FileUtil.java @@ -686,7 +686,7 @@ public static String calculateChecksum(byte[] dataBytes, ChecksumType checksumTy } - private static String checksumDigestToString(byte[] digestBytes) { + public static String checksumDigestToString(byte[] digestBytes) { StringBuilder sb = new StringBuilder(""); for (int i = 0; i < digestBytes.length; i++) { sb.append(Integer.toString((digestBytes[i] & 0xff) + 0x100, 16).substring(1)); diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index b52dabdb31d..c37efc3178f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -1,5 +1,6 @@ package edu.harvard.iq.dataverse.util.json; +import edu.harvard.iq.dataverse.AuxiliaryFile; import edu.harvard.iq.dataverse.ControlledVocabularyValue; import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.DataFileTag; @@ -563,6 +564,16 @@ public static JsonObjectBuilder json(FileMetadata fmd) { .add("dataFile", JsonPrinter.json(fmd.getDataFile(), fmd)); } + public static JsonObjectBuilder json(AuxiliaryFile auxFile) { + return jsonObjectBuilder() + .add("formatTag", auxFile.getFormatTag()) + .add("formatVersion", auxFile.getFormatVersion()) // "label" is the filename + .add("origin", auxFile.getOrigin()) + .add("isPublic", auxFile.getIsPublic()) + .add("fileSize", auxFile.getFileSize()) + .add("checksum", auxFile.getChecksum()) + .add("dataFile", JsonPrinter.json(auxFile.getDataFile())); + } public static JsonObjectBuilder json(DataFile df) { return JsonPrinter.json(df, null); } diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..8c475f1c050 --- /dev/null +++ b/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,4 @@ +dataverse.db.host=localhost +dataverse.db.port=5432 +dataverse.db.user=dataverse +dataverse.db.name=dataverse diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 8b4e33858ac..45552f36939 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -3,7 +3,7 @@ org.eclipse.persistence.jpa.PersistenceProvider - jdbc/VDCNetDS + java:app/jdbc/dataverse @@ -20,15 +20,4 @@ - diff --git a/src/main/webapp/dataverse_header.xhtml b/src/main/webapp/dataverse_header.xhtml index 5516fe57dca..3e154b11fd6 100644 --- a/src/main/webapp/dataverse_header.xhtml +++ b/src/main/webapp/dataverse_header.xhtml @@ -193,16 +193,20 @@ - + -
-
- - - -
-
+ + +
+
+ + +
+
+
+
+
- +

#{bundle['file.deleteFileDialog.tip']}

#{bundle['file.deleteFileDialog.failed.tip']}

- -
@@ -947,7 +947,7 @@ function checkFilesSelected() { var count = PF('filesTable').getSelectedRowsCount(); if (count > 0) { - PF('deleteFileConfirmation').show(); + PF('editDeleteFileConfirmation').show(); } else { PF('selectFilesForDeleteFragment').show(); } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java index ad0c93a80d1..4fb1271c8c9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AccessIT.java @@ -6,20 +6,24 @@ package edu.harvard.iq.dataverse.api; import com.jayway.restassured.RestAssured; +import static com.jayway.restassured.RestAssured.given; import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; import edu.harvard.iq.dataverse.DataFile; +import static edu.harvard.iq.dataverse.api.UtilIT.API_TOKEN_HTTP_HEADER; import edu.harvard.iq.dataverse.util.FileUtil; import java.io.IOException; import java.util.zip.ZipInputStream; -import static javax.ws.rs.core.Response.Status.OK; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.util.zip.ZipEntry; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.InputStream; +import java.nio.file.Path; import java.util.HashMap; +import static javax.ws.rs.core.Response.Status.OK; import org.hamcrest.collection.IsMapContaining; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -27,12 +31,6 @@ import static org.junit.Assert.assertTrue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; -import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; /** * @@ -156,6 +154,7 @@ public static void setUp() throws InterruptedException { String tab4PathToFile = "scripts/search/data/tabular/" + tabFile4NameUnpublished; Response tab4AddResponse = UtilIT.uploadFileViaNative(datasetId.toString(), tab4PathToFile, apiToken); tabFile4IdUnpublished = JsonPath.from(tab4AddResponse.body().asString()).getInt("data.files[0].dataFile.id"); + assertTrue("Failed test if Ingest Lock exceeds max duration " + tabFile2Name, UtilIT.sleepForLock(datasetId.longValue(), "Ingest", apiToken, UtilIT.MAXIMUM_INGEST_LOCK_DURATION)); } @@ -172,6 +171,26 @@ public static void tearDown() { } + @Test + public void testSaveAuxiliaryFileWithVersion() throws IOException { + System.out.println("Add aux file with update"); + String mimeType = null; + String pathToFile = "scripts/search/data/tabular/1char"; + Response response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .multiPart("file", new File(pathToFile), mimeType) + .post("/api/access/datafile/" + tabFile1Id + "/metadata/dpJSON/v1"); + response.prettyPrint(); + assertEquals(200, response.getStatusCode()); + System.out.println("Downloading Aux file that was just added"); + response = given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/access/datafile/" + tabFile1Id + "/metadata/dpJSON/v1"); + + String dataStr = response.prettyPrint(); + assertEquals(dataStr,"a\n"); + assertEquals(200, response.getStatusCode()); + } //This test does a lot of testing of non-original downloads as well @Test diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java index e944ca5b2bf..84da33cd3ee 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java @@ -750,4 +750,39 @@ public void testLoadMetadataBlock_ErrorHandling() { message ); } + + @Test + public void testBannerMessages(){ + + String pathToJsonFile = "scripts/api/data/bannerMessageError.json"; + Response addBannerMessageErrorResponse = UtilIT.addBannerMessage(pathToJsonFile); + addBannerMessageErrorResponse.prettyPrint(); + String body = addBannerMessageErrorResponse.getBody().asString(); + String status = JsonPath.from(body).getString("status"); + assertEquals("ERROR", status); + + pathToJsonFile = "scripts/api/data/bannerMessageTest.json"; + + Response addBannerMessageResponse = UtilIT.addBannerMessage(pathToJsonFile); + addBannerMessageResponse.prettyPrint(); + body = addBannerMessageResponse.getBody().asString(); + status = JsonPath.from(body).getString("status"); + assertEquals("OK", status); + + Response getBannerMessageResponse = UtilIT.getBannerMessages(); + getBannerMessageResponse.prettyPrint(); + body = getBannerMessageResponse.getBody().asString(); + status = JsonPath.from(body).getString("status"); + assertEquals("OK", status); + String deleteId = UtilIT.getBannerMessageIdFromResponse(getBannerMessageResponse.getBody().asString()); + + System.out.print("delete id: " + deleteId); + + Response deleteBannerMessageResponse = UtilIT.deleteBannerMessage(new Long (deleteId)); + deleteBannerMessageResponse.prettyPrint(); + body = deleteBannerMessageResponse.getBody().asString(); + status = JsonPath.from(body).getString("status"); + assertEquals("OK", status); + + } } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 47aedb910d9..97afd7e060e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -36,6 +36,8 @@ import org.hamcrest.Matcher; import static com.jayway.restassured.path.xml.XmlPath.from; import static com.jayway.restassured.RestAssured.given; +import java.io.StringReader; +import javax.json.JsonArray; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -2482,4 +2484,43 @@ static Response findDatasetDownloadSize(String datasetId, String version, Strin .get("/api/datasets/" + datasetId + "/versions/" + version + "/downloadsize"); } + static Response addBannerMessage(String pathToJsonFile) { + String jsonIn = getDatasetJson(pathToJsonFile); + + Response addBannerMessageResponse = given() + .body(jsonIn) + .contentType("application/json") + .post("/api/admin/bannerMessage"); + return addBannerMessageResponse; + } + + static Response getBannerMessages() { + + Response getBannerMessagesResponse = given() + .get("/api/admin/bannerMessage"); + return getBannerMessagesResponse; + } + + static Response deleteBannerMessage(Long id) { + + Response deleteBannerMessageResponse = given() + .delete("/api/admin/bannerMessage/"+id.toString()); + return deleteBannerMessageResponse; + } + + static String getBannerMessageIdFromResponse(String getBannerMessagesResponse) { + StringReader rdr = new StringReader(getBannerMessagesResponse); + JsonObject json = Json.createReader(rdr).readObject(); + + for (JsonObject obj : json.getJsonArray("data").getValuesAs(JsonObject.class)) { + String message = obj.getString("displayValue"); + if (message.equals("Banner Message For Deletion")) { + return obj.getJsonNumber("id").toString(); + } + } + + return "0"; + } + + }