diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index f318d2597aac..c2196d5ea1c9 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -48,8 +48,7 @@ - + diff --git a/dspace/modules/additions/src/main/java/org/dspace/core/LicenseServiceImpl.java b/dspace/modules/additions/src/main/java/org/dspace/core/LicenseServiceImpl.java index 6e6bf94875d1..33807dc3a080 100644 --- a/dspace/modules/additions/src/main/java/org/dspace/core/LicenseServiceImpl.java +++ b/dspace/modules/additions/src/main/java/org/dspace/core/LicenseServiceImpl.java @@ -12,20 +12,23 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.core.service.LicenseService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.model.Request; import org.dspace.web.ContextUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +// TAMU Customization - proxy license step +import java.io.FilenameFilter; +// END TAMU Customization - proxy license step /** * Encapsulate the deposit license. @@ -33,7 +36,7 @@ * @author mhwood */ public class LicenseServiceImpl implements LicenseService { - private final Logger log = LoggerFactory.getLogger(LicenseServiceImpl.class); + private final Logger log = LogManager.getLogger(); /** * The default license @@ -54,7 +57,7 @@ public void writeLicenseFile(String licenseFile, out.print(newLicense); out.close(); } catch (IOException e) { - log.warn("license_write: " + e.getLocalizedMessage()); + log.warn("license_write: {}", e::getLocalizedMessage); } license = newLicense; } @@ -102,7 +105,21 @@ public String getLicenseText(String licenseFile) { return license; } - // TAMU Customization - proxy license step get available license filenames + /** + * Get the site-wide default license that submitters need to grant + * + * Localized license requires: default_{{locale}}.license file. + * Locale also must be listed in webui.supported.locales setting. + * + * @return the default license + */ + @Override + public String getDefaultSubmissionLicense() { + init(); + return license; + } + + // TAMU Customization - proxy license step - get available license filenames @Override public String[] getLicenseFilenames() { String homeDir = DSpaceServicesFactory.getInstance() @@ -121,20 +138,7 @@ public boolean accept(File dir, String name) { } }); } - - /** - * Get the site-wide default license that submitters need to grant - * - * Localized license requires: default_{{locale}}.license file. - * Locale also must be listed in webui.supported.locales setting. - * - * @return the default license - */ - @Override - public String getDefaultSubmissionLicense() { - init(); - return license; - } + // END TAMU Customization - proxy license step - get available license filenames /** * Load in the default license. @@ -161,7 +165,7 @@ protected void init() { br.close(); } catch (IOException e) { - log.error("Can't load license: " + licenseFile.toString(), e); + log.error("Can't load license {}: ", licenseFile.toString(), e); // FIXME: Maybe something more graceful here, but with the // configuration we can't do anything diff --git a/dspace/modules/additions/src/main/java/org/dspace/core/service/LicenseService.java b/dspace/modules/additions/src/main/java/org/dspace/core/service/LicenseService.java index 699dc5f2ad51..4d41bafa9213 100644 --- a/dspace/modules/additions/src/main/java/org/dspace/core/service/LicenseService.java +++ b/dspace/modules/additions/src/main/java/org/dspace/core/service/LicenseService.java @@ -39,11 +39,12 @@ public void writeLicenseFile(String licenseFile, */ public String getDefaultSubmissionLicense(); - // TAMU Customization - proxy license step get available license filenames + // TAMU Customization - proxy license step - get available license filenames /** * Get all license filenames with suffix `.license` from config directory * * @return license filenames */ public String[] getLicenseFilenames(); + // END TAMU Customization - proxy license step - get available license filenames } diff --git a/dspace/modules/additions/src/main/java/org/dspace/discovery/FullTextContentStreams.java b/dspace/modules/additions/src/main/java/org/dspace/discovery/FullTextContentStreams.java index 9260f1c0b450..d36b45336b30 100644 --- a/dspace/modules/additions/src/main/java/org/dspace/discovery/FullTextContentStreams.java +++ b/dspace/modules/additions/src/main/java/org/dspace/discovery/FullTextContentStreams.java @@ -17,32 +17,34 @@ import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; -// TAMU Customization - Only index text bitstreams that are not restricted -import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.collect.Iterables; +import jakarta.annotation.Nullable; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -// TAMU Customization - Only index text bitstreams that are not restricted -import org.apache.commons.lang3.time.DateUtils; import org.apache.logging.log4j.Logger; import org.apache.solr.common.util.ContentStreamBase; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.ResourcePolicy; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.BundleService; import org.dspace.core.Context; +// TAMU Customization +import java.time.LocalDate; +import java.time.chrono.ChronoLocalDate; + +import org.dspace.content.service.BundleService; +import org.dspace.authorize.ResourcePolicy; +// END TAMU Customization + /** * Construct a ContentStream from a File */ @@ -55,15 +57,19 @@ public class FullTextContentStreams extends ContentStreamBase { protected List fullTextStreams; protected BitstreamService bitstreamService; - //TAMU Customization - We need a BundleService to check for bitstream restrictions before indexing them + // TAMU Customization - We need a BundleService to check for bitstream restrictions before indexing them protected final BundleService bundleService = ContentServiceFactory.getInstance().getBundleService(); + // END TAMU Customization - We need a BundleService to check for bitstream restrictions before indexing them public FullTextContentStreams(Context context, Item parentItem) throws SQLException { this.context = context; init(parentItem); } + // TAMU Customization + // protected void init(Item parentItem) { protected void init(Item parentItem) throws SQLException { + // END TAMU Customization fullTextStreams = new ArrayList<>(); if (parentItem != null) { @@ -76,7 +82,10 @@ protected void init(Item parentItem) throws SQLException { } } + // TAMU Customization + // private void buildFullTextList(Item parentItem) { private void buildFullTextList(Item parentItem) throws SQLException { + // END TAMU Customization // now get full text of any bitstreams in the TEXT bundle // trundle through the bundles List myBundles = parentItem.getBundles(); @@ -89,6 +98,7 @@ private void buildFullTextList(Item parentItem) throws SQLException { // TAMU Customization - Only index text bitstreams that are not restricted List bundlePolicies = bundleService.getBitstreamPolicies(context, myBundle); boolean isIndexable = false; + // END TAMU Customization log.debug("Processing full-text bitstreams. Item handle: " + sourceInfo); @@ -98,19 +108,19 @@ private void buildFullTextList(Item parentItem) throws SQLException { for (ResourcePolicy rp:bundlePolicies) { if (rp.getdSpaceObject().getID() == fulltextBitstream.getID()) { - Date start = rp.getStartDate(); - Date end = rp.getEndDate(); - Date now = new Date(); + ChronoLocalDate start = rp.getStartDate(); + ChronoLocalDate end = rp.getEndDate(); + ChronoLocalDate now = LocalDate.now(); if (rp.getGroup().getName().equalsIgnoreCase("anonymous") - && (start == null || ((start.before(now) || DateUtils.isSameDay(start, now)) - && (end == null || (end.after(now) || DateUtils.isSameDay(now, end))))) + && (start == null || ((start.isBefore(now) || start.isEqual(now)) + && (end == null || (end.isAfter(now) || now.isEqual(end))))) ) { isIndexable = true; } break; } } - // TAMU Customization - Only index text bitstreams that are not restricted + if (isIndexable) { fullTextStreams.add(new FullTextBitstream(sourceInfo, fulltextBitstream)); @@ -129,6 +139,19 @@ private void buildFullTextList(Item parentItem) throws SQLException { + fulltextBitstream.getSequenceID() + " " + fulltextBitstream.getName()); } + /* + fullTextStreams.add(new FullTextBitstream(sourceInfo, fulltextBitstream)); + + if (fulltextBitstream != null) { + log.debug("Added BitStream: " + + fulltextBitstream.getStoreNumber() + " " + + fulltextBitstream.getSequenceID() + " " + + fulltextBitstream.getName()); + } else { + log.error("Found a NULL bitstream when processing full-text files: item handle:" + sourceInfo); + } + */ + // END TAMU Customization - Only index text bitstreams that are not restricted } } } diff --git a/dspace/modules/additions/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace/modules/additions/src/main/java/org/dspace/discovery/SolrServiceImpl.java index e6dbdcb383a5..2c13b6b86d27 100644 --- a/dspace/modules/additions/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace/modules/additions/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -15,12 +15,9 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; -import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -28,11 +25,9 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.TimeZone; import java.util.UUID; -import java.util.Vector; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Transformer; @@ -42,6 +37,9 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.json.BucketBasedJsonFacet; +import org.apache.solr.client.solrj.response.json.BucketJsonFacet; +import org.apache.solr.client.solrj.response.json.NestableJsonFacet; import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; @@ -53,14 +51,11 @@ import org.apache.solr.common.util.NamedList; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.factory.AuthorizeServiceFactory; -import org.dspace.content.Bitstream; -import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Email; @@ -76,15 +71,23 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.discovery.indexobject.factory.IndexFactory; import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; +import org.dspace.discovery.indexobject.factory.ItemIndexFactory; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; -import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +// TAMU Customization +import java.util.Vector; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.service.ItemService; +import org.dspace.handle.service.HandleService; +// END TAMU Customization + /** * SolrIndexer contains the methods that index Items and their metadata, * collections, communities, etc. It is meant to either be invoked from the @@ -124,11 +127,12 @@ public class SolrServiceImpl implements SearchService, IndexingService { protected SolrSearchCore solrSearchCore; @Autowired protected ConfigurationService configurationService; - // TAMU Customizations + // TAMU Customization @Autowired(required = true) protected ItemService itemService; @Autowired(required = true) protected HandleService handleService; + // END TAMU Customization protected SolrServiceImpl() { @@ -202,134 +206,6 @@ protected void update(Context context, IndexFactory indexableObjectService, Inde } } - /** - * TAMU Customization - to the image URL provided to the Solr documents - * @throws SQLException - */ - private void addCommunityCollectionItem( - Context context,IndexableObject indexableObject, SolrInputDocument doc - ) throws IOException, SolrServerException, SQLException { - - // IndexableObject.getType(); - if (!(indexableObject instanceof IndexableItem)) { - return; - } - - IndexableItem indexableItem = (IndexableItem)indexableObject; - - Item item = (Item)indexableItem.getIndexedObject(); - - - // get the location string (for searching by collection & community) - List locations = getItemLocations(context, item); - - String handle = item.getHandle(); - - if (handle == null) { - handle = handleService.findHandle(context, item); - } - - // TAMU Customization - Write friendly community/collection names to index - if ( !locations.isEmpty() ) { - for (String location : locations) { - String field = location.startsWith("m") ? "location.comm" : "location.coll"; - String dsoName = locationToName(context,field,location.substring(1)); - log.debug("Adding location name:" + field + ".name_stored with value:" + dsoName); - doc.addField(field + ".name_stored", dsoName ); - } - } - - // TAMU Customization - Write bitstream URLs to index - List bitstreamLocations = new ArrayList<>(); - String dspaceUrl = configurationService.getProperty("dspace.server.url"); - for (Bundle bundle : item.getBundles()) { - String bitstreamUrlTemplate = "%s/bitstream/handle/%s/%s?sequence=%d"; - String primaryInternalId = null; - switch (bundle.getName()) { - case "ORIGINAL": - if (bundle.getPrimaryBitstream() != null) { - Bitstream primaryBitstream = bundle.getPrimaryBitstream(); - primaryInternalId = primaryBitstream.getInternalId(); - String primaryName = primaryBitstream.getName(); - int primarySequence = primaryBitstream.getSequenceID(); - String primaryUrl = String.format( - bitstreamUrlTemplate, dspaceUrl, handle, primaryName, primarySequence); - doc.addField("primaryBitstream_stored", primaryUrl); - } - for (Bitstream bitstream : bundle.getBitstreams()) { - if (bitstream != null && bitstream.getInternalId() != null && !bitstream.getInternalId().equals(primaryInternalId)) { - String name = bitstream.getName(); - int sequence = bitstream.getSequenceID(); - String url = String.format(bitstreamUrlTemplate, dspaceUrl, handle, name, sequence); - bitstreamLocations.add(url); - } - } - break; - case "THUMBNAIL": - if (!bundle.getBitstreams().isEmpty()) { - Bitstream thumbnailBitstream = bundle.getBitstreams().get(0); - if (thumbnailBitstream != null) { - String thumbnailName = thumbnailBitstream.getName(); - int thumbnailSequence = thumbnailBitstream.getSequenceID(); - String thumbnailUrl = String.format( - bitstreamUrlTemplate, - dspaceUrl, - handle, - thumbnailName, - thumbnailSequence - ); - doc.addField("thumbnailBitstream_stored", thumbnailUrl); - } - } - break; - case "LICENSE": - if (!bundle.getBitstreams().isEmpty()) { - Bitstream licenseBitstream = bundle.getBitstreams().get(0); - if (licenseBitstream != null) { - String licenseName = licenseBitstream.getName(); - int licenseSequence = licenseBitstream.getSequenceID(); - String licenseUrl = String.format( - bitstreamUrlTemplate, dspaceUrl, handle, licenseName, licenseSequence); - doc.addField("licenseBitstream_stored", licenseUrl); - } - } - break; - default: - break; - } - } - } - - /** - * @param context DSpace context - * @param myitem the item for which our locations are to be retrieved - * @return a list containing the identifiers of the communities and collections - * @throws SQLException sql exception - */ - protected List getItemLocations(Context context, Item myitem) - throws SQLException { - List locations = new Vector(); - - // build list of community ids - List communities = itemService.getCommunities(context, myitem); - - // build list of collection ids - List collections = myitem.getCollections(); - - // now put those into strings - int i = 0; - - for (i = 0; i < communities.size(); i++) { - locations.add("m" + communities.get(i).getID()); - } - - for (i = 0; i < collections.size(); i++) { - locations.add("l" + collections.get(i).getID()); - } - - return locations; - } - /** * unIndex removes an Item, Collection, or Community * @@ -483,6 +359,7 @@ public void updateIndex(Context context, boolean force, String type) { try { final List indexableObjectServices = indexObjectServiceFactory. getIndexFactories(); + int indexObject = 0; for (IndexFactory indexableObjectService : indexableObjectServices) { if (type == null || StringUtils.equals(indexableObjectService.getType(), type)) { final Iterator indexableObjects = indexableObjectService.findAll(context); @@ -490,6 +367,10 @@ public void updateIndex(Context context, boolean force, String type) { final IndexableObject indexableObject = indexableObjects.next(); indexContent(context, indexableObject, force); context.uncacheEntity(indexableObject.getIndexedObject()); + indexObject++; + if ((indexObject % 100) == 0 && indexableObjectService instanceof ItemIndexFactory) { + context.uncacheEntities(); + } } } } @@ -594,10 +475,10 @@ public void optimize() { if (solrSearchCore.getSolr() == null) { return; } - long start = System.currentTimeMillis(); + long start = Instant.now().toEpochMilli(); System.out.println("SOLR Search Optimize -- Process Started:" + start); solrSearchCore.getSolr().optimize(); - long finish = System.currentTimeMillis(); + long finish = Instant.now().toEpochMilli(); System.out.println("SOLR Search Optimize -- Process Finished:" + finish); System.out.println("SOLR Search Optimize -- Total time taken:" + (finish - start) + " (ms)."); } catch (SolrServerException | IOException e) { @@ -648,7 +529,7 @@ protected void emailException(Exception exception) { Locale.getDefault(), "internal_error")); email.addRecipient(recipient); email.addArgument(configurationService.getProperty("dspace.ui.url")); - email.addArgument(new Date()); + email.addArgument(Instant.now()); String stackTrace; @@ -684,7 +565,7 @@ protected void emailException(Exception exception) { * @throws IOException io exception * @throws SearchServiceException if something went wrong with querying the solr server */ - protected boolean requiresIndexing(String uniqueId, Date lastModified) + protected boolean requiresIndexing(String uniqueId, Instant lastModified) throws SQLException, IOException, SearchServiceException { // Check if we even have a last modified date @@ -716,11 +597,13 @@ protected boolean requiresIndexing(String uniqueId, Date lastModified) Object value = doc.getFieldValue(SearchUtils.LAST_INDEXED_FIELD); - if (value instanceof Date) { - Date lastIndexed = (Date) value; - - if (lastIndexed.before(lastModified)) { + // If it's a java.util.Date, convert to an Instant + if (value instanceof java.util.Date) { + value = ((java.util.Date) value).toInstant(); + } + if (value instanceof Instant lastIndexed) { + if (lastIndexed.isBefore(lastModified)) { reindexItem = true; } } @@ -795,73 +678,6 @@ public String createLocationQueryForAdministrableItems(Context context) return locationQuery.toString(); } - /** - * Helper function to retrieve a date using a best guess of the potential - * date encodings on a field - * - * @param t the string to be transformed to a date - * @return a date if the formatting was successful, null if not able to transform to a date - */ - public Date toDate(String t) { - SimpleDateFormat[] dfArr; - - // Choose the likely date formats based on string length - switch (t.length()) { - // case from 1 to 3 go through adding anyone a single 0. Case 4 define - // for all the SimpleDateFormat - case 1: - t = "0" + t; - // fall through - case 2: - t = "0" + t; - // fall through - case 3: - t = "0" + t; - // fall through - case 4: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat("yyyy")}; - break; - case 6: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat("yyyyMM")}; - break; - case 7: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat("yyyy-MM")}; - break; - case 8: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat("yyyyMMdd"), - new SimpleDateFormat("yyyy MMM")}; - break; - case 10: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat("yyyy-MM-dd")}; - break; - case 11: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat("yyyy MMM dd")}; - break; - case 20: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss'Z'")}; - break; - default: - dfArr = new SimpleDateFormat[] {new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")}; - break; - } - - for (SimpleDateFormat df : dfArr) { - try { - // Parse the date - df.setCalendar(Calendar - .getInstance(TimeZone.getTimeZone("UTC"))); - df.setLenient(false); - return df.parse(t); - } catch (ParseException pe) { - log.error("Unable to parse date format", pe); - } - } - - return null; - } - public String locationToName(Context context, String field, String value) throws SQLException { if ("location.comm".equals(field) || "location.coll".equals(field)) { int type = ("location.comm").equals(field) ? Constants.COMMUNITY : Constants.COLLECTION; @@ -1207,6 +1023,8 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) } //Resolve our facet field values resolveFacetFields(context, query, result, skipLoadingResponse, solrQueryResponse); + //Resolve our json facet field values used for metadata browsing + resolveJsonFacetFields(context, result, solrQueryResponse); } // If any stale entries are found in the current page of results, // we remove those stale entries and rerun the same query again. @@ -1232,7 +1050,42 @@ protected DiscoverResult retrieveResult(Context context, DiscoverQuery query) return result; } + /** + * Process the 'json.facet' response, which is currently only used for metadata browsing + * + * @param context context object + * @param result the result object to add the facet results to + * @param solrQueryResponse the solr query response + * @throws SQLException if database error + */ + private void resolveJsonFacetFields(Context context, DiscoverResult result, QueryResponse solrQueryResponse) + throws SQLException { + NestableJsonFacet response = solrQueryResponse.getJsonFacetingResponse(); + if (response != null && response.getBucketBasedFacetNames() != null) { + for (String facetName : response.getBucketBasedFacetNames()) { + BucketBasedJsonFacet facet = response.getBucketBasedFacets(facetName); + if (facet != null) { + result.setTotalEntries(facet.getNumBucketsCount()); + for (BucketJsonFacet bucket : facet.getBuckets()) { + String facetValue = bucket.getVal() != null ? bucket.getVal().toString() : ""; + String field = facetName + "_filter"; + String displayedValue = transformDisplayedValue(context, field, facetValue); + String authorityValue = transformAuthorityValue(context, field, facetValue); + String sortValue = transformSortValue(context, field, facetValue); + String filterValue = displayedValue; + if (StringUtils.isNotBlank(authorityValue)) { + filterValue = authorityValue; + } + result.addFacetResult(facetName, + new DiscoverResult.FacetResult(filterValue, displayedValue, + authorityValue, sortValue, bucket.getCount(), + DiscoveryConfigurationParameters.TYPE_TEXT)); + } + } + } + } + } private void resolveFacetFields(Context context, DiscoverQuery query, DiscoverResult result, boolean skipLoadingResponse, QueryResponse solrQueryResponse) throws SQLException { @@ -1382,11 +1235,10 @@ public List search(Context context, String query, String orderf } catch (IOException | SQLException | SolrServerException e) { // Any acception that we get ignore it. // We do NOT want any crashed to shown by the user - log.error(LogHelper.getHeader(context, "Error while quering solr", "Query: " + query), e); + log.error(LogHelper.getHeader(context, "Error while querying solr", "Query: " + query), e); return new ArrayList<>(0); } } - @Override public DiscoverFilterQuery toFilterQuery(Context context, String field, String operator, String value, DiscoveryConfiguration config) @@ -1416,10 +1268,14 @@ public DiscoverFilterQuery toFilterQuery(Context context, String field, String o } + filterQuery.append(":"); if ("equals".equals(operator) || "notequals".equals(operator)) { + //DO NOT ESCAPE RANGE QUERIES ! // TAMU Customization - calling the modified regex expression + // if (!value.matches("\\[.*TO.*\\]")) { if ( isNotRangeQuery(value) ) { + // END TAMU Customization - calling the modified regex expression value = ClientUtils.escapeQueryChars(value); filterQuery.append(value); } else { @@ -1431,8 +1287,11 @@ public DiscoverFilterQuery toFilterQuery(Context context, String field, String o filterQuery.append(value); } } else { + //DO NOT ESCAPE RANGE QUERIES ! // TAMU Customization - calling the modified regex expression + // if (!value.matches("\\[.*TO.*\\]")) { if ( isNotRangeQuery(value) ) { + // END TAMU Customization - calling the modified regex expression value = ClientUtils.escapeQueryChars(value); filterQuery.append("\"").append(value).append("\""); } else { @@ -1447,20 +1306,13 @@ public DiscoverFilterQuery toFilterQuery(Context context, String field, String o return result; } - /** - * TAMU Customization - checking the conditions - */ - private boolean isNotRangeQuery(String value) { - return (!(value.startsWith("[") || value.contains("TO") || value.endsWith("]"))); - } - @Override public List getRelatedItems(Context context, Item item, DiscoveryMoreLikeThisConfiguration mltConfig) { List results = new ArrayList<>(); try { SolrQuery solrQuery = new SolrQuery(); //Set the query to handle since this is unique - solrQuery.setQuery(SearchUtils.RESOURCE_UNIQUE_ID + ": " + new IndexableItem(item).getUniqueIndexID()); + solrQuery.setQuery(SearchUtils.RESOURCE_UNIQUE_ID + ":" + new IndexableItem(item).getUniqueIndexID()); //Only return obj identifier fields in result doc solrQuery.setFields(SearchUtils.RESOURCE_TYPE_FIELD, SearchUtils.RESOURCE_ID_FIELD); //Add the more like this parameters ! @@ -1518,7 +1370,7 @@ public String toSortFieldIndex(String metadataField, String type) { * Gets the solr field that contains the facet value split on each word break to the end, so can be searched * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} - * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular + * Only applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular * facet filter field */ protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field, @@ -1570,8 +1422,6 @@ protected String transformFacetField(DiscoverFacetField facetFieldConfig, String } else { return field + "_acid"; } - } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_STANDARD)) { - return field; } else { return field; } @@ -1756,4 +1606,146 @@ public String calculateExtremeValue(Context context, String valueField, return null; } + /** + * TAMU Customization + * + * @param context DSpace context + * @param indexableObject object to index + * @param doc Solr document being indexed + * @return a list containing the identifiers of the communities and collections + * @throws SQLException sql exception + */ + private void addCommunityCollectionItem( + Context context, IndexableObject indexableObject, SolrInputDocument doc + ) throws IOException, SolrServerException, SQLException { + + // IndexableObject.getType(); + if (!(indexableObject instanceof IndexableItem)) { + return; + } + + IndexableItem indexableItem = (IndexableItem)indexableObject; + + Item item = indexableItem.getIndexedObject(); + + + // get the location string (for searching by collection & community) + List locations = getItemLocations(context, item); + + String handle = item.getHandle(); + + if (handle == null) { + handle = handleService.findHandle(context, item); + } + + // TAMU Customization - Write friendly community/collection names to index + if ( !locations.isEmpty() ) { + for (String location : locations) { + String field = location.startsWith("m") ? "location.comm" : "location.coll"; + String dsoName = locationToName(context,field,location.substring(1)); + log.debug("Adding location name:" + field + ".name_stored with value:" + dsoName); + doc.addField(field + ".name_stored", dsoName ); + } + } + + // TAMU Customization - Write bitstream URLs to index + List bitstreamLocations = new ArrayList<>(); + String dspaceUrl = configurationService.getProperty("dspace.server.url"); + for (Bundle bundle : item.getBundles()) { + String bitstreamUrlTemplate = "%s/bitstream/handle/%s/%s?sequence=%d"; + String primaryInternalId = null; + switch (bundle.getName()) { + case "ORIGINAL": + if (bundle.getPrimaryBitstream() != null) { + Bitstream primaryBitstream = bundle.getPrimaryBitstream(); + primaryInternalId = primaryBitstream.getInternalId(); + String primaryName = primaryBitstream.getName(); + int primarySequence = primaryBitstream.getSequenceID(); + String primaryUrl = String.format( + bitstreamUrlTemplate, dspaceUrl, handle, primaryName, primarySequence); + doc.addField("primaryBitstream_stored", primaryUrl); + } + for (Bitstream bitstream : bundle.getBitstreams()) { + if (bitstream != null && bitstream.getInternalId() != null && !bitstream.getInternalId().equals(primaryInternalId)) { + String name = bitstream.getName(); + int sequence = bitstream.getSequenceID(); + String url = String.format(bitstreamUrlTemplate, dspaceUrl, handle, name, sequence); + bitstreamLocations.add(url); + } + } + break; + case "THUMBNAIL": + if (!bundle.getBitstreams().isEmpty()) { + Bitstream thumbnailBitstream = bundle.getBitstreams().get(0); + if (thumbnailBitstream != null) { + String thumbnailName = thumbnailBitstream.getName(); + int thumbnailSequence = thumbnailBitstream.getSequenceID(); + String thumbnailUrl = String.format( + bitstreamUrlTemplate, + dspaceUrl, + handle, + thumbnailName, + thumbnailSequence + ); + doc.addField("thumbnailBitstream_stored", thumbnailUrl); + } + } + break; + case "LICENSE": + if (!bundle.getBitstreams().isEmpty()) { + Bitstream licenseBitstream = bundle.getBitstreams().get(0); + if (licenseBitstream != null) { + String licenseName = licenseBitstream.getName(); + int licenseSequence = licenseBitstream.getSequenceID(); + String licenseUrl = String.format( + bitstreamUrlTemplate, dspaceUrl, handle, licenseName, licenseSequence); + doc.addField("licenseBitstream_stored", licenseUrl); + } + } + break; + default: + break; + } + } + } + + /** + * TAMU Customization - checking the conditions + */ + private boolean isNotRangeQuery(String value) { + return (!(value.startsWith("[") || value.contains("TO") || value.endsWith("]"))); + } + + /** + * TAMU Customization + * + * @param context DSpace context + * @param myitem the item for which our locations are to be retrieved + * @return a list containing the identifiers of the communities and collections + * @throws SQLException sql exception + */ + private List getItemLocations(Context context, Item myitem) + throws SQLException { + List locations = new Vector<>(); + + // build list of community ids + List communities = itemService.getCommunities(context, myitem); + + // build list of collection ids + List collections = myitem.getCollections(); + + // now put those into strings + int i = 0; + + for (i = 0; i < communities.size(); i++) { + locations.add("m" + communities.get(i).getID()); + } + + for (i = 0; i < collections.size(); i++) { + locations.add("l" + collections.get(i).getID()); + } + + return locations; + } + } diff --git a/dspace/modules/additions/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java b/dspace/modules/additions/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java index 5d6210048369..64931258d39b 100644 --- a/dspace/modules/additions/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java +++ b/dspace/modules/additions/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java @@ -15,17 +15,17 @@ import java.util.Iterator; import java.util.List; -import com.hp.hpl.jena.rdf.model.InfModel; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.ResIterator; -import com.hp.hpl.jena.reasoner.Reasoner; -import com.hp.hpl.jena.reasoner.ReasonerRegistry; -import com.hp.hpl.jena.reasoner.ValidityReport; -import com.hp.hpl.jena.util.FileManager; -import com.hp.hpl.jena.util.FileUtils; -import com.hp.hpl.jena.vocabulary.RDF; import org.apache.commons.lang3.StringUtils; +import org.apache.jena.rdf.model.InfModel; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.ResIterator; +import org.apache.jena.reasoner.Reasoner; +import org.apache.jena.reasoner.ReasonerRegistry; +import org.apache.jena.reasoner.ValidityReport; +import org.apache.jena.util.FileManager; +import org.apache.jena.util.FileUtils; +import org.apache.jena.vocabulary.RDF; import org.apache.logging.log4j.Logger; import org.dspace.app.util.factory.UtilServiceFactory; import org.dspace.authorize.AuthorizeException; @@ -125,15 +125,17 @@ public Model convert(Context context, DSpaceObject dso) } } - //TAMU Customization - Map more than just ITEMs - // should be changed, if Communities and Collections have metadata as well. + // TAMU Customization - Map more than just ITEMs /* + // should be changed, if Communities and Collections have metadata as well. if (!(dso instanceof Item)) { log.error("This DspaceObject (" + dsoService.getTypeText(dso) + " " + dso.getID() + ") should not have bin submitted to this " + "plugin, as it supports Items only!"); return null; - }*/ + } + */ + // END TAMU Customization - Map more than just ITEMs List metadata_values = dsoService .getMetadata(dso, MetadataSchemaEnum.DC.getName(), Item.ANY, Item.ANY, Item.ANY); @@ -189,10 +191,11 @@ public Model convert(Context context, DSpaceObject dso) @Override public boolean supports(int type) { - //TAMU Customization - Map more than just ITEMs - return (type == Constants.ITEM || type == Constants.COLLECTION || type == Constants.COMMUNITY); + // TAMU Customization - Map more than just ITEMs // should be changed, if Communities and Collections have metadata as well. // return (type == Constants.ITEM); + return (type == Constants.ITEM || type == Constants.COLLECTION || type == Constants.COMMUNITY); + // END TAMU Customization - Map more than just ITEMs } protected Model loadConfiguration() { diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/BitstreamRestController.java deleted file mode 100644 index ef4c419c02c1..000000000000 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ /dev/null @@ -1,263 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.dspace.app.rest.utils.ContextUtil.obtainContext; -import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; -import static org.springframework.web.bind.annotation.RequestMethod.PUT; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.Response; - -import org.apache.catalina.connector.ClientAbortException; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.model.BitstreamRest; -import org.dspace.app.rest.model.hateoas.BitstreamResource; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.HttpHeadersInitializer; -import org.dspace.app.rest.utils.Utils; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Bitstream; -import org.dspace.content.BitstreamFormat; -import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.BitstreamService; -import org.dspace.core.Context; -import org.dspace.disseminate.service.CitationDocumentService; -import org.dspace.eperson.EPerson; -import org.dspace.services.ConfigurationService; -import org.dspace.services.EventService; -import org.dspace.usage.UsageEvent; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -/** - * This is a specialized controller to provide access to the bitstream binary - * content - * - * The mapping for requested endpoint try to resolve a valid UUID, for example - *
- * {@code
- * https:///api/core/bitstreams/26453b4d-e513-44e8-8d5b-395f62972eff/content
- * }
- * 
- * - * @author Andrea Bollini (andrea.bollini at 4science.it) - * @author Tom Desair (tom dot desair at atmire dot com) - * @author Frederic Van Reet (frederic dot vanreet at atmire dot com) - */ -@RestController -@RequestMapping("/api/" + BitstreamRest.CATEGORY + "/" + BitstreamRest.PLURAL_NAME - + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) -public class BitstreamRestController { - - private static final Logger log = org.apache.logging.log4j.LogManager - .getLogger(BitstreamRestController.class); - - //Most file systems are configured to use block sizes of 4096 or 8192 and our buffer should be a multiple of that. - private static final int BUFFER_SIZE = 4096 * 10; - - @Autowired - private BitstreamService bitstreamService; - - @Autowired - BitstreamFormatService bitstreamFormatService; - - @Autowired - private EventService eventService; - - @Autowired - private CitationDocumentService citationDocumentService; - - @Autowired - private ConfigurationService configurationService; - - @Autowired - ConverterService converter; - - @Autowired - Utils utils; - - @PreAuthorize("hasPermission(#uuid, 'BITSTREAM', 'READ')") - @RequestMapping( method = {RequestMethod.GET, RequestMethod.HEAD}, value = "content") - public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse response, - HttpServletRequest request) throws IOException, SQLException, AuthorizeException { - - - Context context = ContextUtil.obtainContext(request); - - Bitstream bit = bitstreamService.find(context, uuid); - EPerson currentUser = context.getCurrentUser(); - - if (bit == null) { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - return null; - } - - Long lastModified = bitstreamService.getLastModified(bit); - BitstreamFormat format = bit.getFormat(context); - String mimetype = format.getMIMEType(); - String name = getBitstreamName(bit, format); - - if (StringUtils.isBlank(request.getHeader("Range"))) { - //We only log a download request when serving a request without Range header. This is because - //a browser always sends a regular request first to check for Range support. - eventService.fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - request, - context, - bit)); - } - - try { - long filesize = bit.getSizeBytes(); - Boolean citationEnabledForBitstream = citationDocumentService.isCitationEnabledForBitstream(bit, context); - - HttpHeadersInitializer httpHeadersInitializer = new HttpHeadersInitializer() - .withBufferSize(BUFFER_SIZE) - .withFileName(name) - .withChecksum(bit.getChecksum()) - .withMimetype(mimetype) - .with(request) - .with(response); - - if (lastModified != null) { - httpHeadersInitializer.withLastModified(lastModified); - } - - //Determine if we need to send the file as a download or if the browser can open it inline - //The file will be downloaded if its size is larger than the configured threshold, - //or if its mimetype/extension appears in the "webui.content_disposition_format" config - long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold"); - if ((dispositionThreshold >= 0 && filesize > dispositionThreshold) - || checkFormatForContentDisposition(format)) { - httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT); - } - - org.dspace.app.rest.utils.BitstreamResource bitstreamResource = - new org.dspace.app.rest.utils.BitstreamResource(name, uuid, - currentUser != null ? currentUser.getID() : null, - context.getSpecialGroupUuids(), citationEnabledForBitstream); - - //We have all the data we need, close the connection to the database so that it doesn't stay open during - //download/streaming - context.complete(); - - //Send the data - if (httpHeadersInitializer.isValid()) { - HttpHeaders httpHeaders = httpHeadersInitializer.initialiseHeaders(); - - // TAMU Customization - only return headers for HEAD request - if ("HEAD".equals(request.getMethod())) { - log.debug("HEAD request - no response body"); - return ResponseEntity.ok().headers(httpHeaders).build(); - } - - return ResponseEntity.ok().headers(httpHeaders).body(bitstreamResource); - } - - } catch (ClientAbortException ex) { - log.debug("Client aborted the request before the download was completed. " + - "Client is probably switching to a Range request.", ex); - } catch (Exception e) { - throw e; - } - return null; - } - - private String getBitstreamName(Bitstream bit, BitstreamFormat format) { - String name = bit.getName(); - if (name == null) { - // give a default name to the file based on the UUID and the primary extension of the format - name = bit.getID().toString(); - if (format != null && format.getExtensions() != null && format.getExtensions().size() > 0) { - name += "." + format.getExtensions().get(0); - } - } - return name; - } - - private boolean isNotAnErrorResponse(HttpServletResponse response) { - Response.Status.Family responseCode = Response.Status.Family.familyOf(response.getStatus()); - return responseCode.equals(Response.Status.Family.SUCCESSFUL) - || responseCode.equals(Response.Status.Family.REDIRECTION); - } - - private boolean checkFormatForContentDisposition(BitstreamFormat format) { - // never automatically download undefined formats - if (format == null) { - return false; - } - List formats = List.of((configurationService.getArrayProperty("webui.content_disposition_format"))); - boolean download = formats.contains(format.getMIMEType()); - if (!download) { - for (String ext : format.getExtensions()) { - if (formats.contains(ext)) { - download = true; - break; - } - } - } - return download; - } - - /** - * This method will update the bitstream format of the bitstream that corresponds to the provided bitstream uuid. - * - * @param uuid The UUID of the bitstream for which to update the bitstream format - * @param request The request object - * @return The wrapped resource containing the bitstream which in turn contains the bitstream format - * @throws SQLException If something goes wrong in the database - */ - @RequestMapping(method = PUT, consumes = {"text/uri-list"}, value = "format") - @PreAuthorize("hasPermission(#uuid, 'BITSTREAM','WRITE')") - @PostAuthorize("returnObject != null") - public BitstreamResource updateBitstreamFormat(@PathVariable UUID uuid, - HttpServletRequest request) throws SQLException { - - Context context = obtainContext(request); - - List bitstreamFormats = utils.constructBitstreamFormatList(request, context); - - if (bitstreamFormats.size() > 1) { - throw new DSpaceBadRequestException("Only one bitstream format is allowed"); - } - - BitstreamFormat bitstreamFormat = bitstreamFormats.stream().findFirst() - .orElseThrow(() -> new DSpaceBadRequestException("No valid bitstream format was provided")); - - Bitstream bitstream = bitstreamService.find(context, uuid); - - if (bitstream == null) { - throw new ResourceNotFoundException("Bitstream with id: " + uuid + " not found"); - } - - bitstream.setFormat(context, bitstreamFormat); - - context.commit(); - - BitstreamRest bitstreamRest = converter.toRest(context.reloadEntity(bitstream), utils.obtainProjection()); - return converter.toResource(bitstreamRest); - } -} diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/model/CollectionRest.java index 71ea3936b63b..2c039b8d7d6e 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -10,48 +10,22 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * TAMU Customization - Customized Collection REST Resource + * The Collection REST Resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - // TAMU Customization - proxy license step - @LinkRest( - name = CollectionRest.LICENSES, - method = "getLicenses" - ), - @LinkRest( - name = CollectionRest.LICENSE, - method = "getLicense" - ), - @LinkRest( - name = CollectionRest.LOGO, - method = "getLogo" - ), - @LinkRest( - name = CollectionRest.MAPPED_ITEMS, - method = "getMappedItems" - ), - @LinkRest( - name = CollectionRest.PARENT_COMMUNITY, - method = "getParentCommunity" - ), - @LinkRest( - name = CollectionRest.ADMIN_GROUP, - method = "getAdminGroup" - ), - @LinkRest( - name = CollectionRest.SUBMITTERS_GROUP, - method = "getSubmittersGroup" - ), - @LinkRest( - name = CollectionRest.ITEM_READ_GROUP, - method = "getItemReadGroup" - ), - @LinkRest( - name = CollectionRest.BITSTREAM_READ_GROUP, - method = "getBitstreamReadGroup" - ), + @LinkRest(name = CollectionRest.LICENSE, method = "getLicense"), + // TAMU Customization - proxy license step + @LinkRest(name = CollectionRest.LICENSES, method = "getLicenses"), + // END TAMU Customization - proxy license step + @LinkRest(name = CollectionRest.LOGO, method = "getLogo"), + @LinkRest(name = CollectionRest.MAPPED_ITEMS, method = "getMappedItems"), + @LinkRest(name = CollectionRest.PARENT_COMMUNITY, method = "getParentCommunity"), + @LinkRest(name = CollectionRest.ADMIN_GROUP, method = "getAdminGroup"), + @LinkRest(name = CollectionRest.SUBMITTERS_GROUP, method = "getSubmittersGroup"), + @LinkRest(name = CollectionRest.ITEM_READ_GROUP, method = "getItemReadGroup"), + @LinkRest(name = CollectionRest.BITSTREAM_READ_GROUP, method = "getBitstreamReadGroup"), }) public class CollectionRest extends DSpaceObjectRest { public static final String NAME = "collection"; @@ -62,6 +36,7 @@ public class CollectionRest extends DSpaceObjectRest { public static final String LICENSE = "license"; // TAMU Customization - proxy license step public static final String LICENSES = "licenses"; + // END TAMU Customization - proxy license step public static final String LOGO = "logo"; public static final String MAPPED_ITEMS = "mappedItems"; public static final String PARENT_COMMUNITY = "parentCommunity"; @@ -82,6 +57,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + private int archivedItemsCount; public int getArchivedItemsCount() { diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/model/LicenseRest.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/model/LicenseRest.java index 8d988e03c813..8d245f69d5b9 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/model/LicenseRest.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/model/LicenseRest.java @@ -7,67 +7,73 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonIgnore; - /** - * TAMU Customization - Customized License REST resource. + * The License text REST resource. * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -public class LicenseRest extends RestAddressableModel { - +public class LicenseRest implements RestModel { public static final String NAME = "license"; + public static final String PLURAL_NAME = "licenses"; - private final String name; + private boolean custom = false; + private String text; + // TAMU Customization - proxy license step + private final String name; private final String label; + // END TAMU Customization - proxy license step - private final String text; - - private final boolean custom; - + // TAMU Customization - proxy license step private LicenseRest(String name, String label, String text, boolean custom) { + super(); this.name = name; this.label = label; this.text = text; this.custom = custom; } + // END TAMU Customization - proxy license step - public String getName() { - return name; + public boolean isCustom() { + return custom; } - public String getLabel() { - return label; + public void setCustom(boolean custom) { + this.custom = custom; } public String getText() { return text; } - public boolean isCustom() { - return custom; + public void setText(String text) { + this.text = text; } - @Override - public String getType() { - return NAME; + // TAMU Customization - proxy license step + public String getName() { + return name; } + public String getLabel() { + return label; + } + // END TAMU Customization - proxy license step + @Override - @JsonIgnore - public String getCategory() { - return null; + public String getType() { + return NAME; } @Override - @JsonIgnore - public Class getController() { - return null; + public String getTypePlural() { + return PLURAL_NAME; } + // TAMU Customization - proxy license step public static LicenseRest of(String name, String label, String text, boolean custom) { return new LicenseRest(name, label, text, custom); } + // END TAMU Customization - proxy license step } diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java index c809e0e6a50c..db2a072582ed 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicenseLinkRepository.java @@ -9,9 +9,9 @@ import java.sql.SQLException; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.LicenseRest; @@ -20,30 +20,35 @@ import org.dspace.content.service.CollectionService; import org.dspace.core.Context; import org.dspace.core.service.LicenseService; -import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +// TAMU Customization - proxy license step +import org.dspace.services.ConfigurationService; +// END TAMU Customization - proxy license step + /** - * TAMU Customization - Customized License Link repository for "license" subresource of an individual collection. + * Link repository for "license" subresource of an individual collection. * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LICENSE) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.LICENSE) public class CollectionLicenseLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - private CollectionService collectionService; + CollectionService collectionService; @Autowired - private LicenseService licenseService; + LicenseService licenseService; + // TAMU Customization - proxy license step @Autowired - private ConfigurationService configurationService; + ConfigurationService configurationService; + // END TAMU Customization - proxy license step @PreAuthorize("hasPermission(#collectionId, 'COLLECTION', 'READ')") public LicenseRest getLicense(@Nullable HttpServletRequest request, @@ -56,8 +61,7 @@ public LicenseRest getLicense(@Nullable HttpServletRequest request, if (collection == null) { throw new ResourceNotFoundException("No such collection: " + collectionId); } - - // TAMU Customization - use customized LicenseRest DTO + // TAMU Customization - proxy license step - use customized LicenseRest DTO String license = "default"; boolean custom = false; @@ -74,18 +78,20 @@ public LicenseRest getLicense(@Nullable HttpServletRequest request, } return LicenseRest.of(license, label, text, custom); - // LicenseRest licenseRest = new LicenseRest(); - // String text = collection.getLicenseCollection(); - // if (StringUtils.isNotBlank(text)) { - // licenseRest.setCustom(true); - // licenseRest.setText(text); - // } else { - // licenseRest.setText(licenseService.getDefaultSubmissionLicense()); - // } - // return licenseRest; + /* + LicenseRest licenseRest = new LicenseRest(); + String text = collection.getLicenseCollection(); + if (StringUtils.isNotBlank(text)) { + licenseRest.setCustom(true); + licenseRest.setText(text); + } else { + licenseRest.setText(licenseService.getDefaultSubmissionLicense()); + } + return licenseRest; + */ + // TAMU Customization - proxy license step - use customized LicenseRest DTO } catch (SQLException e) { throw new RuntimeException(e); } } - } diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicensesLinkRepository.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicensesLinkRepository.java index 3ca30fc98455..206524155d48 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicensesLinkRepository.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/CollectionLicensesLinkRepository.java @@ -7,14 +7,11 @@ */ package org.dspace.app.rest.repository; -import java.io.File; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.LicenseRest; @@ -23,39 +20,51 @@ import org.dspace.content.service.CollectionService; import org.dspace.core.Context; import org.dspace.core.service.LicenseService; -import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +// TAMU Customization - proxy license step +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.services.ConfigurationService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +// END TAMU Customization - proxy license step + /** - * TAMU Customization - Customized License Link repository for "license" subresource of an individual collection. + * Link repository for "license" subresource of an individual collection. * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LICENSES) +@Component(CollectionRest.CATEGORY + "." + CollectionRest.PLURAL_NAME + "." + CollectionRest.LICENSES) +// TAMU Customization - proxy license step +// public class CollectionLicenseLinkRepository extends AbstractDSpaceRestRepository public class CollectionLicensesLinkRepository extends AbstractDSpaceRestRepository +// END TAMU Customization - proxy license step implements LinkRestRepository { @Autowired - private CollectionService collectionService; + CollectionService collectionService; @Autowired - private LicenseService licenseService; + LicenseService licenseService; + // TAMU Customization - proxy license step @Autowired - private ConfigurationService configurationService; + ConfigurationService configurationService; + // END TAMU Customization - proxy license step - // TAMU Customization - get available licenses + // TAMU Customization - proxy license step - get available licenses slight refactor of getLicense @PreAuthorize("hasPermission(#collectionId, 'COLLECTION', 'READ')") public Page getLicenses(@Nullable HttpServletRequest request, - UUID collectionId, - @Nullable Pageable optionalPageable, - Projection projection) { + UUID collectionId, + @Nullable Pageable optionalPageable, + Projection projection) { try { Context context = obtainContext(); Collection collection = collectionService.find(context, collectionId); @@ -99,5 +108,5 @@ public Page getLicenses(@Nullable HttpServletRequest request, throw new RuntimeException(e); } } - + // END TAMU Customization - proxy license step - get available licenses slight refactor of getLicense } diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index f03cc6e15539..e9472d73daab 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -14,8 +14,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -73,7 +73,7 @@ * @author Andrea Bollini (andrea.bollini at 4science.it) * @author Pasquale Cavallo (pasquale.cavallo at 4science.it) */ -@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME) +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.PLURAL_NAME) public class WorkspaceItemRestRepository extends DSpaceRestRepository implements ReloadableEntityObjectRepository { @@ -251,7 +251,7 @@ public Iterable upload(Context context, HttpServletRequest re } SubmissionConfig submissionConfig = - submissionConfigService.getSubmissionConfigByCollection(collection.getHandle()); + submissionConfigService.getSubmissionConfigByCollection(collection); List result = null; List records = new ArrayList<>(); try { @@ -300,6 +300,7 @@ public Iterable upload(Context context, HttpServletRequest re if (UploadableStep.class.isAssignableFrom(stepClass) && !((UploadableStep) stepInstance).isExclusiveMatchingStepId()) { // if (UploadableStep.class.isAssignableFrom(stepClass)) { + // END TAMU Customization - proxy license step - respect exclusivity of uploadable step UploadableStep uploadableStep = (UploadableStep) stepInstance; for (MultipartFile mpFile : uploadfiles) { ErrorRest err = uploadableStep.upload(context, diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/SubmissionService.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/SubmissionService.java index e05195da2390..5a401856f62e 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/SubmissionService.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/SubmissionService.java @@ -8,20 +8,15 @@ package org.dspace.app.rest.submit; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.Part; -import org.apache.commons.io.IOUtils; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.atteo.evo.inflector.English; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; @@ -32,9 +27,11 @@ import org.dspace.app.rest.model.CheckSumRest; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.PotentialDuplicateRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.step.DataCCLicense; +import org.dspace.app.rest.model.step.DataDuplicateDetection; import org.dspace.app.rest.model.step.DataUpload; import org.dspace.app.rest.model.step.UploadBitstreamRest; import org.dspace.app.rest.projection.Projection; @@ -53,11 +50,14 @@ import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.CollectionService; +import org.dspace.content.service.DuplicateDetectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.content.virtual.PotentialDuplicate; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Utils; +import org.dspace.discovery.SearchServiceException; import org.dspace.license.service.CreativeCommonsService; import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; @@ -70,11 +70,22 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.rest.webmvc.json.patch.PatchException; import org.springframework.jdbc.datasource.init.UncategorizedScriptException; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +// TAMU Customization - proxy license step +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.Optional; + +import jakarta.servlet.http.Part; + +import org.apache.commons.io.IOUtils; +// END TAMU Customization - proxy license step + /** * Service to manipulate in-progress submissions. * @@ -87,6 +98,7 @@ public class SubmissionService { // TAMU Customization - proxy license step private static final String FORM_DATA_SECTION_ID = "sectionId"; + // END TAMU Customization - proxy license step @Autowired protected ConfigurationService configurationService; @@ -110,6 +122,8 @@ public class SubmissionService { @Autowired private org.dspace.app.rest.utils.Utils utils; private SubmissionConfigService submissionConfigService; + @Autowired + private DuplicateDetectionService duplicateDetectionService; public SubmissionService() throws SubmissionConfigReaderException { submissionConfigService = SubmissionServiceFactory.getInstance().getSubmissionConfigService(); @@ -227,7 +241,7 @@ public UploadBitstreamRest buildUploadBitstream(ConfigurationService configurati data.setCheckSum(checksum); data.setSizeBytes(source.getSizeBytes()); data.setUrl(configurationService.getProperty("dspace.server.url") + "/api/" + BitstreamRest.CATEGORY + "/" + - English.plural(BitstreamRest.NAME) + "/" + source.getID() + "/content"); + BitstreamRest.PLURAL_NAME + "/" + source.getID() + "/content"); return data; } @@ -249,7 +263,7 @@ public XmlWorkflowItem createWorkflowItem(Context context, String requestUriList if (StringUtils.isBlank(requestUriListString)) { throw new UnprocessableEntityException("Malformed body..." + requestUriListString); } - String regex = "\\/api\\/" + WorkspaceItemRest.CATEGORY + "\\/" + English.plural(WorkspaceItemRest.NAME) + String regex = "\\/api\\/" + WorkspaceItemRest.CATEGORY + "\\/" + WorkspaceItemRest.PLURAL_NAME + "\\/"; String[] split = requestUriListString.split(regex, 2); if (split.length != 2) { @@ -322,6 +336,51 @@ public DataCCLicense getDataCCLicense(InProgressSubmission obj) return result; } + /** + * Prepare section data containing a list of potential duplicates, for use in submission steps. + * This method belongs in SubmissionService and not DuplicateDetectionService because it depends on + * the DataDuplicateDetection class which only appears in the REST project. + * + * @param context DSpace context + * @param obj The in-progress submission object + * @return A DataDuplicateDetection object which implements SectionData for direct use in + * a submission step (see DuplicateDetectionStep) + * @throws SearchServiceException if an error is encountered during Discovery search + */ + public DataDuplicateDetection getDataDuplicateDetection(Context context, InProgressSubmission obj) + throws SearchServiceException { + // Test for a valid object or throw a not found exception + if (obj == null) { + throw new ResourceNotFoundException("Duplicate data step could not find valid in-progress submission obj"); + } + // Initialise an empty section data object + DataDuplicateDetection data = new DataDuplicateDetection(); + + // Get the item for this submission object, throw a not found exception if null + Item item = obj.getItem(); + if (item == null) { + throw new ResourceNotFoundException("Duplicate data step could not find valid item for the" + + " current in-progress submission obj id=" + obj.getID()); + } + // Initialise empty list of PotentialDuplicateRest objects for use in the section data object + List potentialDuplicateRestList = new LinkedList<>(); + + // Get discovery search result for a duplicate detection search based on this item and populate + // the list of REST objects + List potentialDuplicates = duplicateDetectionService.getPotentialDuplicates(context, item); + for (PotentialDuplicate potentialDuplicate : potentialDuplicates) { + // Convert and add the potential duplicate to the list + potentialDuplicateRestList.add(converter.toRest( + potentialDuplicate, utils.obtainProjection())); + } + + // Set the final duplicates list of the section data object + data.setPotentialDuplicates(potentialDuplicateRestList); + + // Return section data + return data; + } + /** * Utility method used by the {@link WorkspaceItemRestRepository} and * {@link WorkflowItemRestRepository} to deal with the upload in an inprogress @@ -336,10 +395,9 @@ public DataCCLicense getDataCCLicense(InProgressSubmission obj) */ public List uploadFileToInprogressSubmission(Context context, HttpServletRequest request, AInprogressSubmissionRest wsi, InProgressSubmission source, MultipartFile file) { - // TAMU Customization - proxy license step Optional sectionId = getSectionId(request); - + // END TAMU Customization - proxy license step List errors = new ArrayList(); SubmissionConfig submissionConfig = submissionConfigService.getSubmissionConfigByName(wsi.getSubmissionDefinition().getName()); @@ -360,7 +418,6 @@ public List uploadFileToInprogressSubmission(Context context, HttpSer stepClass = loader.loadClass(stepConfig.getProcessingClassName()); if (UploadableStep.class.isAssignableFrom(stepClass)) { Object stepInstance = stepClass.newInstance(); - // TAMU Customization - proxy license step - exclusive and only when matching step id boolean isExclusiveMatchingStepId = ((UploadableStep) stepInstance).isExclusiveMatchingStepId(); if (isExclusiveMatchingStepId) { @@ -372,6 +429,8 @@ public List uploadFileToInprogressSubmission(Context context, HttpSer } else { stepInstancesAndConfigs.add(new Object[] {stepInstance, stepConfig}); } + // stepInstancesAndConfigs.add(new Object[] {stepInstance, stepConfig}); + // END TAMU Customization - proxy license step - exclusive and only when matching step id } } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/UploadableStep.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/UploadableStep.java index e3895be514ab..3ec77b24ade1 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/UploadableStep.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/UploadableStep.java @@ -16,20 +16,22 @@ import org.springframework.web.multipart.MultipartFile; /** - * TAMU Customized interface for submission Steps that need to deal with file upload + * The interface for submission Steps that need to deal with file upload * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it) */ public interface UploadableStep extends RestProcessingStep { + // TAMU Customization - proxy license step /** - * TAMU Customization - Method to specify step only invokes upload exclusively + * Method to specify step only invokes upload exclusively * when matching step id from multipart form */ - default public boolean isExclusiveMatchingStepId() { + public default boolean isExclusiveMatchingStepId() { return false; } + // END TAMU Customization - proxy license step /** * The method to implement to support upload of a file in the submission section (aka panel / step) diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java index 3c716e05bea1..e69ba241feda 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseAddPatchOperation.java @@ -7,26 +7,34 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.BooleanUtils; -import org.dspace.app.rest.utils.ProxyLicenseUtils; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; +import org.dspace.content.LicenseUtils; import org.dspace.content.service.ItemService; import org.dspace.core.Context; +import org.dspace.eperson.EPerson; import org.springframework.beans.factory.annotation.Autowired; +// TAMU Customization - proxy license step +import org.dspace.app.rest.utils.ProxyLicenseUtils; +// END TAMU Customization - proxy license step + /** - * TAMU Customization - Customized Submission "add" PATCH operation + * Submission "add" PATCH operation * - * To accept/reject the granted license: + * To accept/reject the license. * * Example: * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: * application/json" -d '[{ "op": "add", "path": "/sections/license/granted", "value":"true"}]' * * + * Please note that according to the JSON Patch specification RFC6902 a + * subsequent add operation on the "granted" path will have the effect to + * replace the previous granted license with a new one. + * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ public class LicenseAddPatchOperation extends AddPatchOperation { @@ -62,12 +70,27 @@ void add(Context context, HttpServletRequest currentRequest, InProgressSubmissio } Item item = source.getItem(); - + // TAMU Customization - proxy license step if (grant) { ProxyLicenseUtils.grantLicense(context, item); } else { ProxyLicenseUtils.revokeLicense(context, item); } + /* + EPerson submitter = context.getCurrentUser(); + + // remove any existing DSpace license (just in case the user + // accepted it previously) + itemService.removeDSpaceLicense(context, item); + + if (grant) { + String license = LicenseUtils.getLicenseText(context.getCurrentLocale(), source.getCollection(), item, + submitter); + + LicenseUtils.grantLicense(context, item, license, null); + } + */ + // END TAMU Customization - proxy license step } } diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java index 2130364e1580..531cd7550688 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseRemovePatchOperation.java @@ -7,19 +7,21 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.utils.ProxyLicenseUtils; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; +// TAMU Customization - proxy license step +import org.dspace.app.rest.utils.ProxyLicenseUtils; +// END TAMU Customization - proxy license step + /** - * TAMU Customization - Customized Submission License "remove" patch operation. + * Submission License "remove" patch operation. * - * To revoke previous granted license: + * To remove a previous granted license: * * Example: * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: @@ -37,7 +39,11 @@ public class LicenseRemovePatchOperation extends RemovePatchOperation { void remove(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, Object value) throws Exception { Item item = source.getItem(); + // TAMU Customization - proxy license step + // itemService.removeDSpaceLicense(context, item); ProxyLicenseUtils.revokeLicense(context, item); + // END TAMU Customization - proxy license step + } @Override diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java index cf05cb74c727..188fda85bfb5 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseReplacePatchOperation.java @@ -7,13 +7,78 @@ */ package org.dspace.app.rest.submit.factory.impl; +// TAMU Customization - proxy license step +/* +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.BooleanUtils; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.content.LicenseUtils; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; +*/ +// END TAMU Customization - proxy license step + /** - * TAMU Customization - Customized Submission "replace" patch operation + * Submission "replace" patch operation * * {@link LicenseAddPatchOperation} * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ +// TAMU Customization - proxy license step public class LicenseReplacePatchOperation extends LicenseAddPatchOperation { +} +/* +public class LicenseReplacePatchOperation extends ReplacePatchOperation { + + @Autowired + ItemService itemService; + + @Override + void replace(Context context, HttpServletRequest currentRequest, InProgressSubmission source, String path, + Object value) throws Exception { + + Boolean grant = null; + // we are friendly with the client and accept also a string representation for the boolean + if (value instanceof String) { + grant = BooleanUtils.toBooleanObject((String) value); + } else { + grant = (Boolean) value; + } + + if (grant == null) { + throw new IllegalArgumentException( + "Value is not a valid boolean expression (permitted value: on/off, true/false and yes/no"); + } + + Item item = source.getItem(); + EPerson submitter = context.getCurrentUser(); + + // remove any existing DSpace license (just in case the user + // accepted it previously) + itemService.removeDSpaceLicense(context, item); + + if (grant) { + String license = LicenseUtils.getLicenseText(context.getCurrentLocale(), source.getCollection(), item, + submitter); + + LicenseUtils.grantLicense(context, item, license, null); + } + } + + @Override + protected Class getArrayClassForEvaluation() { + return String[].class; + } + + @Override + protected Class getClassForEvaluation() { + return String.class; + } } +*/ +// END TAMU Customization - proxy license step diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedAddPatchOperation.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedAddPatchOperation.java index afd7d893f9ab..90cedb1b7043 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedAddPatchOperation.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedAddPatchOperation.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.submit.factory.impl; import java.io.File; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.utils.ProxyLicenseUtils; diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedRemovePatchOperation.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedRemovePatchOperation.java index 8c61efc0acd3..9f7ef1f266ed 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedRemovePatchOperation.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/factory/impl/LicenseSelectedRemovePatchOperation.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.submit.factory.impl; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/step/ProxyLicenseStep.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/step/ProxyLicenseStep.java index 107cd90bd0d9..e53b78397d5c 100644 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/step/ProxyLicenseStep.java +++ b/dspace/modules/server/src/main/java/org/dspace/app/rest/submit/step/ProxyLicenseStep.java @@ -2,7 +2,7 @@ import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -27,6 +27,11 @@ import org.dspace.core.Context; import org.springframework.web.multipart.MultipartFile; +/** + * TAMU Customization + * + * Proxy License step for DSpace Spring Rest. + */ public class ProxyLicenseStep extends LicenseStep implements UploadableStep { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ProxyLicenseStep.class); diff --git a/dspace/modules/server/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace/modules/server/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java deleted file mode 100644 index 464e23b58798..000000000000 --- a/dspace/modules/server/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ /dev/null @@ -1,271 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.utils; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; -import static javax.mail.internet.MimeUtility.encodeText; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.StringUtils; -import org.apache.tomcat.util.http.FastHttpDateFormat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; - -/** - * This class takes data from the Bitstream/File that has to be send. It'll then digest this input and save it in - * its local variables. - * When calling {{@link #initialiseHeaders()}}, the input and information will be used to set the proper headers - * with this info and return an Object of {@link HttpHeaders} to be used in the response that'll be generated - */ -public class HttpHeadersInitializer { - - protected final Logger log = LoggerFactory.getLogger(this.getClass()); - - private static final String METHOD_HEAD = "HEAD"; - private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; - private static final String CONTENT_TYPE_MULTITYPE_WITH_BOUNDARY = "multipart/byteranges; boundary=" + - MULTIPART_BOUNDARY; - public static final String CONTENT_DISPOSITION_INLINE = "inline"; - public static final String CONTENT_DISPOSITION_ATTACHMENT = "attachment"; - private static final String IF_NONE_MATCH = "If-None-Match"; - private static final String IF_MODIFIED_SINCE = "If-Modified-Since"; - private static final String ETAG = "ETag"; - private static final String IF_MATCH = "If-Match"; - private static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; - private static final String CONTENT_TYPE = "Content-Type"; - private static final String ACCEPT_RANGES = "Accept-Ranges"; - private static final String BYTES = "bytes"; - private static final String LAST_MODIFIED = "Last-Modified"; - private static final String EXPIRES = "Expires"; - private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - private static final String IMAGE = "image"; - private static final String ACCEPT = "Accept"; - private static final String CONTENT_DISPOSITION = "Content-Disposition"; - private static final String CONTENT_DISPOSITION_FORMAT = "%s;filename=\"%s\""; - private static final String CACHE_CONTROL = "Cache-Control"; - - private int bufferSize = 1000000; - - private static final long DEFAULT_EXPIRE_TIME = 60L * 60L * 1000L; - - //no-cache so request is always performed for logging - private static final String CACHE_CONTROL_SETTING = "private,no-cache"; - - private HttpServletRequest request; - private HttpServletResponse response; - private String contentType; - private String disposition; - private long lastModified; - private long length; - private String fileName; - private String checksum; - - public HttpHeadersInitializer() { - //Convert to BufferedInputStream so we can re-read the stream - } - - public HttpHeadersInitializer with(HttpServletRequest httpRequest) { - request = httpRequest; - return this; - } - - public HttpHeadersInitializer with(HttpServletResponse httpResponse) { - response = httpResponse; - return this; - } - - public HttpHeadersInitializer withLength(long length) { - this.length = length; - return this; - } - - public HttpHeadersInitializer withFileName(String fileName) { - this.fileName = fileName; - return this; - } - - public HttpHeadersInitializer withChecksum(String checksum) { - this.checksum = checksum; - return this; - } - - public HttpHeadersInitializer withMimetype(String mimetype) { - this.contentType = mimetype; - return this; - } - - public HttpHeadersInitializer withLastModified(long lastModified) { - this.lastModified = lastModified; - return this; - } - - public HttpHeadersInitializer withBufferSize(int bufferSize) { - if (bufferSize > 0) { - this.bufferSize = bufferSize; - } - return this; - } - public HttpHeadersInitializer withDisposition(String contentDisposition) { - this.disposition = contentDisposition; - return this; - } - - /** - * This method will be called to create a {@link HttpHeaders} object which will contain the headers needed - * to form a proper response when returning the Bitstream/File - * @return A {@link HttpHeaders} object containing the information for the Bitstream/File to be sent - * @throws IOException If something goes wrong - */ - public HttpHeaders initialiseHeaders() throws IOException { - - HttpHeaders httpHeaders = new HttpHeaders(); - // Validate and process range ------------------------------------------------------------- - - log.debug("Content-Type : {}", contentType); - //TODO response.reset() => Can be re-instated/investigated once we upgrade to Spring 5.2.9, see issue #3056 - // Initialize response. - response.setBufferSize(bufferSize); - if (contentType != null) { - httpHeaders.put(CONTENT_TYPE, Collections.singletonList(contentType)); - } - httpHeaders.put(ACCEPT_RANGES, Collections.singletonList(BYTES)); - if (checksum != null) { - httpHeaders.put(ETAG, Collections.singletonList(checksum)); - } - httpHeaders.put(LAST_MODIFIED, Collections.singletonList(FastHttpDateFormat.formatDate(lastModified))); - httpHeaders.put(EXPIRES, Collections.singletonList(FastHttpDateFormat.formatDate( - System.currentTimeMillis() + DEFAULT_EXPIRE_TIME))); - - //No-cache so that we can log every download - httpHeaders.put(CACHE_CONTROL, Collections.singletonList(CACHE_CONTROL_SETTING)); - - if (isNullOrEmpty(disposition)) { - if (contentType == null) { - contentType = APPLICATION_OCTET_STREAM; - } else if (!contentType.startsWith(IMAGE)) { - String accept = request.getHeader(ACCEPT); - disposition = accept != null && accepts(accept, - contentType) ? CONTENT_DISPOSITION_INLINE : - CONTENT_DISPOSITION_ATTACHMENT; - } - - } - - httpHeaders.put(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, - Collections.singletonList(HttpHeaders.ACCEPT_RANGES)); - // TAMU Customization - without knowing details only add Content-Disposition header if disposition defined - // httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - // disposition, - // encodeText(fileName)))); - if (!isNullOrEmpty(disposition)) { - httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - disposition, - encodeText(fileName)))); - } - - log.debug("Content-Disposition : {}", disposition); - - // Content phase - // TAMU Customization - return headers regardless of request verb - // if (METHOD_HEAD.equals(request.getMethod())) { - // log.debug("HEAD request - skipping content"); - // return null; - // } - - return httpHeaders; - - } - - /** - * This method will validate whether or not the given Response/Request/Information/Variables are valid. - * If they're invalid, the Response shouldn't be given. - * This will do null checks on the response, request, inputstream and filename. - * Other than this, it'll check Request headers to see if their information is correct. - * @return - * @throws IOException - */ - public boolean isValid() throws IOException { - if (response == null || request == null) { - return false; - } - - if (StringUtils.isEmpty(fileName)) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - return false; - } - - // Validate request headers for caching --------------------------------------------------- - // If-None-Match header should contain "*" or ETag. If so, then return 304. - String ifNoneMatch = request.getHeader(IF_NONE_MATCH); - if (nonNull(ifNoneMatch) && matches(ifNoneMatch, checksum)) { - log.debug("If-None-Match header should contain \"*\" or ETag. If so, then return 304."); - response.setHeader(ETAG, checksum); // Required in 304. - response.sendError(HttpServletResponse.SC_NOT_MODIFIED); - return false; - } - - // If-Modified-Since header should be greater than LastModified. If so, then return 304. - // This header is ignored if any If-None-Match header is specified. - long ifModifiedSince = request.getDateHeader(IF_MODIFIED_SINCE); - if (isNull(ifNoneMatch) && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) { - log.debug("If-Modified-Since header should be greater than LastModified. If so, then return 304."); - response.setHeader(ETAG, checksum); // Required in 304. - response.sendError(HttpServletResponse.SC_NOT_MODIFIED); - return false; - } - - // Validate request headers for resume ---------------------------------------------------- - - // If-Match header should contain "*" or ETag. If not, then return 412. - String ifMatch = request.getHeader(IF_MATCH); - if (nonNull(ifMatch) && !matches(ifMatch, checksum)) { - log.error("If-Match header should contain \"*\" or ETag. If not, then return 412."); - response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); - return false; - } - - // If-Unmodified-Since header should be greater than LastModified. If not, then return 412. - long ifUnmodifiedSince = request.getDateHeader(IF_UNMODIFIED_SINCE); - if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) { - log.error("If-Unmodified-Since header should be greater than LastModified. If not, then return 412."); - response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); - return false; - } - - return true; - } - - - private static boolean isNullOrEmpty(String disposition) { - return StringUtils.isBlank(disposition); - } - - - private static boolean accepts(String acceptHeader, String toAccept) { - String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*"); - Arrays.sort(acceptValues); - - return Arrays.binarySearch(acceptValues, toAccept) > -1 - || Arrays.binarySearch(acceptValues, toAccept.replaceAll("/.*$", "/*")) > -1 - || Arrays.binarySearch(acceptValues, "*/*") > -1; - } - - private static boolean matches(String matchHeader, String toMatch) { - String[] matchValues = matchHeader.split("\\s*,\\s*"); - Arrays.sort(matchValues); - return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; - } - -}