diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java
index 50fff6228fce..0d2ae242954a 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java
@@ -86,8 +86,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
-import static org.fao.geonet.resources.Resources.DEFAULT_LOGO_EXTENSION;
-
public class BaseMetadataIndexer implements IMetadataIndexer, ApplicationEventPublisherAware {
@Autowired
@@ -456,7 +454,9 @@ public void indexMetadata(final String metadataId,
}
}
- String logoUUID = null;
+ // Group logo are in the harvester folder and contains extension in file name
+ boolean groupLogoAdded = false;
+
if (groupOwner != null) {
final Optional groupOpt = groupRepository.findById(groupOwner);
if (groupOpt.isPresent()) {
@@ -466,37 +466,29 @@ public void indexMetadata(final String metadataId,
if (group.getWebsite() != null && !group.getWebsite().isEmpty() && preferGroup) {
fields.put(Geonet.IndexFieldNames.GROUP_WEBSITE, group.getWebsite());
}
- if (group.getLogo() != null && preferGroup) {
- logoUUID = group.getLogo();
- }
- }
- }
-
- // Group logo are in the harvester folder and contains extension in file name
- boolean added = false;
- if (StringUtils.isNotEmpty(logoUUID)) {
- final Path harvesterLogosDir = resources.locateHarvesterLogosDir(getServiceContext());
- try (Resources.ResourceHolder logo = resources.getImage(getServiceContext(), logoUUID, harvesterLogosDir)) {
- if (logo != null) {
- added = true;
- fields.put(Geonet.IndexFieldNames.LOGO,
- "/images/harvesting/" + logo.getPath().getFileName());
+ if (preferGroup && StringUtils.isNotEmpty(group.getLogo())) {
+ final Path harvesterLogosDir = resources.locateHarvesterLogosDir(getServiceContext());
+ try (Resources.ResourceHolder logo = resources.getImage(getServiceContext(), group.getLogo(), harvesterLogosDir)) {
+ if (logo != null) {
+ groupLogoAdded = true;
+ fields.put(Geonet.IndexFieldNames.LOGO,
+ "/srv/api/groups/" + group.getId() + "/logo");
+ }
+ }
}
}
}
- // If not available, use the local catalog logo
- if (!added) {
+ // If not available, use the local catalog or the harvester logo
+ if (!groupLogoAdded) {
Source sourceCatalogue = sourceRepository.findOneByUuid(source);
- logoUUID =
- sourceCatalogue != null
- && StringUtils.isNotEmpty(sourceCatalogue.getLogo())
- ? sourceCatalogue.getLogo() : source + DEFAULT_LOGO_EXTENSION;
final Path logosDir = resources.locateLogosDir(getServiceContext());
- try (Resources.ResourceHolder image = resources.getImage(getServiceContext(), logoUUID, logosDir)) {
- if (image != null) {
- fields.put(Geonet.IndexFieldNames.LOGO,
- "/images/logos/" + logoUUID);
+ if (sourceCatalogue != null && StringUtils.isNotEmpty(sourceCatalogue.getLogo())) {
+ try (Resources.ResourceHolder image = resources.getImage(getServiceContext(), sourceCatalogue.getLogo(), logosDir)) {
+ if (image != null) {
+ fields.put(Geonet.IndexFieldNames.LOGO,
+ "/srv/api/sources/" + sourceCatalogue.getUuid() + "/logo");
+ }
}
}
}
diff --git a/core/src/main/java/org/fao/geonet/resources/FileResources.java b/core/src/main/java/org/fao/geonet/resources/FileResources.java
index 5119f936d50d..3e647c84d5bf 100644
--- a/core/src/main/java/org/fao/geonet/resources/FileResources.java
+++ b/core/src/main/java/org/fao/geonet/resources/FileResources.java
@@ -36,7 +36,7 @@ public class FileResources extends Resources {
try (DirectoryStream possibleLogos = Files.newDirectoryStream(logosDir, imageName + ".*")) {
for (final Path next: possibleLogos) {
String ext = FilenameUtils.getExtension(next.getFileName().toString());
- if (IMAGE_EXTENSIONS.contains(ext.toLowerCase())) {
+ if (IMAGE_EXTENSIONS.contains(ext.toLowerCase()) || "svg".equalsIgnoreCase(ext)) {
return next;
}
}
diff --git a/core/src/main/java/org/fao/geonet/resources/ResourceFilter.java b/core/src/main/java/org/fao/geonet/resources/ResourceFilter.java
index d89883aa0e20..7f74e9c1f3f1 100644
--- a/core/src/main/java/org/fao/geonet/resources/ResourceFilter.java
+++ b/core/src/main/java/org/fao/geonet/resources/ResourceFilter.java
@@ -28,8 +28,10 @@
import jeeves.config.springutil.JeevesDelegatingFilterProxy;
import org.fao.geonet.NodeInfo;
import org.fao.geonet.domain.Pair;
+import org.fao.geonet.domain.Source;
import org.fao.geonet.kernel.GeonetworkDataDirectory;
import org.fao.geonet.kernel.setting.SettingManager;
+import org.fao.geonet.repository.SourceRepository;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.ServletWebRequest;
@@ -40,8 +42,11 @@
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
+import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
+import static com.google.common.net.MediaType.SVG_UTF_8;
+
/**
* Servlet for serving up resources located in GeoNetwork data directory. For example, this solves a
* largely historical issue because logos are hardcoded across the application to be in
@@ -52,8 +57,10 @@
* User: jeichar Date: 1/17/12 Time: 4:03 PM
*/
public class ResourceFilter implements Filter {
+ public static final String DEFAULT_LOGO = "GN3.png";
private static final int FIVE_DAYS = 60 * 60 * 24 * 5;
private static final int SIX_HOURS = 60 * 60 * 6;
+ public static final String IMAGES_LOGOS_FOLDER = "images/logos/";
private FilterConfig config;
private ServletContext servletContext;
private volatile Pair defaultImage;
@@ -94,13 +101,17 @@ public Instance(ServletRequest request, ServletResponse response) throws IOExcep
this.siteId = applicationContext.getBean(SettingManager.class).getSiteId();
this.resourcesDir = resources.locateResourcesDir(servletContext, applicationContext);
this.schemaPublicationDir = applicationContext.getBean(GeonetworkDataDirectory.class).getSchemaPublicationDir();
+ SourceRepository sourceRepository = applicationContext.getBean(SourceRepository.class);
+ this.nodeId = applicationContext.getBean(NodeInfo.class).getId();
if (defaultImage == null) {
- defaultImage = resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".png", new byte[0], -1);
+ Optional catalogues = sourceRepository.findById(this.nodeId);
+ String defaultImageName = IMAGES_LOGOS_FOLDER + (
+ catalogues.isPresent() ? catalogues.get().getLogo() : DEFAULT_LOGO);
+ defaultImage = resources.loadResource(resourcesDir, servletContext, appPath, defaultImageName, new byte[0], -1);
}
- this.nodeId = applicationContext.getBean(NodeInfo.class).getId();
if (!faviconMap.containsKey(nodeId)) {
final byte[] defaultImageBytes = defaultImage.one();
- addFavIcon(nodeId, resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".ico",
+ addFavIcon(nodeId, resources.loadResource(resourcesDir, servletContext, appPath, IMAGES_LOGOS_FOLDER + siteId + ".ico",
defaultImageBytes, -1));
}
@@ -136,8 +147,8 @@ public void execute() throws IOException {
httpServletResponse.setContentType(contentType);
httpServletResponse.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
- if (filename.equals("images/logos/" + siteId + ".ico")) {
- favicon = resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".ico", favicon.one(),
+ if (filename.equals(IMAGES_LOGOS_FOLDER + siteId + ".ico")) {
+ favicon = resources.loadResource(resourcesDir, servletContext, appPath, IMAGES_LOGOS_FOLDER + siteId + ".ico", favicon.one(),
favicon.two());
addFavIcon(nodeId, favicon);
@@ -186,6 +197,8 @@ private String extensionToMediaType(String ext) {
contentType = MediaType.APPLICATION_XML_VALUE;
} else if(ext.equals("txt")) {
contentType = MediaType.TEXT_PLAIN_VALUE;
+ } else if (ext.equals("svg")) {
+ contentType = SVG_UTF_8.toString();
} else {
contentType = "image/" + ext;
}
diff --git a/core/src/main/java/org/fao/geonet/resources/Resources.java b/core/src/main/java/org/fao/geonet/resources/Resources.java
index 157877c9ee2c..1bec918fbcc5 100644
--- a/core/src/main/java/org/fao/geonet/resources/Resources.java
+++ b/core/src/main/java/org/fao/geonet/resources/Resources.java
@@ -62,7 +62,6 @@
*/
public abstract class Resources {
public static final String BLANK_LOGO = "blank.png";
- public static final String DEFAULT_LOGO_EXTENSION = ".png";
protected final static Set IMAGE_READ_SUFFIXES;
protected final static Set IMAGE_WRITE_SUFFIXES;
diff --git a/schemas/iso19115-3.2018/src/test/resources/UpperRhineCastles-ISO19115-3-full-view.html b/schemas/iso19115-3.2018/src/test/resources/UpperRhineCastles-ISO19115-3-full-view.html
index b57a8a60484a..ccccbbcffd95 100644
--- a/schemas/iso19115-3.2018/src/test/resources/UpperRhineCastles-ISO19115-3-full-view.html
+++ b/schemas/iso19115-3.2018/src/test/resources/UpperRhineCastles-ISO19115-3-full-view.html
@@ -848,7 +848,7 @@
-
+
diff --git a/services/src/main/java/org/fao/geonet/api/LogoUtils.java b/services/src/main/java/org/fao/geonet/api/LogoUtils.java
new file mode 100644
index 000000000000..58c736cb1232
--- /dev/null
+++ b/services/src/main/java/org/fao/geonet/api/LogoUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2001-2026 Food and Agriculture Organization of the
+ * United Nations (FAO-UN), United Nations World Food Programme (WFP)
+ * and United Nations Environment Programme (UNEP)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
+ * Rome - Italy. email: geonetwork@osgeo.org
+ */
+
+package org.fao.geonet.api;
+
+import jeeves.server.context.ServiceContext;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.fao.geonet.api.records.attachments.AttachmentsApi;
+import org.fao.geonet.resources.Resources;
+import org.springframework.web.context.request.WebRequest;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+
+public final class LogoUtils {
+ public static final String API_GET_LOGO_NOTE = "If last-modified header "
+ + "is present it is used to check if the logo has been modified since "
+ + "the header date. If it hasn't been modified returns an empty 304 Not"
+ + " Modified response. If modified returns the image. If there is "
+ + "no logo then returns a transparent 1x1 px PNG image.";
+
+ private static final int SIX_HOURS = 60 * 60 * 6;
+
+ private static final String TRANSPARENT_1_X_1_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII";
+
+ private static final byte[] TRANSPARENT_1_X_1_PNG = org.apache.commons.codec.binary.Base64.decodeBase64(TRANSPARENT_1_X_1_PNG_BASE64);
+
+ private LogoUtils() {
+ }
+
+ public static Resources.ResourceHolder getImage(Resources resources,
+ ServiceContext serviceContext,
+ String logoRef) throws IOException {
+ final Path logosDir = resources.locateLogosDir(serviceContext);
+ final Path harvesterLogosDir = resources.locateHarvesterLogosDir(serviceContext);
+ Resources.ResourceHolder image = null;
+ if (isLocalLogoRef(logoRef)) {
+ image = resources.getImage(serviceContext, logoRef, logosDir);
+ if (image == null) {
+ image = resources.getImage(serviceContext, logoRef, harvesterLogosDir);
+ }
+ }
+ return image;
+ }
+
+ public static void writeImageOrTransparentLogo(WebRequest webRequest,
+ HttpServletResponse response,
+ Resources.ResourceHolder image) throws IOException {
+ if (image != null) {
+ FileTime lastModifiedTime = image.getLastModifiedTime();
+ response.setDateHeader("Expires", System.currentTimeMillis() + SIX_HOURS * 1000L);
+ if (webRequest.checkNotModified(lastModifiedTime.toMillis())) {
+ return;
+ }
+ response.setContentType(AttachmentsApi.getFileContentType(image.getPath().getFileName().toString()));
+ response.setContentLength((int) Files.size(image.getPath()));
+ response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
+ FileUtils.copyFile(image.getPath().toFile(), response.getOutputStream());
+ return;
+ }
+
+ if (webRequest.checkNotModified(0L)) {
+ return;
+ }
+ response.setContentType("image/png");
+ response.setContentLength(TRANSPARENT_1_X_1_PNG.length);
+ response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
+ response.getOutputStream().write(TRANSPARENT_1_X_1_PNG);
+ }
+
+ private static boolean isLocalLogoRef(String logoRef) {
+ return StringUtils.isNotBlank(logoRef)
+ && !logoRef.startsWith("http://")
+ && !logoRef.startsWith("https://")
+ && !logoRef.startsWith("https//");
+ }
+}
diff --git a/services/src/main/java/org/fao/geonet/api/groups/GroupsApi.java b/services/src/main/java/org/fao/geonet/api/groups/GroupsApi.java
index 0add43272b26..4f96d8961c3d 100644
--- a/services/src/main/java/org/fao/geonet/api/groups/GroupsApi.java
+++ b/services/src/main/java/org/fao/geonet/api/groups/GroupsApi.java
@@ -31,19 +31,16 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
-import java.util.regex.Pattern;
import jeeves.server.UserSession;
import jeeves.server.context.ServiceContext;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.api.API;
import org.fao.geonet.api.ApiParams;
import org.fao.geonet.api.ApiUtils;
+import org.fao.geonet.api.LogoUtils;
import org.fao.geonet.api.exception.NotAllowedException;
import org.fao.geonet.api.exception.ResourceNotFoundException;
-import org.fao.geonet.api.records.attachments.AttachmentsApi;
import org.fao.geonet.api.tools.i18n.LanguageUtils;
import org.fao.geonet.api.tools.i18n.TranslationPackBuilder;
import org.fao.geonet.constants.Geonet;
@@ -77,14 +74,13 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
import java.sql.SQLException;
import java.util.*;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static org.fao.geonet.api.LogoUtils.API_GET_LOGO_NOTE;
import static org.springframework.data.jpa.domain.Specification.where;
@RequestMapping(value = {
@@ -111,27 +107,6 @@ public class GroupsApi {
public static final int GROUPNAME_MAX_LENGHT = 32;
- /**
- * API logo note.
- */
- private static final String API_GET_LOGO_NOTE = "If last-modified header "
- + "is present it is used to check if the logo has been modified since "
- + "the header date. If it hasn't been modified returns an empty 304 Not"
- + " Modified response. If modified returns the image. If the group has "
- + "no logo then returns a transparent 1x1 px PNG image.";
- /**
- * Six hours in seconds.
- */
- private static final int SIX_HOURS = 60 * 60 * 6;
- /**
- * Transparent 1x1 px PNG encoded in Base64.
- */
- private static final String TRANSPARENT_1_X_1_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR"
- + "42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
- /**
- * Transparent 1x1 px PNG.
- */
- private static final byte[] TRANSPARENT_1_X_1_PNG = org.apache.commons.codec.binary.Base64.decodeBase64(TRANSPARENT_1_X_1_PNG_BASE64);
/**
* Message source.
*/
@@ -169,20 +144,6 @@ public class GroupsApi {
@Autowired
private PageRepository pageRepository;
- private static Resources.ResourceHolder getImage(Resources resources, ServiceContext serviceContext, Group group) throws IOException {
- final Path logosDir = resources.locateLogosDir(serviceContext);
- final Path harvesterLogosDir = resources.locateHarvesterLogosDir(serviceContext);
- final String logoUUID = group.getLogo();
- Resources.ResourceHolder image = null;
- if (StringUtils.isNotBlank(logoUUID) && !logoUUID.startsWith("http://") && !logoUUID.startsWith("https//")) {
- image = resources.getImage(serviceContext, logoUUID, logosDir);
- if (image == null) {
- image = resources.getImage(serviceContext, logoUUID, harvesterLogosDir);
- }
- }
- return image;
- }
-
/**
* Writes the group logo image to the response. If no image is found, it
* writes a 1x1 transparent PNG. If the request contains cache-related
@@ -220,35 +181,9 @@ public void getGroupLogo(
}
try {
final Resources resources = context.getBean(Resources.class);
- final String logoUUID = group.get().getLogo();
- if (StringUtils.isNotBlank(logoUUID) && !logoUUID.startsWith("http://") && !logoUUID.startsWith("https//")) {
- try (Resources.ResourceHolder image = getImage(resources, serviceContext, group.get())) {
- if (image != null) {
- FileTime lastModifiedTime = image.getLastModifiedTime();
- response.setDateHeader("Expires", System.currentTimeMillis() + SIX_HOURS * 1000L);
- if (webRequest.checkNotModified(lastModifiedTime.toMillis())) {
- // webRequest.checkNotModified sets the right HTTP headers
- return;
- }
- response.setContentType(AttachmentsApi.getFileContentType(image.getPath().getFileName().toString()));
- response.setContentLength((int) Files.size(image.getPath()));
- response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
- FileUtils.copyFile(image.getPath().toFile(), response.getOutputStream());
- return;
- }
- }
- }
-
- // no logo image found. Return a transparent 1x1 png
- FileTime lastModifiedTime = FileTime.fromMillis(0);
- if (webRequest.checkNotModified(lastModifiedTime.toMillis())) {
- return;
+ try (Resources.ResourceHolder image = LogoUtils.getImage(resources, serviceContext, group.get().getLogo())) {
+ LogoUtils.writeImageOrTransparentLogo(webRequest, response, image);
}
- response.setContentType("image/png");
- response.setContentLength(TRANSPARENT_1_X_1_PNG.length);
- response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
- response.getOutputStream().write(TRANSPARENT_1_X_1_PNG);
-
} catch (IOException e) {
Log.error(LOGGER, String.format("There was an error accessing the logo of the group with id '%d'",
groupId));
diff --git a/services/src/main/java/org/fao/geonet/api/harvesting/HarvestersApi.java b/services/src/main/java/org/fao/geonet/api/harvesting/HarvestersApi.java
index 58d434445f9f..de72d0c13e26 100644
--- a/services/src/main/java/org/fao/geonet/api/harvesting/HarvestersApi.java
+++ b/services/src/main/java/org/fao/geonet/api/harvesting/HarvestersApi.java
@@ -34,14 +34,10 @@
import org.fao.geonet.api.ApiUtils;
import org.fao.geonet.api.exception.NoResultsFoundException;
import org.fao.geonet.api.exception.ResourceNotFoundException;
-import org.fao.geonet.domain.AbstractMetadata;
-import org.fao.geonet.domain.HarvestHistory;
-import org.fao.geonet.domain.ISODate;
-import org.fao.geonet.domain.Source;
+import org.fao.geonet.domain.*;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.kernel.datamanager.IMetadataManager;
import org.fao.geonet.kernel.datamanager.IMetadataUtils;
-import org.fao.geonet.kernel.harvest.Common;
import org.fao.geonet.kernel.harvest.HarvestManager;
import org.fao.geonet.kernel.harvest.harvester.AbstractHarvester;
import org.fao.geonet.repository.HarvestHistoryRepository;
@@ -54,14 +50,11 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
-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.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -173,6 +166,34 @@ public HttpEntity assignHarvestedRecordToSource(
return new HttpEntity<>(HttpStatus.NO_CONTENT);
}
+ @io.swagger.v3.oas.annotations.Operation(
+ summary = "Get harvester logo image.",
+ description = "Redirect to source logo endpoint."
+ )
+ @GetMapping(
+ value = "/{harvesterUuid}/logo"
+ )
+ @ResponseStatus(value = HttpStatus.FOUND)
+ public void getHarvesterLogo(
+ @PathVariable
+ String portal,
+ @Parameter(
+ description = "The harvester UUID"
+ )
+ @PathVariable
+ String harvesterUuid,
+ HttpServletResponse response
+ ) throws ResourceNotFoundException, IOException {
+ Source source = sourceRepository.findOneByUuid(harvesterUuid);
+ if (source == null || source.getType() != SourceType.harvester) {
+ throw new ResourceNotFoundException(String.format(
+ "Harvester with UUID '%s' not found.",
+ harvesterUuid));
+ }
+
+ response.sendRedirect("/" + portal + "/api/sources/" + harvesterUuid + "/logo");
+ }
+
@io.swagger.v3.oas.annotations.Operation(
summary = "Reindexes all records of an harvester",
diff --git a/services/src/main/java/org/fao/geonet/api/site/LogosApi.java b/services/src/main/java/org/fao/geonet/api/site/LogosApi.java
index 872751d99e48..d3941354a31f 100644
--- a/services/src/main/java/org/fao/geonet/api/site/LogosApi.java
+++ b/services/src/main/java/org/fao/geonet/api/site/LogosApi.java
@@ -37,7 +37,6 @@
import org.fao.geonet.api.exception.ResourceAlreadyExistException;
import org.fao.geonet.api.exception.ResourceNotFoundException;
import org.fao.geonet.domain.Group;
-import org.fao.geonet.kernel.GeonetworkDataDirectory;
import org.fao.geonet.kernel.setting.SettingManager;
import org.fao.geonet.repository.GroupRepository;
import org.fao.geonet.resources.Resources;
@@ -71,10 +70,8 @@
description = "Logos operations")
@Controller("siteLogos")
public class LogosApi {
- private static final String[] iconExt = {".gif", ".png", ".jpg", ".jpeg"};
+ private static final String[] iconExt = {".gif", ".png", ".jpg", ".jpeg", ".svg"};
- @Autowired
- GeonetworkDataDirectory dataDirectory;
@Autowired
SettingManager settingManager;
diff --git a/services/src/main/java/org/fao/geonet/api/site/SiteApi.java b/services/src/main/java/org/fao/geonet/api/site/SiteApi.java
index ecb23ec77945..67b446c35a82 100644
--- a/services/src/main/java/org/fao/geonet/api/site/SiteApi.java
+++ b/services/src/main/java/org/fao/geonet/api/site/SiteApi.java
@@ -41,18 +41,24 @@
import jeeves.server.context.ServiceContext;
import jeeves.xlink.Processor;
import org.apache.commons.lang3.StringUtils;
-import org.fao.geonet.*;
+import org.fao.geonet.ApplicationContextHolder;
+import org.fao.geonet.GeonetContext;
+import org.fao.geonet.NodeInfo;
+import org.fao.geonet.SystemInfo;
import org.fao.geonet.api.ApiParams;
import org.fao.geonet.api.ApiUtils;
+import org.fao.geonet.api.LogoUtils;
import org.fao.geonet.api.OpenApiConfig;
import org.fao.geonet.api.exception.FeatureNotEnabledException;
import org.fao.geonet.api.exception.NotAllowedException;
+import org.fao.geonet.api.exception.ResourceNotFoundException;
import org.fao.geonet.api.site.model.SettingSet;
import org.fao.geonet.api.site.model.SettingsListResponse;
import org.fao.geonet.api.tools.i18n.LanguageUtils;
import org.fao.geonet.api.users.recaptcha.RecaptchaChecker;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.*;
+import org.fao.geonet.exceptions.BadParameterEx;
import org.fao.geonet.exceptions.OperationAbortedEx;
import org.fao.geonet.index.Status;
import org.fao.geonet.index.es.EsRestClient;
@@ -88,16 +94,15 @@
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.WebRequest;
-import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Collectors;
@@ -813,9 +818,9 @@ public ProxyConfiguration getProxyConfiguration(
@io.swagger.v3.oas.annotations.Operation(
summary = "Set catalog logo",
description = "Logos are stored in the data directory " +
- "resources/images/harvesting as PNG or GIF images. " +
+ "`resources/images/harvesting` images. " +
"When a logo is assigned to the catalog, a new " +
- "image is created in images/logos/.png.")
+ "image is created in `images/logos/.`.")
@RequestMapping(
path = "/logo",
produces = MediaType.APPLICATION_JSON_VALUE,
@@ -852,51 +857,85 @@ public void setLogo(
String nodeUuid = settingManager.getSiteId();
- Resources.ResourceHolder holder = resources.getImage(serviceContext, file, logoDirectory);
- final Path resourcesDir =
- resources.locateResourcesDir(request.getServletContext(), serviceContext.getApplicationContext());
- if (holder == null || holder.getPath() == null) {
- holder = resources.getImage(serviceContext, "images/harvesting/" + file, resourcesDir);
+ Optional siteSourceOpt = sourceRepository.findById(nodeUuid);
+ if (siteSourceOpt.isEmpty()) {
+ throw new ResourceNotFoundException("Unable to find main catalog in source table with uuid '" + nodeUuid + "'.");
}
- try {
- try (InputStream inputStream = Files.newInputStream(holder.getPath())) {
- BufferedImage source = ImageIO.read(inputStream);
+ Source siteSource = siteSourceOpt.get();
+ if (siteSource.getType() != SourceType.portal) {
+ throw new BadParameterEx("Source with uuid '" + nodeUuid + "' is not of the type portal (main catalogue).");
+ }
+ boolean isSvg = file.endsWith(".svg");
+
+ if (asFavicon) {
+ if (isSvg) {
+ throw new IllegalArgumentException("SVG logo can not be used to generate favicon.");
+ }
+
+ Resources.ResourceHolder holder = resources.getImage(serviceContext, file, logoDirectory);
+ final Path resourcesDir =
+ resources.locateResourcesDir(request.getServletContext(), serviceContext.getApplicationContext());
- if (asFavicon) {
+ if (holder == null || holder.getPath() == null) {
+ holder = resources.getImage(serviceContext, "images/harvesting/" + file, resourcesDir);
+ }
+
+ if (holder == null || holder.getPath() == null) {
+ throw new FileNotFoundException("Logo file '" + file + "' not found in resources.");
+ }
+
+ try {
+
+ try (InputStream inputStream = Files.newInputStream(holder.getPath())) {
+ java.awt.image.BufferedImage source = javax.imageio.ImageIO.read(inputStream);
try (Resources.ResourceHolder favicon =
resources.getWritableImage(serviceContext, "images/logos/favicon.png",
resourcesDir)) {
ApiUtils.createFavicon(source, favicon.getPath());
}
- } else {
- try (Resources.ResourceHolder logo =
- resources.getWritableImage(serviceContext,
- "images/logos/" + nodeUuid + ".png",
- resourcesDir);
- Resources.ResourceHolder defaultLogo =
- resources.getWritableImage(serviceContext,
- "images/logo.png", resourcesDir)) {
- if (!file.endsWith(".png")) {
- try (
- OutputStream logoOut = Files.newOutputStream(logo.getPath());
- OutputStream defLogoOut = Files.newOutputStream(defaultLogo.getPath())
- ) {
- ImageIO.write(source, "png", logoOut);
- ImageIO.write(source, "png", defLogoOut);
- }
- } else {
- Files.copy(holder.getPath(), logo.getPath(), StandardCopyOption.REPLACE_EXISTING);
- Files.copy(holder.getPath(), defaultLogo.getPath(),
- StandardCopyOption.REPLACE_EXISTING);
- }
- }
}
+ } finally {
+ holder.close();
}
- } catch (Exception e) {
- throw new Exception(
- "Unable to move uploaded thumbnail to destination directory. Error: " + e.getMessage());
- } finally {
- holder.close();
+ } else {
+ String actualLogo = siteSource.getLogo();
+ if (StringUtils.isNotEmpty(actualLogo)) {
+ resources.deleteImageIfExists(actualLogo, dataDirectory.getResourcesDir().resolve("images").resolve("logos"));
+ }
+ String logoFile = resources.copyLogo(serviceContext,
+ "images" + File.separator + "harvesting" + File.separator + file, nodeUuid);
+
+ siteSource.setLogo(logoFile);
+ sourceRepository.save(siteSource);
+ }
+ }
+
+ @io.swagger.v3.oas.annotations.Operation(
+ summary = "Get catalog logo image.",
+ description = LogoUtils.API_GET_LOGO_NOTE)
+ @GetMapping(
+ path = "/logo")
+ @ResponseStatus(HttpStatus.OK)
+ @ResponseBody
+ public void getLogo(
+ @Parameter(hidden = true) WebRequest webRequest,
+ HttpServletRequest request,
+ HttpServletResponse response
+ ) {
+ ServiceContext serviceContext = ApiUtils.createServiceContext(request);
+ final Resources resources = serviceContext.getBean(Resources.class);
+ final String siteUuid = settingManager.getSiteId();
+ final String logoRef = sourceRepository.findById(siteUuid)
+ .map(Source::getLogo)
+ .filter(StringUtils::isNotBlank)
+ .orElse(siteUuid);
+
+ try (Resources.ResourceHolder image = LogoUtils.getImage(resources, serviceContext, logoRef)) {
+ LogoUtils.writeImageOrTransparentLogo(webRequest, response, image);
+ } catch (IOException e) {
+ Log.error(Geonet.GEONETWORK + ".api.site",
+ String.format("There was an error accessing the logo for site uuid '%s'", siteUuid));
+ throw new RuntimeException(e);
}
}
diff --git a/services/src/main/java/org/fao/geonet/api/sources/SourcesApi.java b/services/src/main/java/org/fao/geonet/api/sources/SourcesApi.java
index 9edd5a11f274..2a374f7f2fd6 100644
--- a/services/src/main/java/org/fao/geonet/api/sources/SourcesApi.java
+++ b/services/src/main/java/org/fao/geonet/api/sources/SourcesApi.java
@@ -37,13 +37,16 @@
import org.fao.geonet.api.ApiParams;
import org.fao.geonet.api.ApiUtils;
import org.fao.geonet.api.exception.ResourceNotFoundException;
+import org.fao.geonet.api.LogoUtils;
import org.fao.geonet.api.tools.i18n.TranslationPackBuilder;
+import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.*;
import org.fao.geonet.guiapi.search.XsltResponseWriter;
import org.fao.geonet.repository.LanguageRepository;
import org.fao.geonet.repository.SortUtils;
import org.fao.geonet.repository.SourceRepository;
import org.fao.geonet.resources.Resources;
+import org.fao.geonet.utils.Log;
import org.jdom.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
@@ -53,6 +56,7 @@
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -73,6 +77,7 @@
description = "Source catalogue operations")
@Controller("sources")
public class SourcesApi {
+ private static final String LOGGER = Geonet.GEONETWORK + ".api.sources";
@Autowired
SourceRepository sourceRepository;
@@ -133,7 +138,7 @@ public ResponseEntity> getSources(
if (sources == null) {
return ResponseEntity.notFound().build();
}
- String acceptHeader = StringUtils.isBlank(request.getHeader(HttpHeaders.ACCEPT))?MediaType.APPLICATION_JSON_VALUE:request.getHeader(HttpHeaders.ACCEPT);
+ String acceptHeader = StringUtils.isBlank(request.getHeader(HttpHeaders.ACCEPT)) ? MediaType.APPLICATION_JSON_VALUE : request.getHeader(HttpHeaders.ACCEPT);
if (acceptHeader.contains(MediaType.TEXT_HTML_VALUE)) {
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(getSourcesAsHtml(sources));
} else {
@@ -145,12 +150,12 @@ private String getSourcesAsHtml(List sources) throws Exception {
Element sourcesList = new Element("sources");
sources.stream().map(GeonetEntity::asXml).forEach(sourcesList::addContent);
return new XsltResponseWriter(null, "portal")
- .withJson("catalog/locales/en-core.json")
- .withJson("catalog/locales/en-search.json")
- .withXml(sourcesList)
- .withParam("cssClass", "gn-portal")
- .withXsl("xslt/ui-search/portal-list.xsl")
- .asHtml();
+ .withJson("catalog/locales/en-core.json")
+ .withJson("catalog/locales/en-search.json")
+ .withXml(sourcesList)
+ .withParam("cssClass", "gn-portal")
+ .withXsl("xslt/ui-search/portal-list.xsl")
+ .asHtml();
}
@io.swagger.v3.oas.annotations.Operation(
@@ -185,6 +190,49 @@ public Source getSource(
}
}
+ @io.swagger.v3.oas.annotations.Operation(
+ summary = "Get source logo image.",
+ description = LogoUtils.API_GET_LOGO_NOTE)
+ @GetMapping(
+ value = "/{sourceIdentifier}/logo")
+ @ResponseStatus(HttpStatus.OK)
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Logo returned."),
+ @ApiResponse(responseCode = "404", description = "Source not found.",
+ content = @Content(schema = @Schema(implementation = ApiError.class)))
+ })
+ @ResponseBody
+ public void getSourceLogo(
+ @Parameter(
+ description = "Source identifier",
+ required = true
+ )
+ @PathVariable
+ String sourceIdentifier,
+ @Parameter(hidden = true) WebRequest webRequest,
+ HttpServletRequest request,
+ HttpServletResponse response
+ ) throws ResourceNotFoundException {
+ Optional source = sourceRepository.findById(sourceIdentifier);
+ if (source.isEmpty()) {
+ throw new ResourceNotFoundException(String.format(
+ "Source with uuid '%s' does not exist.",
+ sourceIdentifier
+ ));
+ }
+
+ ServiceContext serviceContext = ApiUtils.createServiceContext(request);
+ Resources resources = serviceContext.getBean(Resources.class);
+ String logoRef = getLogoReference(source.get());
+ try (Resources.ResourceHolder image = LogoUtils.getImage(resources, serviceContext, logoRef)) {
+ LogoUtils.writeImageOrTransparentLogo(webRequest, response, image);
+ } catch (IOException e) {
+ Log.error(LOGGER, String.format("There was an error accessing the logo of the source with id '%s'",
+ sourceIdentifier));
+ throw new RuntimeException(e);
+ }
+ }
+
@io.swagger.v3.oas.annotations.Operation(
summary = "Add a source",
description = "")
@@ -366,4 +414,13 @@ private void updateSource(String sourceIdentifier,
});
}
+
+ private String getLogoReference(Source source) {
+ if (source.getType() == SourceType.portal
+ || source.getType() == SourceType.subportal
+ || source.getType() == SourceType.harvester) {
+ return source.getLogo();
+ }
+ return StringUtils.defaultIfBlank(source.getLogo(), source.getUuid());
+ }
}
diff --git a/services/src/main/java/org/fao/geonet/util/FileMimetypeChecker.java b/services/src/main/java/org/fao/geonet/util/FileMimetypeChecker.java
index 8a1a0674690f..00af11b3b0a2 100644
--- a/services/src/main/java/org/fao/geonet/util/FileMimetypeChecker.java
+++ b/services/src/main/java/org/fao/geonet/util/FileMimetypeChecker.java
@@ -32,7 +32,7 @@
@Component
public class FileMimetypeChecker {
- private static final String[] validImagesMimeTypes = {"image/gif", "image/png", "image/jpeg"};
+ private static final String[] validImagesMimeTypes = {"image/gif", "image/png", "image/jpeg", "image/svg+xml"};
// application/xml --> SDMX files, application/rdf+xml --> RDF, OWL files
private static final String[] validThesaurusMimeTypes = {"application/xml", "application/rdf+xml"};
diff --git a/services/src/test/java/org/fao/geonet/api/harvesting/HarvestersApiTest.java b/services/src/test/java/org/fao/geonet/api/harvesting/HarvestersApiTest.java
new file mode 100644
index 000000000000..1df53941dcc5
--- /dev/null
+++ b/services/src/test/java/org/fao/geonet/api/harvesting/HarvestersApiTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2001-2026 Food and Agriculture Organization of the
+ * United Nations (FAO-UN), United Nations World Food Programme (WFP)
+ * and United Nations Environment Programme (UNEP)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
+ * Rome - Italy. email: geonetwork@osgeo.org
+ */
+package org.fao.geonet.api.harvesting;
+
+import org.fao.geonet.domain.ISODate;
+import org.fao.geonet.domain.Source;
+import org.fao.geonet.domain.SourceType;
+import org.fao.geonet.repository.SourceRepository;
+import org.fao.geonet.services.AbstractServiceIntegrationTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.assertNotNull;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+public class HarvestersApiTest extends AbstractServiceIntegrationTest {
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private SourceRepository sourceRepo;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setUp() {
+ createTestData();
+ }
+
+ @Test
+ public void getHarvesterLogoRedirectsToSourceLogo() throws Exception {
+ Source source = sourceRepo.findOneByName("harvester-source");
+ assertNotNull(source);
+
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ this.mockMvc.perform(get("/srv/api/harvesters/" + source.getUuid() + "/logo"))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("/srv/api/sources/" + source.getUuid() + "/logo"));
+ }
+
+ @Test
+ public void getHarvesterLogoWithNonExistingHarvester() throws Exception {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ this.mockMvc.perform(get("/srv/api/harvesters/not-an-existing-harvester/logo"))
+ .andExpect(status().isNotFound());
+ }
+
+ private void createTestData() {
+ Source source = new Source();
+ source.setName("harvester-source");
+ source.setUuid("harvester-source-uuid");
+ source.setCreationDate(new ISODate());
+ source.setType(SourceType.harvester);
+ sourceRepo.save(source);
+ }
+}
diff --git a/services/src/test/java/org/fao/geonet/api/records/formatters/AlternateLogoForPdfExportTest.java b/services/src/test/java/org/fao/geonet/api/records/formatters/AlternateLogoForPdfExportTest.java
index 73130398d92f..b489f5bbbaa5 100644
--- a/services/src/test/java/org/fao/geonet/api/records/formatters/AlternateLogoForPdfExportTest.java
+++ b/services/src/test/java/org/fao/geonet/api/records/formatters/AlternateLogoForPdfExportTest.java
@@ -84,7 +84,6 @@ public void whenNotGeneratingPdfWithPropertySetSiteLogoIsUsed() throws Exception
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
MockHttpSession mockHttpSession = loginAsAdmin();
settingManager.setValue("metadata/pdfReport/headerLogoFileName", "pdf_test_banner_to_use.png");
- String siteId = settingManager.getValue("system/site/siteId");
String url = "/srv/api/records/" + metadata.getUuid() + "/formatters/xsl-view?language=fre";
mockMvc.perform(get(url)
@@ -96,7 +95,7 @@ public void whenNotGeneratingPdfWithPropertySetSiteLogoIsUsed() throws Exception
ArgumentCaptor captor = ArgumentCaptor.forClass(byte[].class);
Mockito.verify(responseWriterSpy).writeOutResponse(any(ServiceContext.class), any(String.class), any(String.class), any(HttpServletResponse.class), any(FormatType.class), captor.capture());
assertFalse(new String(captor.getValue(), StandardCharsets.UTF_8).contains("pdf_test_banner_to_use.png"));
- assertTrue(new String(captor.getValue(), StandardCharsets.UTF_8).contains("images/logos/" + siteId + ".png"));
+ assertTrue(new String(captor.getValue(), StandardCharsets.UTF_8).contains("srv/api/sources/" + metadata.getSourceInfo().getSourceId() + "/logo"));
}
@Test
@@ -105,7 +104,6 @@ public void whenGeneratingPdfWithPropertyNotSetSiteLogoIsUsed() throws Exception
MockHttpSession mockHttpSession = loginAsAdmin();
Optional se = settingRepository.findById("metadata/pdfReport/headerLogoFileName");
se.ifPresent(settingRepository::delete);
- String siteId = settingManager.getValue("system/site/siteId");
String url = "/srv/api/records/" + metadata.getUuid() + "/formatters/xsl-view?output=pdf&language=fre";
mockMvc.perform(get(url)
@@ -117,6 +115,6 @@ public void whenGeneratingPdfWithPropertyNotSetSiteLogoIsUsed() throws Exception
ArgumentCaptor captor = ArgumentCaptor.forClass(byte[].class);
Mockito.verify(responseWriterSpy).writeOutResponse(any(ServiceContext.class), any(String.class), any(String.class), any(HttpServletResponse.class), any(FormatType.class), captor.capture());
assertFalse(new String(captor.getValue(), StandardCharsets.UTF_8).contains("pdf_test_banner_to_use.png"));
- assertTrue(new String(captor.getValue(), StandardCharsets.UTF_8).contains("images/logos/" + siteId + ".png"));
+ assertTrue(new String(captor.getValue(), StandardCharsets.UTF_8).contains("srv/api/sources/" + metadata.getSourceInfo().getSourceId() + "/logo"));
}
}
diff --git a/services/src/test/java/org/fao/geonet/api/sources/SourcesApiTest.java b/services/src/test/java/org/fao/geonet/api/sources/SourcesApiTest.java
index 9e7ca4b79775..21c7f189766b 100644
--- a/services/src/test/java/org/fao/geonet/api/sources/SourcesApiTest.java
+++ b/services/src/test/java/org/fao/geonet/api/sources/SourcesApiTest.java
@@ -24,7 +24,6 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import junit.framework.Assert;
import org.fao.geonet.api.FieldNameExclusionStrategy;
import org.fao.geonet.api.JsonFieldNamingStrategy;
import org.fao.geonet.domain.ISODate;
@@ -44,6 +43,8 @@
import java.util.UUID;
import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -73,7 +74,7 @@ public void setUp() throws Exception {
@Test
public void getSource() throws Exception {
Source source = sourceRepo.findOneByName("source-test");
- Assert.assertNotNull(source);
+ assertNotNull(source);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
@@ -86,10 +87,28 @@ public void getSource() throws Exception {
.andExpect(content().contentType(API_JSON_EXPECTED_ENCODING));
}
+ @Test
+ public void getSourceLogo() throws Exception {
+ Source source = sourceRepo.findOneByName("source-test");
+ assertNotNull(source);
+
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ this.mockMvc.perform(get("/srv/api/sources/" + source.getUuid() + "/logo"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(API_PNG_EXPECTED_ENCODING));
+ }
+
+ @Test
+ public void getNonExistingSourceLogo() throws Exception {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
+ this.mockMvc.perform(get("/srv/api/sources/source-test-2/logo"))
+ .andExpect(status().is(404));
+ }
+
@Test
public void getNonExistingSource() throws Exception {
Source sourceToUpdate = sourceRepo.findOneByName("source-test-2");
- Assert.assertNull(sourceToUpdate);
+ assertNull(sourceToUpdate);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
@@ -117,7 +136,7 @@ public void getSources() throws Exception {
@Test
public void updateSource() throws Exception {
Source source = sourceRepo.findOneByName("source-test");
- Assert.assertNotNull(source);
+ assertNotNull(source);
source.setName("source-test-updated");
@@ -142,7 +161,7 @@ public void updateSource() throws Exception {
@Test
public void updateNonExistingSource() throws Exception {
Source sourceToUpdate = sourceRepo.findOneByName("source-test-2");
- Assert.assertNull(sourceToUpdate);
+ assertNull(sourceToUpdate);
sourceToUpdate = new Source();
sourceToUpdate.setName("source-test-2");
diff --git a/web-ui/src/main/resources/catalog/components/admin/harvester/HarvesterDirective.js b/web-ui/src/main/resources/catalog/components/admin/harvester/HarvesterDirective.js
index 26ee2d40a6aa..fac053d5795f 100644
--- a/web-ui/src/main/resources/catalog/components/admin/harvester/HarvesterDirective.js
+++ b/web-ui/src/main/resources/catalog/components/admin/harvester/HarvesterDirective.js
@@ -61,11 +61,9 @@
}
$("#translationModal").modal("show");
};
- $http
- .get("admin.harvester.info?type=icons&_content_type=json", { cache: true })
- .then(function (response) {
- scope.icons = response.data[0];
- });
+ $http.get("../api/logos", { cache: true }).then(function (response) {
+ scope.icons = response.data;
+ });
$http
.get("info?_content_type=json&type=languages", { cache: true })
.then(function (response) {
@@ -338,11 +336,9 @@
"../../catalog/components/admin/harvester/partials/" + "logopicker.html",
link: function (scope, element, attrs) {
- $http
- .get("admin.harvester.info?type=icons&_content_type=json", { cache: true })
- .then(function (response) {
- scope.icons = response.data[0];
- });
+ $http.get("../api/logos", { cache: true }).then(function (response) {
+ scope.icons = response.data;
+ });
/**
* Set the icon
diff --git a/web-ui/src/main/resources/catalog/components/metadataactions/partials/metadatagroupupdater.html b/web-ui/src/main/resources/catalog/components/metadataactions/partials/metadatagroupupdater.html
index 0864bdb6e1db..837c899d9678 100644
--- a/web-ui/src/main/resources/catalog/components/metadataactions/partials/metadatagroupupdater.html
+++ b/web-ui/src/main/resources/catalog/components/metadataactions/partials/metadatagroupupdater.html
@@ -34,7 +34,7 @@
{{g.label[lang]}}
diff --git a/web-ui/src/main/resources/catalog/components/toolbar/partials/menu-sitename.html b/web-ui/src/main/resources/catalog/components/toolbar/partials/menu-sitename.html
index 35c0e52d428f..de40ee7a77f0 100644
--- a/web-ui/src/main/resources/catalog/components/toolbar/partials/menu-sitename.html
+++ b/web-ui/src/main/resources/catalog/components/toolbar/partials/menu-sitename.html
@@ -1,10 +1,13 @@
-
+
{{::info['system/site/name']}}
diff --git a/web-ui/src/main/resources/catalog/components/utility/partials/recordOriginLogo.html b/web-ui/src/main/resources/catalog/components/utility/partials/recordOriginLogo.html
index f0d39d5f2039..b5314aae9d11 100644
--- a/web-ui/src/main/resources/catalog/components/utility/partials/recordOriginLogo.html
+++ b/web-ui/src/main/resources/catalog/components/utility/partials/recordOriginLogo.html
@@ -1,14 +1,14 @@
diff --git a/web-ui/src/main/resources/catalog/templates/admin/harvest/harvest-settings.html b/web-ui/src/main/resources/catalog/templates/admin/harvest/harvest-settings.html
index 33e59c43ce6d..3fd01e9f833f 100644
--- a/web-ui/src/main/resources/catalog/templates/admin/harvest/harvest-settings.html
+++ b/web-ui/src/main/resources/catalog/templates/admin/harvest/harvest-settings.html
@@ -41,6 +41,11 @@
>{{h.site.name}} ({{('harvester-' + h['@type']) |
translate}})
+
@@ -73,7 +78,7 @@
{{h.info.result.total}}
{{h.info.result.updated}} |
-
+ |
{{h.info.result.unchanged}}
|
diff --git a/web-ui/src/main/resources/catalog/templates/admin/settings/logo.html b/web-ui/src/main/resources/catalog/templates/admin/settings/logo.html
index bc53925fe2c5..9932b31103a6 100644
--- a/web-ui/src/main/resources/catalog/templates/admin/settings/logo.html
+++ b/web-ui/src/main/resources/catalog/templates/admin/settings/logo.html
@@ -7,9 +7,7 @@
-
![]()
+
diff --git a/web-ui/src/main/resources/catalog/templates/admin/settings/sources.html b/web-ui/src/main/resources/catalog/templates/admin/settings/sources.html
index 03d795571075..1bfc9c17f506 100644
--- a/web-ui/src/main/resources/catalog/templates/admin/settings/sources.html
+++ b/web-ui/src/main/resources/catalog/templates/admin/settings/sources.html
@@ -51,7 +51,7 @@
diff --git a/web-ui/src/main/resources/catalog/templates/admin/usergroup/groups.html b/web-ui/src/main/resources/catalog/templates/admin/usergroup/groups.html
index cde2965bffb7..f5c98cb8f4bd 100644
--- a/web-ui/src/main/resources/catalog/templates/admin/usergroup/groups.html
+++ b/web-ui/src/main/resources/catalog/templates/admin/usergroup/groups.html
@@ -30,12 +30,13 @@
data-ng-class="g.id === groupSelected.id ? 'active' : ''"
data-ng-click="selectGroup(g)"
>
+ {{g.label[lang]|empty:g.name}}
- {{g.label[lang]|empty:g.name}}
diff --git a/web/src/main/java/org/fao/geonet/Geonetwork.java b/web/src/main/java/org/fao/geonet/Geonetwork.java
index 312d2b7c072b..f696205494c0 100644
--- a/web/src/main/java/org/fao/geonet/Geonetwork.java
+++ b/web/src/main/java/org/fao/geonet/Geonetwork.java
@@ -84,6 +84,7 @@
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
+import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
@@ -95,6 +96,8 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import static org.fao.geonet.resources.ResourceFilter.DEFAULT_LOGO;
+
/**
* This is the main class, it handles http connections and inits the system.
@@ -301,7 +304,7 @@ public Object start(Element config, ServiceContext context) throws Exception {
// Creates a default site logo, only if the logo image doesn't exists
// This can happen if the application has been updated with a new version preserving the database and
// images/logos folder is not copied from old application
- createSiteLogo(settingMan.getSiteId(), context, context.getAppPath());
+ createSiteLogo(settingMan.getSiteId(), context, sourceRepository);
//-- Initialize the proxy configuration if required
Lib.net.setupProxy(settingMan);
@@ -520,13 +523,24 @@ private boolean checkDBWrite() {
/**
* Creates a default site logo, only if the logo image doesn't exists
*/
- private void createSiteLogo(String nodeUuid, ServiceContext context, Path appPath) {
+ private void createSiteLogo(String nodeUuid, ServiceContext context, SourceRepository sourceRepository) {
try {
final Resources resources = context.getBean(Resources.class);
Path logosDir = resources.locateLogosDir(context);
- Path logo = logosDir.resolve(nodeUuid + ".png");
- if (!Files.exists(logo)) {
- resources.copyLogo(context, "images" + File.separator + "harvesting" + File.separator + "GN3.png", nodeUuid);
+ String logoFile = null;
+ try (DirectoryStream logos = Files.newDirectoryStream(logosDir, nodeUuid + ".*")) {
+ for (Path logo : logos) {
+ logoFile = logo.getFileName().toString();
+ break;
+ }
+ }
+ if (StringUtils.isBlank(logoFile)) {
+ logoFile = resources.copyLogo(context, "images" + File.separator + "harvesting" + File.separator + DEFAULT_LOGO, nodeUuid);
+ }
+ Source source = sourceRepository.findOneByUuid(nodeUuid);
+ if (source != null) {
+ source.setLogo(logoFile);
+ sourceRepository.save(source);
}
} catch (Throwable e) {
logger.error(" Error when setting the logo: " + e.getMessage());
diff --git a/web/src/main/webapp/WEB-INF/data/data/formatter/xslt/render-layout.xsl b/web/src/main/webapp/WEB-INF/data/data/formatter/xslt/render-layout.xsl
index 9c94344df2a6..a323c15de165 100644
--- a/web/src/main/webapp/WEB-INF/data/data/formatter/xslt/render-layout.xsl
+++ b/web/src/main/webapp/WEB-INF/data/data/formatter/xslt/render-layout.xsl
@@ -222,7 +222,7 @@
+ src="{$nodeUrl}api/sources/{$source}/logo" />
diff --git a/web/src/main/webapp/xslt/services/pdf/metadata-fop.xsl b/web/src/main/webapp/xslt/services/pdf/metadata-fop.xsl
index de3a89c56c3a..10f14be50485 100644
--- a/web/src/main/webapp/xslt/services/pdf/metadata-fop.xsl
+++ b/web/src/main/webapp/xslt/services/pdf/metadata-fop.xsl
@@ -323,7 +323,7 @@
url('')"
diff --git a/web/src/main/webapp/xslt/skin/default/skin.xsl b/web/src/main/webapp/xslt/skin/default/skin.xsl
index 0d7bd763a79b..a0c5646be6c5 100644
--- a/web/src/main/webapp/xslt/skin/default/skin.xsl
+++ b/web/src/main/webapp/xslt/skin/default/skin.xsl
@@ -84,7 +84,7 @@
+ src="{/root/gui/nodeUrl}api/site/logo"/>
diff --git a/web/src/main/webapp/xslt/ui-search/portal-list.xsl b/web/src/main/webapp/xslt/ui-search/portal-list.xsl
index a08619389bf5..f2b452eab87b 100644
--- a/web/src/main/webapp/xslt/ui-search/portal-list.xsl
+++ b/web/src/main/webapp/xslt/ui-search/portal-list.xsl
@@ -48,7 +48,7 @@