diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 582ede07080..55efecfc033 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ main ] + branches: [ 4.2.x ] pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: [ 4.2.x ] schedule: - cron: '44 20 * * 5' @@ -38,10 +38,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 - + uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Setup Java JDK - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v3.12.0 with: java-version: 8 # Java distribution. See the list of supported distributions in README file diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1fcc8cfa395..6169b7de27a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,21 +15,18 @@ jobs: - os: ubuntu-22.04 jdk: 8 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # 500 commits, set to 0 to get all fetch-depth: 500 submodules: 'recursive' + show-progress: 'false' - name: Set up JDK - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v3.12.0 with: distribution: 'temurin' java-version: ${{ matrix.jdk }} cache: 'maven' - - name: Set up Maven - uses: stCarolas/setup-maven@v4 - with: - maven-version: 3.6.3 - name: Build with Maven run: mvn -B -V install -DskipTests=true -Dmaven.javadoc.skip=true - name: Remove SNAPSHOT jars from repository @@ -42,13 +39,14 @@ jobs: QA: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # 500 commits, set to 0 to get all fetch-depth: 500 submodules: 'recursive' + show-progress: 'false' - name: Set up JDK - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v3.12.0 with: distribution: 'temurin' java-version: 8 diff --git a/.github/workflows/mvn-dep-tree.yml b/.github/workflows/mvn-dep-tree.yml index 4b807118189..615c988543f 100644 --- a/.github/workflows/mvn-dep-tree.yml +++ b/.github/workflows/mvn-dep-tree.yml @@ -5,20 +5,22 @@ name: "MavenDepTreeSubmission" on: push: - branches: [ main ] + branches: [ 4.2.x ] schedule: - cron: '44 22 * * 5' jobs: update-maven-dep-tree: runs-on: ubuntu-latest - + steps: - name: Checkout repository - uses: actions/checkout@v3 - + uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Setup Java JDK - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v3.12.0 with: java-version: 8 # Java distribution. See the list of supported distributions in README file @@ -26,6 +28,6 @@ jobs: # The package type (jdk, jre, jdk+fx, jre+fx) java-package: jdk cache: maven - + - name: Submit Dependency Snapshot uses: advanced-security/maven-dependency-submission-action@v3 diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 72c61694603..ec28ac64e28 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -2,9 +2,7 @@ name: SonarCloud QA on: push: branches: - - main - - 4.0.x - - 3.12.x + - 4.2.x pull_request: types: [opened, synchronize, reopened] jobs: @@ -15,16 +13,17 @@ jobs: # https://github.community/t/how-to-detect-a-pull-request-from-a-fork/18363/4 if: github.event.pull_request.head.repo.fork != true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis submodules: 'recursive' + show-progress: 'false' # For building GeoNetwork, JDK8 is necessary, but for running # the SonarQube plugin, JDK11 is necessary. # So, first install JDK 8, build GeoNetwork, then install JDK11 # and run SonarQube: - name: Set up JDK 8 - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v3.12.0 with: java-version: 8 distribution: 'temurin' @@ -38,7 +37,7 @@ jobs: - name: Build GN run: mvn -B package -DskipTests - name: Set up JDK 11 - uses: actions/setup-java@v3.10.0 + uses: actions/setup-java@v3.12.0 with: distribution: 'temurin' java-version: '11' diff --git a/cachingxslt/pom.xml b/cachingxslt/pom.xml index 05dfdd3625e..524d5757f01 100644 --- a/cachingxslt/pom.xml +++ b/cachingxslt/pom.xml @@ -31,7 +31,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT diff --git a/common/pom.xml b/common/pom.xml index f898d290296..ecfd5313fea 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -31,7 +31,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT diff --git a/common/src/main/java/org/fao/geonet/utils/AbstractHttpRequest.java b/common/src/main/java/org/fao/geonet/utils/AbstractHttpRequest.java index 457842d0a4e..9d4f92adc13 100644 --- a/common/src/main/java/org/fao/geonet/utils/AbstractHttpRequest.java +++ b/common/src/main/java/org/fao/geonet/utils/AbstractHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2023 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -83,7 +83,7 @@ public class AbstractHttpRequest { private boolean useProxy; private String proxyHost; private int proxyPort; - private ArrayList alSimpleParams = new ArrayList(); + private ArrayList alSimpleParams = new ArrayList<>(); private String postData; private boolean preemptiveBasicAuth; private HttpClientContext httpClientContext; @@ -303,9 +303,9 @@ protected HttpRequestBase setupHttpMethod() throws IOException { } if (host == null || protocol == null) { - throw new IllegalStateException(String.format(getClass().getSimpleName() + " is not ready to be executed: \n\tprotocol: '%s' " + + throw new IllegalStateException(String.format("%s is not ready to be executed: \n\tprotocol: '%s' " + "\n\tuserinfo: '%s'\n\thost: '%s' \n\tport: '%s' \n\taddress: '%s'\n\tquery '%s'" + - "\n\tfragment: '%s'", protocol, userInfo, host, port, address, query, fragment)); + "\n\tfragment: '%s'", getClass().getSimpleName(), protocol, userInfo, host, port, address, query, fragment)); } HttpRequestBase httpMethod; @@ -352,25 +352,25 @@ protected HttpRequestBase setupHttpMethod() throws IOException { protected String getSentData(HttpRequestBase httpMethod) { URI uri = httpMethod.getURI(); - StringBuilder sentData = new StringBuilder(httpMethod.getMethod()).append(" ").append(uri.getPath()); + StringBuilder sentDataValue = new StringBuilder(httpMethod.getMethod()).append(" ").append(uri.getPath()); if (uri.getQuery() != null) { - sentData.append("?" + uri.getQuery()); + sentDataValue.append("?" + uri.getQuery()); } - sentData.append("\r\n"); + sentDataValue.append("\r\n"); for (Header h : httpMethod.getAllHeaders()) { - sentData.append(h); + sentDataValue.append(h); } - sentData.append("\r\n"); + sentDataValue.append("\r\n"); if (httpMethod instanceof HttpPost) { - sentData.append(postData); + sentDataValue.append(postData); } - return sentData.toString(); + return sentDataValue.toString(); } private Element soapEmbed(Element elem) { @@ -393,7 +393,7 @@ protected Element soapUnembed(Element envelope) throws BadSoapResponseEx { List list = body.getChildren(); - if (list.size() == 0) + if (list.isEmpty()) throw new BadSoapResponseEx(envelope); return list.get(0); diff --git a/common/src/main/java/org/fao/geonet/utils/GeonetHttpRequestFactory.java b/common/src/main/java/org/fao/geonet/utils/GeonetHttpRequestFactory.java index 7ba666fd2a9..bd548561c05 100644 --- a/common/src/main/java/org/fao/geonet/utils/GeonetHttpRequestFactory.java +++ b/common/src/main/java/org/fao/geonet/utils/GeonetHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2023 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -200,6 +200,7 @@ public HttpClientBuilder getDefaultHttpClientBuilder() { final HttpClientBuilder builder = HttpClientBuilder.create(); builder.setRedirectStrategy(new LaxRedirectStrategy()); builder.disableContentCompression(); + builder.useSystemProperties(); synchronized (this) { if (connectionManager == null) { @@ -249,40 +250,40 @@ public void closeIdleConnections(long idleTimeout, TimeUnit tunit) { private static class AdaptingResponse extends AbstractClientHttpResponse { - private final CloseableHttpResponse _response; - private final CloseableHttpClient _client; + private final CloseableHttpResponse response; + private final CloseableHttpClient client; public AdaptingResponse(CloseableHttpClient client, CloseableHttpResponse response) { - this._response = response; - this._client = client; + this.response = response; + this.client = client; } @Override public int getRawStatusCode() throws IOException { - return _response.getStatusLine().getStatusCode(); + return response.getStatusLine().getStatusCode(); } @Override public String getStatusText() throws IOException { - return _response.getStatusLine().getReasonPhrase(); + return response.getStatusLine().getReasonPhrase(); } @Override public void close() { - IOUtils.closeQuietly(_response); - IOUtils.closeQuietly(_client); + IOUtils.closeQuietly(response); + IOUtils.closeQuietly(client); } @Override public InputStream getBody() throws IOException { - return _response.getEntity().getContent(); + return response.getEntity().getContent(); } @Override public HttpHeaders getHeaders() { final HttpHeaders httpHeaders = new HttpHeaders(); - final Header[] headers = _response.getAllHeaders(); + final Header[] headers = response.getAllHeaders(); for (Header header : headers) { final HeaderElement[] elements = header.getElements(); diff --git a/core/pom.xml b/core/pom.xml index 746926a6726..a4ade4e1c7d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/core/src/main/java/jeeves/config/springutil/JeevesNodeAwareLogoutSuccessHandler.java b/core/src/main/java/jeeves/config/springutil/JeevesNodeAwareLogoutSuccessHandler.java index e1c0aec9692..8442c90959a 100644 --- a/core/src/main/java/jeeves/config/springutil/JeevesNodeAwareLogoutSuccessHandler.java +++ b/core/src/main/java/jeeves/config/springutil/JeevesNodeAwareLogoutSuccessHandler.java @@ -26,11 +26,15 @@ import org.fao.geonet.NodeInfo; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; +import org.fao.geonet.constants.Geonet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.apache.commons.lang3.StringUtils; +import org.fao.geonet.kernel.setting.SettingInfo; + import java.io.IOException; import java.net.MalformedURLException; @@ -84,8 +88,10 @@ protected String determineTargetUrl(HttpServletRequest request, HttpServletRespo String siteHost = settingManager.getValue(Settings.SYSTEM_SERVER_HOST); String siteProtocol = settingManager.getValue(Settings.SYSTEM_SERVER_PROTOCOL); - int sitePort = settingManager.getValueAsInt(Settings.SYSTEM_SERVER_PORT); - + + // some conditional logic to handle the case where there's no port in the settings + SettingInfo si = new SettingInfo(); + int sitePort = si.getSitePort(); if (!hostName.equalsIgnoreCase(siteHost) || !protocol.equalsIgnoreCase(siteProtocol) || diff --git a/core/src/main/java/org/fao/geonet/kernel/SpringLocalServiceInvoker.java b/core/src/main/java/org/fao/geonet/kernel/SpringLocalServiceInvoker.java index 87da11f4888..fa3711cb858 100644 --- a/core/src/main/java/org/fao/geonet/kernel/SpringLocalServiceInvoker.java +++ b/core/src/main/java/org/fao/geonet/kernel/SpringLocalServiceInvoker.java @@ -22,8 +22,6 @@ */ package org.fao.geonet.kernel; -import org.fao.geonet.NodeInfo; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -43,16 +41,19 @@ public class SpringLocalServiceInvoker { - @Autowired - public RequestMappingHandlerMapping requestMappingHandlerMapping; + public final RequestMappingHandlerMapping requestMappingHandlerMapping; - @Autowired - public RequestMappingHandlerAdapter requestMappingHandlerAdapter; + public final RequestMappingHandlerAdapter requestMappingHandlerAdapter; private HandlerMethodArgumentResolverComposite argumentResolvers; private HandlerMethodReturnValueHandlerComposite returnValueHandlers; private DefaultDataBinderFactory webDataBinderFactory; + public SpringLocalServiceInvoker(RequestMappingHandlerMapping requestMappingHandlerMapping, RequestMappingHandlerAdapter requestMappingHandlerAdapter) { + this.requestMappingHandlerMapping = requestMappingHandlerMapping; + this.requestMappingHandlerAdapter = requestMappingHandlerAdapter; + } + public void init() { argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(requestMappingHandlerAdapter.getArgumentResolvers()); returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(requestMappingHandlerAdapter.getReturnValueHandlers()); 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 1f9928a075d..35e346bb346 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 @@ -410,13 +410,13 @@ public void indexMetadata(final String metadataId, fields.put(IndexFields.DRAFT, "n"); fields.put(IndexFields.INDEXING_ERROR_FIELD, true); fields.put(IndexFields.INDEXING_ERROR_MSG, String.format( - "Schema '%s' is not registerd in this catalog. Install it or remove those records", + "Schema '%s' is not registered in this catalog. Install it or remove those records", schema )); searchManager.index(null, md, indexKey, fields, metadataType, forceRefreshReaders, indexingMode); Log.error(Geonet.DATA_MANAGER, String.format( - "Record %s / Schema '%s' is not registerd in this catalog. Install it or remove those records. Record is indexed indexing error flag.", + "Record %s / Schema '%s' is not registered in this catalog. Install it or remove those records. Record is indexed indexing error flag.", metadataId, schema)); } else { diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java index 52b9db2c5eb..61ba2251061 100644 --- a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java +++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java @@ -49,10 +49,10 @@ import org.fao.geonet.kernel.schema.SchemaPlugin; import org.fao.geonet.kernel.search.EsSearchManager; import org.fao.geonet.kernel.search.IndexingMode; -import org.fao.geonet.kernel.search.MetaSearcher; import org.fao.geonet.kernel.search.index.BatchOpsMetadataReindexer; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; +import org.fao.geonet.kernel.setting.SettingInfo; import org.fao.geonet.lib.Lib; import org.fao.geonet.repository.*; import org.fao.geonet.repository.specification.MetadataFileUploadSpecs; @@ -914,15 +914,8 @@ private Element buildInfoElem(ServiceContext context, String id, String version) } // add baseUrl of this site (from settings) - String protocol = settingManager.getValue(Settings.SYSTEM_SERVER_PROTOCOL); - String host = settingManager.getValue(Settings.SYSTEM_SERVER_HOST); - String port = settingManager.getValue(Settings.SYSTEM_SERVER_PORT); - if (port.equals("80")) { - port = ""; - } else { - port = ":" + port; - } - addElement(info, Edit.Info.Elem.BASEURL, protocol + "://" + host + port + context.getBaseUrl()); + SettingInfo si = new SettingInfo(); + addElement(info, Edit.Info.Elem.BASEURL, si.getSiteUrl() + context.getBaseUrl()); addElement(info, Edit.Info.Elem.LOCSERV, "/srv/en"); return info; } diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java index d235f33456c..040acbf4aca 100644 --- a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java +++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java @@ -572,6 +572,10 @@ protected String createDraft(ServiceContext context, String templateId, String g Integer status = Integer.valueOf(StatusValue.Status.DRAFT); java.util.Optional statusValue = statusValueRepository.findById(status); + String lang = context.getLanguage(); + ResourceBundle messages = ResourceBundle.getBundle("org.fao.geonet.api.Messages", + new Locale(lang)); + if (statusValue.isPresent()) { for (Integer mdId : metadataIds) { MetadataStatus metadataStatus = new MetadataStatus(); @@ -580,7 +584,7 @@ protected String createDraft(ServiceContext context, String templateId, String g metadataStatus.setChangeDate(new ISODate()); metadataStatus.setUserId(author); metadataStatus.setStatusValue(statusValue.get()); - metadataStatus.setChangeMessage("Editing instance created"); + metadataStatus.setChangeMessage(messages.getString("metadata_status_editing_instance_created_text")); metadataStatus.setTitles(metadataUtils.extractTitles(newMetadata.getDataInfo().getSchemaId(), xml)); List listOfStatusChange = new ArrayList<>(1); diff --git a/core/src/main/java/org/fao/geonet/kernel/mef/Importer.java b/core/src/main/java/org/fao/geonet/kernel/mef/Importer.java index a5cd2c695fc..77f7d18f00e 100644 --- a/core/src/main/java/org/fao/geonet/kernel/mef/Importer.java +++ b/core/src/main/java/org/fao/geonet/kernel/mef/Importer.java @@ -45,6 +45,7 @@ import org.fao.geonet.kernel.datamanager.IMetadataValidator; import org.fao.geonet.kernel.search.IndexingMode; import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.kernel.setting.Settings; import org.fao.geonet.repository.*; import org.fao.geonet.utils.FilePathChecker; import org.fao.geonet.utils.Log; @@ -65,7 +66,7 @@ public class Importer { @Deprecated public static List doImport(final Element params, final ServiceContext context, final Path mefFile, - final Path stylePath) throws Exception { + final Path stylePath) throws Exception { String fileType = Util.getParam(params, "file_type", "mef"); String style = Util.getParam(params, Params.STYLESHEET, "_none_"); String uuidAction = Util.getParam(params, Params.UUID_ACTION, Params.NOTHING); @@ -81,8 +82,8 @@ public static List doImport(final Element params, final ServiceContext c } public static List doImport(String fileType, final MEFLib.UuidAction uuidAction, final String style, final String source, - final MetadataType isTemplate, final String[] category, final String groupId, final boolean validate, final boolean assign, - final ServiceContext context, final Path mefFile) throws Exception { + final MetadataType isTemplate, final String[] category, final String groupId, final boolean validate, final boolean assign, + final ServiceContext context, final Path mefFile) throws Exception { ApplicationContext applicationContext = ApplicationContextHolder.get(); final DataManager dm = applicationContext.getBean(DataManager.class); final SettingManager sm = applicationContext.getBean(SettingManager.class); @@ -479,8 +480,8 @@ public static void addCategoriesToMetadata(AbstractMetadata metadata, Element fi } public static void importRecord(String uuid, MEFLib.UuidAction uuidAction, List md, String schema, int index, String source, - String sourceName, Map sourceTranslations, ServiceContext context, List id, String createDate, - String changeDate, String groupId, MetadataType isTemplate) throws Exception { + String sourceName, Map sourceTranslations, ServiceContext context, List id, String createDate, + String changeDate, String groupId, MetadataType isTemplate) throws Exception { GeonetContext gc = (GeonetContext) context.getHandlerContext(Geonet.CONTEXT_NAME); DataManager dm = gc.getBean(DataManager.class); @@ -512,31 +513,68 @@ public static void importRecord(String uuid, MEFLib.UuidAction uuidAction, List< } } - try { - if (dm.existsMetadataUuid(uuid) && uuidAction != MEFLib.UuidAction.NOTHING) { - // user has privileges to replace the existing metadata + boolean metadataExist = dm.existsMetadataUuid(uuid); + + SettingManager settingManager = gc.getBean(SettingManager.class); + boolean isMdWorkflowEnable = settingManager.getValueAsBool(Settings.METADATA_WORKFLOW_ENABLE); + + String metadataId = ""; + if (metadataExist && uuidAction == MEFLib.UuidAction.NOTHING) { + throw new UnAuthorizedException("Record already exists. Change the import mode to overwrite or generating a new UUID.", null); + } else if (metadataExist && uuidAction == MEFLib.UuidAction.OVERWRITE){ + if (isMdWorkflowEnable) { + throw new UnAuthorizedException("Overwrite mode is not allowed when workflow is enabled. Use the metadata editor.", null); + } + + String recordToUpdateId = dm.getMetadataId(uuid); + if (dm.getAccessManager().canEdit(context, recordToUpdateId)) { + MetadataValidationRepository metadataValidationRepository = + context.getBean(MetadataValidationRepository.class); + List validationStatus = metadataValidationRepository + .findAllById_MetadataId(Integer.parseInt(recordToUpdateId)); + + // Refresh validation status if set + boolean validate = !validationStatus.isEmpty(); + metadataManager.updateMetadata( + context, recordToUpdateId, md.get(index), + validate, true, + context.getLanguage(), + null, true, IndexingMode.full); + metadataId = recordToUpdateId; + } else { + throw new UnAuthorizedException("User has no privilege to overwrite existing metadata", null); + } + } else if (metadataExist && uuidAction == MEFLib.UuidAction.REMOVE_AND_REPLACE){ + if (isMdWorkflowEnable) { + throw new UnAuthorizedException("Overwrite mode is not allowed when workflow is enabled. Use the metadata editor.", null); + } + + try { if (dm.getAccessManager().canEdit(context, dm.getMetadataId(uuid))) { if (Log.isDebugEnabled(Geonet.MEF)) { Log.debug(Geonet.MEF, "Deleting existing metadata with UUID : " + uuid); } metadataManager.deleteMetadata(context, dm.getMetadataId(uuid)); metadataManager.flush(); - } - // user does not hav privileges to replace the existing metadata - else { + } else { throw new UnAuthorizedException("User has no privilege to replace existing metadata", null); } + } catch (Exception e) { + throw new Exception(" Existing metadata with UUID " + uuid + " could not be deleted. Error is: " + e.getMessage()); } - } catch (Exception e) { - throw new Exception(" Existing metadata with UUID " + uuid + " could not be deleted. Error is: " + e.getMessage()); + metadataId = insertMetadata(uuid, md, schema, index, source, context, createDate, changeDate, groupId, isTemplate, dm, metadataManager); + } else { + metadataId = insertMetadata(uuid, md, schema, index, source, context, createDate, changeDate, groupId, isTemplate, dm, metadataManager); } + id.add(index, metadataId); + + } + + private static String insertMetadata(String uuid, List md, String schema, int index, String source, ServiceContext context, String createDate, String changeDate, String groupId, MetadataType isTemplate, DataManager dm, IMetadataManager metadataManager) throws Exception { if (Log.isDebugEnabled(Geonet.MEF)) Log.debug(Geonet.MEF, "Adding metadata with uuid:" + uuid); - // - // insert metadata - // int userid = context.getUserSession().getUserIdAsInt(); String docType = null, category = null; boolean ufo = false; @@ -546,15 +584,11 @@ public static void importRecord(String uuid, MEFLib.UuidAction uuidAction, List< createDate, changeDate, ufo, IndexingMode.none); dm.activateWorkflowIfConfigured(context, metadataId, groupId); - - id.add(index, metadataId); - + return metadataId; } - // -------------------------------------------------------------------------- - private static void saveFile(ServiceContext context, String id, MetadataResourceVisibility access, String file, String changeDate, - InputStream is) throws Exception { + InputStream is) throws Exception { final Store store = context.getBean("resourceStore", Store.class); final IMetadataUtils metadataUtils = context.getBean(IMetadataUtils.class); final String metadataUuid = metadataUtils.getMetadataUuid(id); @@ -609,7 +643,7 @@ private static Group addPrivileges(final ServiceContext context, final DataManag * Add operations according to information file. */ private static Set addOperations(final ServiceContext context, final DataManager dm, final Element group, - final int metadataId, final int grpId) { + final int metadataId, final int grpId) { @SuppressWarnings("unchecked") List operations = group.getChildren("operation"); Set toAdd = new HashSet(); diff --git a/core/src/main/java/org/fao/geonet/kernel/mef/MEFLib.java b/core/src/main/java/org/fao/geonet/kernel/mef/MEFLib.java index 273950c141c..ab78e4bc312 100644 --- a/core/src/main/java/org/fao/geonet/kernel/mef/MEFLib.java +++ b/core/src/main/java/org/fao/geonet/kernel/mef/MEFLib.java @@ -563,7 +563,17 @@ public static void backupRecord(AbstractMetadata metadata, ServiceContext contex public enum UuidAction { GENERATEUUID("generateUUID"), NOTHING("nothing"), - OVERWRITE("overwrite"); + + /** + * Update the XML of the metadata record. + */ + OVERWRITE("overwrite"), + + /** + * Remove the metadata (and privileges, status, ...) + * and insert the new one with the same UUID. + */ + REMOVE_AND_REPLACE("removeAndReplace"); String name; UuidAction(String name) { @@ -583,7 +593,7 @@ public static UuidAction parse(String value) { public enum Format { /** - * Only metadata record and infomation + * Only metadata record and information */ SIMPLE, /** diff --git a/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java b/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java index 0f703b2847c..e8678d483ab 100644 --- a/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java +++ b/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java @@ -264,6 +264,11 @@ protected void notify(List userToNotify, MetadataStatus status) throws Exc textTemplate = messages.getString("status_change_default_email_text"); } + // Replace link in message + ApplicationContext applicationContext = ApplicationContextHolder.get(); + SettingManager sm = applicationContext.getBean(SettingManager.class); + textTemplate = textTemplate.replace("{{link}}", sm.getNodeURL()+ "api/records/'{{'index:uuid'}}'"); + UserRepository userRepository = context.getBean(UserRepository.class); User owner = userRepository.findById(status.getOwner()).orElse(null); diff --git a/core/src/main/java/org/fao/geonet/kernel/search/EsSearchManager.java b/core/src/main/java/org/fao/geonet/kernel/search/EsSearchManager.java index c385176dee4..db7b1ff722b 100644 --- a/core/src/main/java/org/fao/geonet/kernel/search/EsSearchManager.java +++ b/core/src/main/java/org/fao/geonet/kernel/search/EsSearchManager.java @@ -529,6 +529,7 @@ private void checkIndexResponse(BulkResponse bulkItemResponses, .add("cat") .add("keyword") .add("extentDescriptionObject") + .add("extentIdentifierObject") .add("resourceAltTitleObject") .add("resourceCredit") .add("resourceCreditObject") diff --git a/core/src/main/java/org/fao/geonet/kernel/search/index/OverviewIndexFieldUpdater.java b/core/src/main/java/org/fao/geonet/kernel/search/index/OverviewIndexFieldUpdater.java index 95f9668389f..19944c779e4 100644 --- a/core/src/main/java/org/fao/geonet/kernel/search/index/OverviewIndexFieldUpdater.java +++ b/core/src/main/java/org/fao/geonet/kernel/search/index/OverviewIndexFieldUpdater.java @@ -1,3 +1,26 @@ +//============================================================================= +//=== Copyright (C) 2001-2023 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.kernel.search.index; import org.apache.commons.lang.StringUtils; @@ -20,13 +43,26 @@ public class OverviewIndexFieldUpdater { EsSearchManager searchManager; public void process(String uuid) { + processOverview(uuid); + processOverview(uuid + "-draft"); + } + + private ArrayList> getHitOverviews(Map fields) { + Object overviews = fields.get("overview"); + if (overviews != null) { + return ((ArrayList) overviews); + } + return new ArrayList<>(); + } + + private void processOverview(String id) { Set source = new HashSet<>(); source.add("overview"); SearchResponse response = null; try { response = searchManager.query(String.format( - "+uuid:\"%s\" _exists_:overview.url -_exists_:overview.data", - uuid), null, source, 0, 1); + "+id:\"%s\" _exists_:overview.url -_exists_:overview.data", + id), null, source, 0, 1); response.getHits().forEach(hit -> { AtomicBoolean updates = new AtomicBoolean(false); Map fields = hit.getSourceAsMap(); @@ -42,7 +78,7 @@ public void process(String uuid) { }); if (updates.get()) { try { - searchManager.updateFields(uuid, fields, source); + searchManager.updateFields(id, fields, source); } catch (Exception e) { e.printStackTrace(); } @@ -52,12 +88,4 @@ public void process(String uuid) { e.printStackTrace(); } } - - private ArrayList> getHitOverviews(Map fields) { - Object overviews = fields.get("overview"); - if (overviews != null) { - return ((ArrayList) overviews); - } - return new ArrayList<>(); - } } diff --git a/core/src/main/java/org/fao/geonet/kernel/security/keycloak/KeycloakUserUtils.java b/core/src/main/java/org/fao/geonet/kernel/security/keycloak/KeycloakUserUtils.java index 899df05fe2f..acb5f796b03 100644 --- a/core/src/main/java/org/fao/geonet/kernel/security/keycloak/KeycloakUserUtils.java +++ b/core/src/main/java/org/fao/geonet/kernel/security/keycloak/KeycloakUserUtils.java @@ -251,8 +251,7 @@ private Map> getProfileGroups(AccessToken accessToken) { * @param user to apply the changes to. */ private void updateGroups(Map> profileGroups, User user) { - // First we remove all previous groups - userGroupRepository.deleteAll(UserGroupSpecs.hasUserId(user.getId())); + Set userGroups = new HashSet<>(); // Now we add the groups for (Profile p : profileGroups.keySet()) { @@ -293,12 +292,14 @@ private void updateGroups(Map> profileGroups, User user) { ug.setGroup(group); ug.setUser(user); ug.setProfile(Profile.Editor); - userGroupRepository.save(ug); + userGroups.add(ug); } - userGroupRepository.save(usergroup); + userGroups.add(usergroup); } } + + userGroupRepository.updateUserGroups(user.getId(), userGroups); } /** diff --git a/core/src/main/java/org/fao/geonet/kernel/security/openidconnect/OidcUser2GeonetworkUser.java b/core/src/main/java/org/fao/geonet/kernel/security/openidconnect/OidcUser2GeonetworkUser.java index 4d9cf1f40a7..3c0cf6f3d78 100644 --- a/core/src/main/java/org/fao/geonet/kernel/security/openidconnect/OidcUser2GeonetworkUser.java +++ b/core/src/main/java/org/fao/geonet/kernel/security/openidconnect/OidcUser2GeonetworkUser.java @@ -41,8 +41,10 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.util.StringUtils; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Class for handling Oidc User and the Geonetwork User. @@ -159,8 +161,7 @@ public UserDetails getUserDetails(OidcIdToken idToken, Map attributes, boolean w */ //from keycloak protected void updateGroups(Map> profileGroups, User user) { - // First we remove all previous groups - userGroupRepository.deleteAll(UserGroupSpecs.hasUserId(user.getId())); + Set userGroups = new HashSet<>(); // Now we add the groups for (Profile p : profileGroups.keySet()) { @@ -201,13 +202,13 @@ protected void updateGroups(Map> profileGroups, User user) ug.setGroup(group); ug.setUser(user); ug.setProfile(Profile.Editor); - userGroupRepository.save(ug); + userGroups.add(ug); } - userGroupRepository.save(usergroup); + userGroups.add(usergroup); } } - } - + userGroupRepository.updateUserGroups(user.getId(), userGroups); + } } diff --git a/core/src/main/java/org/fao/geonet/kernel/security/shibboleth/ShibbolethUserUtils.java b/core/src/main/java/org/fao/geonet/kernel/security/shibboleth/ShibbolethUserUtils.java index b99c0cb9bf2..c17e9e00788 100644 --- a/core/src/main/java/org/fao/geonet/kernel/security/shibboleth/ShibbolethUserUtils.java +++ b/core/src/main/java/org/fao/geonet/kernel/security/shibboleth/ShibbolethUserUtils.java @@ -48,7 +48,9 @@ import jeeves.component.ProfileManager; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @author ETj (etj at geo-solutions.it) @@ -153,9 +155,6 @@ protected UserDetails setupUser(ServletRequest request, ShibbolethUserConfigurat user = (User) authProvider.loadUserByUsername(username); if (config.isUpdateGroup()) { - // First we remove all previous groups - userGroupRepository.deleteAll(UserGroupSpecs.hasUserId(user.getId())); - // Now we add the groups assignGroups(groupRepository, userGroupRepository, roleGroups, roleGroupSeparator, user); @@ -222,6 +221,9 @@ protected UserDetails setupUser(ServletRequest request, ShibbolethUserConfigurat private void assignGroups(GroupRepository groupRepository, UserGroupRepository userGroupRepository, String[] role_groups, String separator, User user) { + + Set userGroups = new HashSet<>(); + // Assign groups int i = 0; @@ -258,14 +260,16 @@ private void assignGroups(GroupRepository groupRepository, UserGroupRepository u ug.setGroup(g); ug.setUser(user); ug.setProfile(Profile.Editor); - userGroupRepository.save(ug); + userGroups.add(ug); } } else { // Failback if no profile usergroup.setProfile(Profile.Guest); } - userGroupRepository.save(usergroup); + userGroups.add(usergroup); } + + userGroupRepository.updateUserGroups(user.getId(), userGroups); } private void assignProfile(String[] role_groups, String roleGroupSeparator, User user) { diff --git a/core/src/main/java/org/fao/geonet/kernel/setting/SettingInfo.java b/core/src/main/java/org/fao/geonet/kernel/setting/SettingInfo.java index cb72fac0a95..7a2f97b0623 100644 --- a/core/src/main/java/org/fao/geonet/kernel/setting/SettingInfo.java +++ b/core/src/main/java/org/fao/geonet/kernel/setting/SettingInfo.java @@ -1,5 +1,5 @@ //============================================================================= -//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the +//=== Copyright (C) 2001-2023 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== @@ -24,9 +24,6 @@ package org.fao.geonet.kernel.setting; import org.fao.geonet.ApplicationContextHolder; -import org.fao.geonet.constants.Geonet; - -import static org.fao.geonet.kernel.setting.SettingManager.isPortRequired; public class SettingInfo { @@ -39,46 +36,29 @@ public String getSiteName() { //--------------------------------------------------------------------------- /** - * Return a string like 'http://HOST[:PORT]' + * Despite the method name, returns the server URL. That's why it calls settingManager.getServerURL() instead + * of settingManager.getSiteURL(). + * + * @return a string like 'http://HOST[:PORT]' */ public String getSiteUrl() { SettingManager settingManager = ApplicationContextHolder.get().getBean(SettingManager.class); + return settingManager.getServerURL(); + } - String protocol = settingManager.getValue(Settings.SYSTEM_SERVER_PROTOCOL); - Integer port; - String host = settingManager.getValue(Settings.SYSTEM_SERVER_HOST); - Integer configuredPort = toIntOrNull(Settings.SYSTEM_SERVER_PORT); - if (configuredPort != null) { - port = configuredPort; - } else if (protocol.equalsIgnoreCase(Geonet.HttpProtocol.HTTPS)) { - port = Geonet.DefaultHttpPort.HTTPS; - } else { - port = Geonet.DefaultHttpPort.HTTP; - } - - StringBuffer sb = new StringBuffer(protocol + "://"); - - sb.append(host); - - if (isPortRequired(protocol, port + "")) { - sb.append(":"); - sb.append(port); - } - return sb.toString(); + /** + * Retrieves the server port. + * + * @return the server port. + */ + public Integer getSitePort() { + SettingManager settingManager = ApplicationContextHolder.get().getBean(SettingManager.class); + return settingManager.getServerPort(); } //--------------------------------------------------------------------------- - private Integer toIntOrNull(String key) { - try { - SettingManager settingManager = ApplicationContextHolder.get().getBean(SettingManager.class); - return Integer.parseInt(settingManager.getValue(key)); - } catch (NumberFormatException e) { - return null; - } - } - public String getSelectionMaxRecords() { SettingManager settingManager = ApplicationContextHolder.get().getBean(SettingManager.class); String value = settingManager.getValue(Settings.SYSTEM_SELECTIONMANAGER_MAXRECORDS); @@ -102,6 +82,6 @@ public boolean isSearchStatsEnabled() { public String getFeedbackEmail() { SettingManager settingManager = ApplicationContextHolder.get().getBean(SettingManager.class); - return settingManager.getValue("system/feedback/email"); + return settingManager.getValue(Settings.SYSTEM_FEEDBACK_EMAIL); } } diff --git a/core/src/main/java/org/fao/geonet/kernel/setting/SettingManager.java b/core/src/main/java/org/fao/geonet/kernel/setting/SettingManager.java index 4d0c7ae9dd0..a3cd94bcb3c 100644 --- a/core/src/main/java/org/fao/geonet/kernel/setting/SettingManager.java +++ b/core/src/main/java/org/fao/geonet/kernel/setting/SettingManager.java @@ -1,5 +1,5 @@ //============================================================================= -//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the +//=== Copyright (C) 2001-2023 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== @@ -431,6 +431,15 @@ String getSiteURL(String language) { public @Nonnull String getNodeURL() { + return getBaseURL() + getNodeId() + "/"; + } + + /** + * Return node id - i.e. srv + */ + public + @Nonnull + String getNodeId() { String nodeId = NodeInfo.DEFAULT_NODE; try { NodeInfo node = ApplicationContextHolder.get().getBean(NodeInfo.class); @@ -438,7 +447,7 @@ String getNodeURL() { nodeId = node.getId(); } } catch (Exception e) {} - return getBaseURL() + nodeId + "/"; + return nodeId; } /** * Return complete node URL eg. http://localhost:8080/geonetwork/ @@ -457,9 +466,36 @@ String getBaseURL() { String getServerURL() { String protocol = getValue(Settings.SYSTEM_SERVER_PROTOCOL); String host = getValue(Settings.SYSTEM_SERVER_HOST); - String port = getValue(Settings.SYSTEM_SERVER_PORT); + Integer port = getServerPort(); - return protocol + "://" + host + (isPortRequired(protocol, port) ? ":" + port : ""); + StringBuffer sb = new StringBuffer(protocol + "://"); + + sb.append(host); + + if (isPortRequired(protocol, port + "")) { + sb.append(":"); + sb.append(port); + } + + return sb.toString(); + } + + public Integer getServerPort() { + String protocol = getValue(Settings.SYSTEM_SERVER_PROTOCOL); + + // some conditional logic to handle the case where there's no port in the settings + Integer sitePort; + + Integer configuredPort = getValueAsInt(Settings.SYSTEM_SERVER_PORT, -1); + if (configuredPort != -1) { + sitePort = configuredPort; + } else if (protocol != null && protocol.equalsIgnoreCase(Geonet.HttpProtocol.HTTPS)) { + sitePort = Geonet.DefaultHttpPort.HTTPS; + } else { + sitePort = Geonet.DefaultHttpPort.HTTP; + } + + return sitePort; } public static boolean isPortRequired(String protocol, String port) { @@ -471,4 +507,12 @@ public static boolean isPortRequired(String protocol, String port) { return true; } } + + private Integer toIntOrNull(String key) { + try { + return Integer.parseInt(getValue(key)); + } catch (NumberFormatException e) { + return null; + } + } } 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 238fbd2a813..e00b9e0c2b6 100644 --- a/core/src/main/java/org/fao/geonet/resources/ResourceFilter.java +++ b/core/src/main/java/org/fao/geonet/resources/ResourceFilter.java @@ -102,7 +102,7 @@ public Instance(ServletRequest request, ServletResponse response) throws IOExcep 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/" + siteId + ".ico", defaultImageBytes, -1)); } @@ -143,7 +143,7 @@ public void execute() throws IOException { if (filename.equals("images/logos/" + siteId + ".ico")) { favicon = resources.loadResource(resourcesDir, servletContext, appPath, "images/logos/" + siteId + ".ico", favicon.one(), favicon.two()); - AddFavIcon(nodeId, favicon); + addFavIcon(nodeId, favicon); httpServletResponse.setContentLength(favicon.one().length); httpServletResponse.addHeader("Cache-Control", "max-age=" + FIVE_DAYS + ", public"); @@ -167,7 +167,7 @@ public void execute() throws IOException { } } - private synchronized void AddFavIcon(String nodeId, Pair favicon) { + private synchronized void addFavIcon(String nodeId, Pair favicon) { if (faviconMap.containsKey(nodeId)) { faviconMap.replace(nodeId, favicon); } else { diff --git a/core/src/main/java/org/fao/geonet/util/MailUtil.java b/core/src/main/java/org/fao/geonet/util/MailUtil.java index f9fbf53aebd..fc0c743c6fe 100644 --- a/core/src/main/java/org/fao/geonet/util/MailUtil.java +++ b/core/src/main/java/org/fao/geonet/util/MailUtil.java @@ -68,6 +68,7 @@ public static Boolean sendHtmlMail(List toAddress, String subject, email.setSubject(subject); try { + email.setCharset(EmailConstants.UTF_8); email.setHtmlMsg(htmlMessage); } catch (EmailException e1) { Log.error("Error setting email HTML content. Subject:" + subject, e1); diff --git a/core/src/main/java/org/fao/geonet/util/XslUtil.java b/core/src/main/java/org/fao/geonet/util/XslUtil.java index 5d0dc08d419..b68d3749649 100644 --- a/core/src/main/java/org/fao/geonet/util/XslUtil.java +++ b/core/src/main/java/org/fao/geonet/util/XslUtil.java @@ -100,11 +100,13 @@ import org.owasp.esapi.errors.EncodingException; import org.owasp.esapi.reference.DefaultEncoder; import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.w3c.dom.Node; import org.xml.sax.SAXException; @@ -438,6 +440,19 @@ public static String getNodeName(String key, String lang, boolean withOrganizati + (withOrganization ? " - " + settingsMan.getValue(SYSTEM_SITE_ORGANIZATION) : ""); } + + /** + * Return the ID of the current node (catalog or subportal). + * If the main one, then srv. + * If a sub portal, use the sub portal key. + * + * @return + */ + public static String getNodeId() { + return ApplicationContextHolder.get().getBean(org.fao.geonet.NodeInfo.class).getId(); + } + + public static String getNodeLogo(String key) { Optional source = getSource(key); return source.isPresent() ? source.get().getLogo() : ""; @@ -520,7 +535,19 @@ public static Node downloadJsonAsXML(String url) { return null; } - /** + /** + * Check if user is authenticated. + */ + public static boolean isAuthenticated() throws Exception { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || AnonymousAuthenticationToken.class.isAssignableFrom(authentication.getClass())) { + return false; + } + return authentication.isAuthenticated(); + } + + + /** * Check if security provider require login form */ public static boolean isDisableLoginForm() { diff --git a/core/src/main/java/org/geonetwork/map/wms/SLDUtil.java b/core/src/main/java/org/geonetwork/map/wms/SLDUtil.java index 35bd9752459..188a1fb0968 100644 --- a/core/src/main/java/org/geonetwork/map/wms/SLDUtil.java +++ b/core/src/main/java/org/geonetwork/map/wms/SLDUtil.java @@ -240,6 +240,12 @@ private static Filter generateFilter2(String fieldName, JSONObject jsonObject) t } else if(filterType.equals("PropertyIsBetween")) { if (parameters.size() != 2) throw new JSONException("Invalid parameter count"); return ff2.between(ff2.property(fieldName), ff2.literal(parameters.get(0)), ff2.literal(parameters.get(1))); + } else if(filterType.equals("PropertyIsBetweenExclusive")) { + if (parameters.size() != 2) throw new JSONException("Invalid parameter count"); + return ff2.and( + ff2.greater(ff2.property(fieldName), ff2.literal(parameters.get(0))), + ff2.less(ff2.property(fieldName), ff2.literal(parameters.get(1))) + ); } else { // Currently, no implementation of topological or distance operators throw new JSONException("No implementation for filter type : " + filterType); diff --git a/core/src/test/java/org/fao/geonet/GeonetTestFixture.java b/core/src/test/java/org/fao/geonet/GeonetTestFixture.java index d8fc9aa47d0..093a8065b17 100644 --- a/core/src/test/java/org/fao/geonet/GeonetTestFixture.java +++ b/core/src/test/java/org/fao/geonet/GeonetTestFixture.java @@ -40,7 +40,6 @@ import org.fao.geonet.utils.IO; import org.fao.geonet.utils.TransformerFactoryFactory; import org.fao.geonet.utils.Xml; -import org.geotools.data.DataStore; import org.jdom.Element; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; @@ -68,14 +67,21 @@ public class GeonetTestFixture { private volatile static FileSystemPool.CreatedFs templateFs; private volatile static SchemaManager templateSchemaManager; @Autowired - private EsSearchManager templateSearchManager; - @Autowired - protected DataStore dataStore; + private EsSearchManager esSearchManager; @Autowired private ConfigurableApplicationContext _applicationContext; @Autowired private IsoLanguagesMapper isoLanguagesMapper; - + @Autowired + private GeonetworkDataDirectory geonetworkDataDirectory; + @Autowired + private ThesaurusManager thesaurusManager; + @Autowired + private DataManager dataManager; + @Autowired + private DataSource dataSource; + @Autowired + private SettingManager settingManager; private FileSystemPool.CreatedFs currentFs; @@ -111,17 +117,15 @@ public boolean accept(Path entry) throws IOException { } }); - Path schemaPluginsDir = templateDataDirectory.resolve("config/schema_plugins"); deploySchema(webappDir, schemaPluginsDir); - final GeonetworkDataDirectory geonetworkDataDirectory = _applicationContext.getBean(GeonetworkDataDirectory.class); - final ServiceConfig serviceConfig = new ServiceConfig(Lists.newArrayList()); + final ServiceConfig serviceConfig = new ServiceConfig(Lists.newArrayList()); geonetworkDataDirectory.init("geonetwork", webappDir, templateDataDirectory, serviceConfig, null); test.addTestSpecificData(geonetworkDataDirectory); // Create ES index - _applicationContext.getBean(EsSearchManager.class).init(false, Optional.empty()); + esSearchManager.init(true, Optional.empty()); templateSchemaManager = initSchemaManager(webappDir, geonetworkDataDirectory); @@ -130,7 +134,7 @@ public boolean accept(Path entry) throws IOException { isoLanguagesMapper.reinit(); } - final String fsName = test.getClass().getSimpleName().replaceAll("[^a-z0-9A-Z]", "") + UUID.randomUUID().toString(); + final String fsName = test.getClass().getSimpleName().replaceAll("[^a-z0-9A-Z]", "") + UUID.randomUUID(); currentFs = FILE_SYSTEM_POOL.get(fsName); assertTrue(Files.isDirectory(currentFs.dataDir.resolve("config"))); @@ -142,35 +146,21 @@ public boolean accept(Path entry) throws IOException { final GeonetworkDataDirectory dataDir = configureDataDir(test, webappDir, currentFs.dataDir); configureNewSchemaManager(dataDir, webappDir); - // TODO: I don't know why but this corrupts other tests that will fail depending on the run order: - // assertCorrectDataDir(); - // for example, running GeonetworkDataDirectoryMultiNodeServiceConfigOnlySystemDataDirSetTest, then - // GeonetworkDataDirectoryMultiNodeSystemPropertyOnlySystemDataDirSetTest with that line enabled, the second fails. - -// if (test.resetLuceneIndex()) { -// _directoryFactory.resetIndex(); -// } - ServiceContext serviceContext = test.createServiceContext(); ApplicationContextHolder.set(_applicationContext); serviceContext.setAsThreadLocal(); -// TODOES -// _applicationContext.getBean(EsSearchManager.class).initNonStaticData(100); - _applicationContext.getBean(DataManager.class).init(serviceContext, false); - _applicationContext.getBean(ThesaurusManager.class).init(true, serviceContext, "WEB-INF/data/config/codelist"); + dataManager.init(serviceContext, false); + thesaurusManager.init(true, serviceContext, "WEB-INF/data/config/codelist"); addSourceUUID(dataDir); - final DataSource dataSource = _applicationContext.getBean(DataSource.class); try (Connection conn = dataSource.getConnection()) { - ThreadUtils.init(conn.getMetaData().getURL(), _applicationContext.getBean(SettingManager.class)); + ThreadUtils.init(conn.getMetaData().getURL(), settingManager); } - } - protected void configureNewSchemaManager(GeonetworkDataDirectory dataDir, Path webappDir) throws Exception { final SchemaManager schemaManager = _applicationContext.getBean(SchemaManager.class); schemaManager.configureFrom(templateSchemaManager, webappDir, dataDir); @@ -179,7 +169,7 @@ protected void configureNewSchemaManager(GeonetworkDataDirectory dataDir, Path w protected void addSourceUUID(GeonetworkDataDirectory dataDirectory) { String siteUuid = dataDirectory.getSystemDataDir().getFileName().toString(); - _applicationContext.getBean(SettingManager.class).setSiteUuid(siteUuid); + settingManager.setSiteUuid(siteUuid); final SourceRepository sourceRepository = _applicationContext.getBean(SourceRepository.class); List sources = sourceRepository.findAll(); if (sources.isEmpty()) { diff --git a/core/src/test/java/org/fao/geonet/kernel/AbstractIntegrationTestWithMockedSingletons.java b/core/src/test/java/org/fao/geonet/kernel/AbstractIntegrationTestWithMockedSingletons.java index 8c07a7fa929..2238444e734 100644 --- a/core/src/test/java/org/fao/geonet/kernel/AbstractIntegrationTestWithMockedSingletons.java +++ b/core/src/test/java/org/fao/geonet/kernel/AbstractIntegrationTestWithMockedSingletons.java @@ -1,20 +1,70 @@ package org.fao.geonet.kernel; +import jeeves.server.context.ServiceContext; import org.fao.geonet.AbstractCoreIntegrationTest; +import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.domain.Metadata; +import org.fao.geonet.domain.MetadataType; +import org.fao.geonet.kernel.datamanager.IMetadataManager; +import org.fao.geonet.kernel.search.IndexingMode; +import org.fao.geonet.repository.SourceRepository; +import org.jdom.Element; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; +import java.util.UUID; +import static org.fao.geonet.domain.MetadataType.METADATA; +import static org.fao.geonet.kernel.UpdateDatestamp.NO; + +@ContextConfiguration( + locations = {"classpath:mocked-core-repository-test-context.xml"} +) public abstract class AbstractIntegrationTestWithMockedSingletons extends AbstractCoreIntegrationTest { - private static SpringLocalServiceInvoker mockInvoker; + private static final int TEST_OWNER_ID = 42; + + @Autowired + private IMetadataManager metadataManager; + + @Autowired + private SchemaManager schemaManager; + + @Autowired + private SourceRepository sourceRepository; + + @Autowired + protected SpringLocalServiceInvoker springLocalServiceInvoker; + + protected AbstractMetadata insertTemplateResourceInDb(ServiceContext serviceContext, Element element) throws Exception { + return insertTemplateResourceInDb(serviceContext, element, METADATA); + } + + protected AbstractMetadata insertTemplateResourceInDb(ServiceContext serviceContext, Element element, MetadataType type) throws Exception { + loginAsAdmin(serviceContext); + + Metadata metadata = new Metadata(); + metadata.setDataAndFixCR(element) + .setUuid(UUID.randomUUID().toString()); + metadata.getDataInfo() + .setRoot(element.getQualifiedName()) + .setSchemaId(schemaManager.autodetectSchema(element)) + .setType(type) + .setPopularity(1000); + metadata.getSourceInfo() + .setOwner(TEST_OWNER_ID) + .setSourceId(sourceRepository.findAll().get(0).getUuid()); + metadata.getHarvestInfo() + .setHarvested(false); - public SpringLocalServiceInvoker resetAndGetMockInvoker() { - if (mockInvoker == null) { - mockInvoker = mock(SpringLocalServiceInvoker.class); - _applicationContext.getBeanFactory().registerSingleton(SpringLocalServiceInvoker.class.getCanonicalName(), mockInvoker); - } - reset(mockInvoker); - return mockInvoker; + return metadataManager.insertMetadata( + serviceContext, + metadata, + element, + IndexingMode.full, + false, + NO, + false, + true); } } diff --git a/core/src/test/java/org/fao/geonet/kernel/ElasticSearchIndexingTest.java b/core/src/test/java/org/fao/geonet/kernel/ElasticSearchIndexingTest.java new file mode 100644 index 00000000000..085ac3922d2 --- /dev/null +++ b/core/src/test/java/org/fao/geonet/kernel/ElasticSearchIndexingTest.java @@ -0,0 +1,66 @@ +package org.fao.geonet.kernel; + +import jeeves.server.context.ServiceContext; +import org.elasticsearch.action.search.SearchResponse; +import org.fao.geonet.AbstractCoreIntegrationTest; +import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.kernel.search.EsSearchManager; +import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.kernel.setting.Settings; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.net.URL; +import java.util.Objects; +import static org.junit.Assert.*; + +public class ElasticSearchIndexingTest extends AbstractIntegrationTestWithMockedSingletons { + + @Autowired + private EsSearchManager searchManager; + + @Autowired + private SettingManager settingManager; + + private ServiceContext serviceContext; + + @Before + public void setUp() throws Exception { + serviceContext = createServiceContext(); + settingManager.setValue(Settings.SYSTEM_XLINKRESOLVER_ENABLE, true); + } + + @Test + public void complexDatesAreIndexedCheck() throws Exception { + // GIVEN + AbstractMetadata dbInsertedSimpleDataMetadata = loadMetadataWithTemporalExtentUsingSimpleDates(); + validateIndexedExpectedData(dbInsertedSimpleDataMetadata, "forest", 1); + validateIndexedExpectedData(dbInsertedSimpleDataMetadata, "holocene", 0); + URL dateResource = AbstractCoreIntegrationTest.class.getResource("kernel/holocene.xml"); + Element dateElement = Xml.loadStream(Objects.requireNonNull(dateResource).openStream()); + + // WHEN + AbstractMetadata dbInsertedMetadata = insertTemplateResourceInDb(serviceContext, dateElement); + + //THEN + SearchResponse response = this.searchManager.query("_id:" + dbInsertedMetadata.getUuid() + " AND resourceTitleObject.default:holocene", null, 0, 10); + long actualHitNbr = response.getHits().getTotalHits().value; + assertEquals(String.format("Incorrect indexation of Holocene data with complex date due to: %s and %s", response, dbInsertedMetadata), 1, actualHitNbr); + } + + private AbstractMetadata loadMetadataWithTemporalExtentUsingSimpleDates() throws Exception { + URL dateResource = AbstractCoreIntegrationTest.class.getResource("kernel/forest.xml"); + Element dateElement = Xml.loadStream(Objects.requireNonNull(dateResource).openStream()); + return insertTemplateResourceInDb(serviceContext, dateElement); + } + + private void validateIndexedExpectedData(AbstractMetadata dbInsertedSimpleDataMetadata, String resourceTitle, long expectedHitNbr) throws Exception { + SearchResponse searchResponse = this.searchManager.query("resourceTitleObject.default:" + resourceTitle, null, 0, 10); + long actualHitNbr = searchResponse.getHits().getTotalHits().value; + String assertionErrorMessage = "The %s data was not indexed the expected number of times due to: %s and %s"; + assertEquals(String.format(assertionErrorMessage, resourceTitle, searchResponse, dbInsertedSimpleDataMetadata), expectedHitNbr, actualHitNbr); + } +} diff --git a/core/src/test/java/org/fao/geonet/kernel/LocalXLinksInMetadataIntegrationTest.java b/core/src/test/java/org/fao/geonet/kernel/LocalXLinksInMetadataIntegrationTest.java index cd6923d4d18..5b141a82cb7 100644 --- a/core/src/test/java/org/fao/geonet/kernel/LocalXLinksInMetadataIntegrationTest.java +++ b/core/src/test/java/org/fao/geonet/kernel/LocalXLinksInMetadataIntegrationTest.java @@ -110,40 +110,38 @@ public void testResolveLocalXLink() throws Exception { String id = _dataManager.insertMetadata(context, schema, metadata, uuid, owner, groupOwner, source, metadataType, null, null, createDate, changeDate, false, IndexingMode.none); - SpringLocalServiceInvoker mockInvoker = resetAndGetMockInvoker(); - String keyword1 = "World"; Element element1 = new SAXBuilder().build(new StringReader(String.format(responseTemplate, keyword1))).getRootElement(); - when(mockInvoker.invoke(any(String.class))).thenReturn(element1); + when(springLocalServiceInvoker.invoke(any(String.class))).thenReturn(element1); final String xpath = "*//gmd:descriptiveKeywords//gmd:keyword/gco:CharacterString"; assertNull(Xml.selectElement(metadata, xpath)); - verify(mockInvoker, never()).invoke(any(String.class)); + verify(springLocalServiceInvoker, never()).invoke(any(String.class)); final Element loadedMetadataNoXLinkAttributesNotEdit = _dataManager.getMetadata(context, id, false, false, false); assertEqualsText(keyword1, loadedMetadataNoXLinkAttributesNotEdit, xpath, GCO, GMD); - verify(mockInvoker, times(1)).invoke(any(String.class)); + verify(springLocalServiceInvoker, times(1)).invoke(any(String.class)); final Element loadedMetadataKeepXLinkAttributesNotEdit = _dataManager.getMetadata(context, id, false, false, true); assertEqualsText(keyword1, loadedMetadataKeepXLinkAttributesNotEdit, xpath, GCO, GMD); - verify(mockInvoker, times(2)).invoke(any(String.class)); + verify(springLocalServiceInvoker, times(2)).invoke(any(String.class)); final Element loadedMetadataNoXLinkAttributesEdit = _dataManager.getMetadata(context, id, false, true, false); assertEqualsText(keyword1, loadedMetadataNoXLinkAttributesEdit, xpath, GCO, GMD); - verify(mockInvoker, times(3)).invoke(any(String.class)); + verify(springLocalServiceInvoker, times(3)).invoke(any(String.class)); final Element loadedMetadataKeepXLinkAttributesEdit = _dataManager.getMetadata(context, id, false, true, true); assertEqualsText(keyword1, loadedMetadataKeepXLinkAttributesEdit, xpath, GCO, GMD); - verify(mockInvoker, times(4)).invoke(any(String.class)); + verify(springLocalServiceInvoker, times(4)).invoke(any(String.class)); Processor.clearCache(); String keyword2 = "Other Word"; Element element2 = new SAXBuilder().build(new StringReader(String.format(responseTemplate, keyword2))).getRootElement(); - when(mockInvoker.invoke(any(String.class))).thenReturn(element2); + when(springLocalServiceInvoker.invoke(any(String.class))).thenReturn(element2); final Element newLoad = _dataManager.getMetadata(context, id, false, true, true); assertEqualsText(keyword2, newLoad, xpath, GCO, GMD); - verify(mockInvoker, times(5)).invoke(any(String.class)); + verify(springLocalServiceInvoker, times(5)).invoke(any(String.class)); } } diff --git a/core/src/test/java/org/fao/geonet/kernel/LocalXLinksUpdateDeleteTest.java b/core/src/test/java/org/fao/geonet/kernel/LocalXLinksUpdateDeleteTest.java index cf7e784e20e..e67834f85ef 100644 --- a/core/src/test/java/org/fao/geonet/kernel/LocalXLinksUpdateDeleteTest.java +++ b/core/src/test/java/org/fao/geonet/kernel/LocalXLinksUpdateDeleteTest.java @@ -3,14 +3,10 @@ import jeeves.server.context.ServiceContext; import org.fao.geonet.AbstractCoreIntegrationTest; import org.fao.geonet.domain.AbstractMetadata; -import org.fao.geonet.domain.Metadata; -import org.fao.geonet.domain.MetadataType; import org.fao.geonet.kernel.datamanager.IMetadataManager; -import org.fao.geonet.kernel.search.EsSearchManager; import org.fao.geonet.kernel.search.IndexingMode; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; -import org.fao.geonet.repository.SourceRepository; import org.fao.geonet.utils.Xml; import org.jdom.Attribute; import org.jdom.Element; @@ -20,11 +16,9 @@ import java.net.URL; import java.util.Arrays; -import java.util.UUID; import static org.fao.geonet.domain.MetadataType.SUB_TEMPLATE; import static org.fao.geonet.domain.MetadataType.TEMPLATE; -import static org.fao.geonet.kernel.UpdateDatestamp.NO; import static org.fao.geonet.schema.iso19139.ISO19139Namespaces.GCO; import static org.fao.geonet.schema.iso19139.ISO19139Namespaces.GMD; import static org.junit.Assert.assertNotNull; @@ -34,31 +28,18 @@ public class LocalXLinksUpdateDeleteTest extends AbstractIntegrationTestWithMockedSingletons { - - private static final int TEST_OWNER = 42; - @Autowired private IMetadataManager metadataManager; - @Autowired - private SchemaManager schemaManager; - - @Autowired - private SourceRepository sourceRepository; - - @Autowired - private EsSearchManager searchManager; - @Autowired private SettingManager settingManager; - private ServiceContext context; + private ServiceContext serviceContext; @Before public void setUp() throws Exception { - this.context = createServiceContext(); + serviceContext = createServiceContext(); settingManager.setValue(Settings.SYSTEM_XLINKRESOLVER_ENABLE, true); - resetAndGetMockInvoker(); } @Test @@ -73,7 +54,7 @@ public void updateHasToRegisterReferrersForIndexation() throws Exception { // assertFalse(context.getBean(IndexingList.class).getIdentifiers().contains(vicinityMapMetadata.getId())); Xml.selectElement(contactElement, "gmd:individualName/gco:CharacterString", Arrays.asList(GMD, GCO)).setText("momo"); - metadataManager.updateMetadata(context, + metadataManager.updateMetadata(serviceContext, Integer.toString(contactMetadata.getId()), contactElement, false, @@ -93,8 +74,8 @@ public void deleteAllowedWhenRefNotExists() throws Exception { AbstractMetadata contactMetadata = insertContact(); AbstractMetadata vicinityMapMetadata = insertVicinityMap(contactMetadata); - metadataManager.deleteMetadata(context, Integer.toString(vicinityMapMetadata.getId())); - metadataManager.deleteMetadata(context, Integer.toString(contactMetadata.getId())); + metadataManager.deleteMetadata(serviceContext, Integer.toString(vicinityMapMetadata.getId())); + metadataManager.deleteMetadata(serviceContext, Integer.toString(contactMetadata.getId())); assertNull(metadataManager.getMetadata(Integer.toString(contactMetadata.getId()))); } @@ -105,7 +86,7 @@ public void deleteHasToBeForbiddenWhenRefExistsAndSettingsSaySo() throws Excepti insertVicinityMap(contactMetadata); try { - metadataManager.deleteMetadata(context, + metadataManager.deleteMetadata(serviceContext, Integer.toString(contactMetadata.getId())); } catch (Exception e) { @@ -119,46 +100,16 @@ public void deleteHasToBeAllowedWhenRefExistsAndSettingsSaySo() throws Exception AbstractMetadata contactMetadata = insertContact(); insertVicinityMap(contactMetadata); - metadataManager.deleteMetadata(context, Integer.toString(contactMetadata.getId())); + metadataManager.deleteMetadata(serviceContext, Integer.toString(contactMetadata.getId())); assertNull(metadataManager.getMetadata(Integer.toString(contactMetadata.getId()))); } - private AbstractMetadata insertTemplateResourceInDb(Element element, MetadataType type) throws Exception { - loginAsAdmin(context); - - Metadata metadata = new Metadata(); - metadata.setDataAndFixCR(element) - .setUuid(UUID.randomUUID().toString()); - metadata.getDataInfo() - .setRoot(element.getQualifiedName()) - .setSchemaId(schemaManager.autodetectSchema(element)) - .setType(type) - .setPopularity(1000); - metadata.getSourceInfo() - .setOwner(TEST_OWNER) - .setSourceId(sourceRepository.findAll().get(0).getUuid()); - metadata.getHarvestInfo() - .setHarvested(false); - - AbstractMetadata dbInsertedMetadata = metadataManager.insertMetadata( - context, - metadata, - element, - IndexingMode.full, - false, - NO, - false, - true); - - return dbInsertedMetadata; - } - private AbstractMetadata insertVicinityMap(AbstractMetadata contactMetadata) throws Exception { URL vicinityMapResource = AbstractCoreIntegrationTest.class.getResource("kernel/vicinityMap.xml"); Element vicinityMapElement = Xml.loadStream(vicinityMapResource.openStream()); Attribute href = (Attribute) Xml.selectElement(vicinityMapElement, "gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact").getAttributes().get(0); href.setValue(href.getValue().replace("@contact_uuid@", contactMetadata.getUuid())); - return insertTemplateResourceInDb(vicinityMapElement, TEMPLATE); + return insertTemplateResourceInDb(serviceContext, vicinityMapElement, TEMPLATE); } private AbstractMetadata insertContact() throws Exception { @@ -168,10 +119,9 @@ private AbstractMetadata insertContact() throws Exception { } private AbstractMetadata insertContact(Element contactElement) throws Exception { - AbstractMetadata contactMetadata = insertTemplateResourceInDb(contactElement, SUB_TEMPLATE); + AbstractMetadata contactMetadata = insertTemplateResourceInDb(serviceContext, contactElement, SUB_TEMPLATE); - SpringLocalServiceInvoker mockInvoker = resetAndGetMockInvoker(); - when(mockInvoker.invoke(any(String.class))).thenReturn(contactElement); + when(springLocalServiceInvoker.invoke(any(String.class))).thenReturn(contactElement); return contactMetadata; } } diff --git a/core/src/test/resources/mocked-core-repository-test-context.xml b/core/src/test/resources/mocked-core-repository-test-context.xml new file mode 100644 index 00000000000..b3d835c52ad --- /dev/null +++ b/core/src/test/resources/mocked-core-repository-test-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/core/src/test/resources/org/fao/geonet/api/Messages.properties b/core/src/test/resources/org/fao/geonet/api/Messages.properties index cf8222548cc..7146a6b8f93 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages.properties @@ -92,6 +92,9 @@ user_feedback_text=User %s (%s - %s)\n\ \n\ See record %s status_email_text=GeoNetwork user %s (%s) edited metadata record #%s +metadata_save_submit_text=Save and submit metadata +metadata_save_approve_text=Save and approve metadata +metadata_status_editing_instance_created_text=Editing instance created # SiteName / Workflow / recordTitle statusName by userName status_change_default_email_subject={0} / Workflow / '{{'index:resourceTitleObject'}}' {1} by {2} status_change_default_email_text={0} modified the status of the record '{{'index:resourceTitleObject'}}'.\n\ @@ -101,7 +104,7 @@ Message: \n\ {1} \n\ \n\ View record: \n\ -{7} +{{link}} # SiteName / Task / recordTitle statusName by userName status_change_doiCreationTask_email_subject={0} / Task / {1} for '{{'index:resourceTitleObject'}}' @@ -113,7 +116,7 @@ Message: \n\ {1} \n\ \n\ View record: \n\ -{7} +{{link}} # TODO: Link to DOI creation panel api.groups.group_not_found=Group with ID ''{0}'' not found in this catalog. @@ -138,7 +141,7 @@ username.field.required=Username is required password.field.length=Password size should be between {min} and {max} characters password.field.invalid=Password must contain at least 1 uppercase, 1 lowercase, 1 number and 1 symbol. Symbols include: `~!@#$%^&*()-_=+[]{}\\|;:'",.<>/?'); api.exception.forbidden=Access denied -api.exception.forbidden.description=Access is denied. To access, try again with a user containing more priviledges. +api.exception.forbidden.description=Access is denied. To access, try again with a user containing more privileges. api.exception.resourceNotFound=Resource not found api.exception.resourceNotFound.description=Resource could not be located. api.exception.resourceAlreadyExists=Resource already exists diff --git a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties index 21500366c00..d4dfb7add40 100644 --- a/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties +++ b/core/src/test/resources/org/fao/geonet/api/Messages_fre.properties @@ -80,6 +80,9 @@ user_feedback_text=Utilisateur %s (%s - %s)\n\ \n\ Consulter la fiche %s status_email_text=L''utilisateur %s (%s) a \u00E9dit\u00E9 une fiche #%s +metadata_save_submit_text=Enregistrer et soumettre les m\u00E9tadonn\u00E9es +metadata_save_approve_text=Enregistrer et approuver les m\u00E9tadonn\u00E9es +metadata_status_editing_instance_created_text=L'instance de modification a \u00E9t\u00E9 cr\u00E9\u00E9e # SiteName / Workflow / recordTitle statusName by userName status_change_default_email_subject={0} / Cycle de vie / '{{'index:resourceTitleObject'}}' {1} par {2} status_change_default_email_text={0} a modifi\u00E9 l''\u00E9tat de la fiche '{{'index:resourceTitleObject'}}'.\n\ @@ -89,7 +92,7 @@ Message: \n\ {1} \n\ \n\ Consulter la fiche : \n\ -{7} +{{link}} # SiteName / Task / recordTitle statusName by userName status_change_doiCreationTask_email_subject={0} / Action / {1} pour '{{'index:resourceTitleObject'}}' @@ -102,7 +105,7 @@ Message: \n\ {1} \n\ \n\ Consulter la fiche : \n\ -{7} +{{link}} # TODO: Link to DOI creation panel metadata_published_subject=%s / Publication de m\u00E9tadonn\u00E9es metadata_published_text=Les fiches suivantes ont \u00E9t\u00E9 trait\u00E9es:\n\ diff --git a/core/src/test/resources/org/fao/geonet/kernel/forest.xml b/core/src/test/resources/org/fao/geonet/kernel/forest.xml new file mode 100644 index 00000000000..cde5dc38252 --- /dev/null +++ b/core/src/test/resources/org/fao/geonet/kernel/forest.xml @@ -0,0 +1,837 @@ + + + 837750fd-5790-4263-ad6d-cd94c43cfe5b + + + + + + + + + + + + + + Atlas of Switzerland + + + Atlas der Schweiz + + + Atlas de la Suisse + + + Atlante della Svizzera + + + Atlas of Switzerland + + + + + + + + + +41 44 633 11 53 + + + + + + + Zurich + + + Zürich + + + Zurich + + + Zurigo + + + Zurich + + + + + 8093 + + + CH + + + atlasinfo@ethz.ch + + + + + + + https://www.atlasderschweiz.ch + + + text/html + + + + + 09h00 - 10h30 / 11h00 - 12h30 / 14h00 - 18h00 GMT+1 + + + + + + + + + + 2023-01-19 + + + ISO 19115/19119 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://www.opengis.net/def/crs/EPSG/0/4326 + + + INSPIRE RS registry + + + + + + + + + + + Forest + + + Wald + + + Forêt + + + Bosco + + + Forest + + + + + Forest + + + Wald + + + Forêt + + + Bosco + + + Forest + + + + + + + 2014-11-17 + + + + + + + + + + 2016-06-03 + + + + + + + + + + 2020-09-25 + + + + + + + + + + 34 + + + ads:online:maps + + + + + + + + + + Forest. Map type: Choropleths. Spatial extent: Switzerland. Time: 2010 + + + Wald. Kartentyp: Choroplethen. Räumliche Ausdehnung: Schweiz. Zeit: 2010 + + + Forêt. Type de carte: Choroplètes. Étendue spatiale: Suisse. Unité temporelle: 2010 + + + Bosco. Tipo di carta: Coropletiche. Estensione spaziale: Svizzera. Unità temporale: 2010 + + + Forest. Map type: Choropleths. Spatial extent: Switzerland. Time: 2010 + + + + + + + + + + Atlas of Switzerland + + + Atlas der Schweiz + + + Atlas de la Suisse + + + Atlante della Svizzera + + + Atlas of Switzerland + + + + + + + + + +41 44 633 11 53 + + + + + + + Zurich + + + Zürich + + + Zurich + + + Zurigo + + + Zurich + + + + + 8093 + + + CH + + + atlasinfo@ethz.ch + + + + + + + https://www.atlasderschweiz.ch + + + text/html + + + + + 09h00 - 10h30 / 11h00 - 12h30 / 14h00 - 18h00 GMT+1 + + + + + + + + + + + + Swiss Federal Office of Topography + + + Bundesamt für Landestopografie + + + Office fédéral de topographie + + + Ufficio federale di topografia + + + Swiss Federal Office of Topography + + + + + + + + + Wabern + + + Wabern + + + Wabern + + + Wabern + + + Wabern + + + + + CH + + + info@swisstopo.ch + + + + + + + http://www.swisstopo.admin.ch/ + + + text/html + + + + + + + + + + + + + + + + + + + + + Basemaps + + + Basiskarten + + + Fonds de carte + + + Carte di base + + + Basemaps + + + + + Elements + + + Elemente + + + Éléments + + + Elementi + + + Elements + + + + + + + + + + Atlas of Switzerland - Categories + + + Atlas der Schweiz - Kategorien + + + Atlas de la Suisse - Catégories + + + Atlante della Svizzera - Categorie + + + Atlas of Switzerland - Categories + + + + + + + 2016-04-01 + + + + + + + + + + + + + + Land cover + + + Bodenbedeckung + + + Couverture du sol + + + Copertura del suolo + + + Land cover + + + + + + + + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + + + + + 2008-06-01 + + + + + + + + + + + + + + See end-user license agreement + + + Siehe Endbenutzer-Lizenzvertrag + + + Voir conditions générales d'utilisation + + + Vedi accordo di licenza con l'utente finale + + + See end-user license agreement + + + + + + + + + + + + There are no limitations on public access to spatial data sets and services. + + + + + + + + + + The conditions applying to access and use are unknown. + + + + + + + + + f6243e5a-3816-49ab-9eeb-14f0c16d8847 + + + + + + + + + + + + + + + + + 200000 + + + + + + + + + + + + + + + + + + + + + + imageryBaseMapsEarthCover + + + imageryBaseMapsEarthCover_EarthCover + + + + + Switzerland + + + Schweiz + + + Suisse + + + Svizzera + + + Switzerland + + + + + + + 5.606041613827549 + + + 10.988229452947234 + + + 45.53396348515653 + + + 47.97997376576611 + + + + + + + + 2010-01-01T00:00:00 + 2010-12-31T23:59:59 + + + + + + + + + + + + + + Digital map in a web atlas + + + Digitale Karte in einem Web-Atlas + + + Carte digitale dans un atlas web + + + Mappa digitale in un web atlante + + + Digital map in a web atlas + + + + + - + + + + + + + + + https://www.atlasofswitzerland.ch/forest/ + + + WWW:LINK-1.0-http--link + + + + + + + + + + + + + + + + + + + + + + Conformity_001 + + + INSPIRE + + + + + + + + + COMMISSION REGULATION (EU) No 1089/2010 of 23 November 2010 + implementing Directive 2007/2/EC of the European Parliament and of the + Council as regards interoperability of spatial data sets and services + + + VERORDNUNG (EG) Nr. 1089/2010 + DER KOMMISSION vom 23. November 2010 zur Durchführung der Richtlinie + 2007/2/EG des Europäischen Parlaments und des Rates hinsichtlich der + Interoperabilität von Geodatensätzen und -diensten + + + RÈGLEMENT (UE) No 1089/2010 + DE LA COMMISSION du 23 novembre 2010 portant modalités d'application + de la directive 2007/2/CE du Parlement européen et du Conseil en ce + qui concerne l'interopérabilité des séries et des services de + données géographiques + + + REGOLAMENTO (UE) N. 1089/2010 + DELLA COMMISSIONE del 23 novembre 2010 recante attuazione della + direttiva 2007/2/CE del Parlamento europeo e del Consiglio per + quanto riguarda l'interoperabilità dei set di dati territoriali e + dei servizi di dati territoriali + + + COMMISSION REGULATION (EU) No + 1089/2010 of 23 November 2010 implementing Directive 2007/2/EC of + the European Parliament and of the Council as regards + interoperability of spatial data sets and services + + + + + + + 2010-12-08 + + + + + + + + + + See the referenced specification + + + Siehe referenzierte Spezifikation + + + Voir la spécification référencée + + + Vedi la specifica riferimento + + + See the referenced specification + + + + + true + + + + + + + + + Source data: Swiss Federal Office of Topography. Data processing: Atlas of Switzerland + + + Ursprungsdaten: Bundesamt für Landestopografie. + Datenverarbeitung: Atlas der Schweiz + + + Données originales: Office fédéral de topographie. + Traitement de données: Atlas de la Suisse + + + Dati di origine: Ufficio federale di topografia. + Trattamento dei dati: Atlante della Svizzera + + + Source data: Swiss Federal Office of Topography. Data + processing: Atlas of Switzerland + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/org/fao/geonet/kernel/holocene.xml b/core/src/test/resources/org/fao/geonet/kernel/holocene.xml new file mode 100644 index 00000000000..1d80e1256d6 --- /dev/null +++ b/core/src/test/resources/org/fao/geonet/kernel/holocene.xml @@ -0,0 +1,919 @@ + + + 4d2794d4-447e-4bbe-89ba-ab11cfdfc7b8 + + + + + + + + + + + + + + Atlas of Switzerland + + + Atlas der Schweiz + + + Atlas de la Suisse + + + Atlante della Svizzera + + + Atlas of Switzerland + + + + + + + + + +41 44 633 11 53 + + + + + + + Zurich + + + Zürich + + + Zurich + + + Zurigo + + + Zurich + + + + + 8093 + + + CH + + + atlasinfo@ethz.ch + + + + + + + https://www.atlasderschweiz.ch + + + text/html + + + + + 09h00 - 10h30 / 11h00 - 12h30 / 14h00 - 18h00 GMT+1 + + + + + + + + + + 2023-02-28 + + + ISO 19115/19119 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://www.opengis.net/def/crs/EPSG/0/4326 + + + INSPIRE RS registry + + + + + + + + + + + Volcanic eruptions in the holocene + + + Vulkane und Eruptionen weltweit + + + Eruptions volcaniques durant l'Holocène + + + Eruzioni vulcaniche nell’olocene + + + Volcanic eruptions in the holocene + + + + + Volcanoes: eruptions + + + Vulkane: Eruptionen + + + Volcans: éruptions + + + Vulcani: eruzioni + + + Volcanoes: eruptions + + + + + + + 2014-08-28 + + + + + + + + + + 2019-02-04 + + + + + + + + + + 2023-02-27 + + + + + + + + + + 163 + + + ads:online:maps + + + + + + + + + + Volcanic eruptions in the holocene. Map type: Symbols. Spatial extent: World. Times: 10,000–0 BC, 0–1699, 1700–1799, 1800–1899, 1900–1999, 2000–2021, Total + + + Vulkane und Eruptionen weltweit. Kartentyp: Symbole. Räumliche Ausdehnung: Welt. Zeiten: 10 000–0 v. Chr., 0–1699, 1700–1799, 1800–1899, 1900–1999, 2000–2021, Total + + + Eruptions volcaniques durant l'Holocène. Type de carte: Symboles. Étendue spatiale: Monde. Unités temporelles: 10 000–0 avant J-C, 0–1699, 1700–1799, 1800–1899, 1900–1999, 2000–2021, Total + + + Eruzioni vulcaniche nell’olocene. Tipo di carta: Simboli. Estensione spaziale: Mondo. Unità temporali: 10.000–0 a.C., 0–1699, 1700–1799, 1800–1899, 1900–1999, 2000–2021, Totale + + + Volcanic eruptions in the holocene. Map type: Symbols. Spatial extent: World. Times: 10,000–0 BC, 0–1699, 1700–1799, 1800–1899, 1900–1999, 2000–2021, Total + + + + + + + + + + Atlas of Switzerland + + + Atlas der Schweiz + + + Atlas de la Suisse + + + Atlante della Svizzera + + + Atlas of Switzerland + + + + + + + + + +41 44 633 11 53 + + + + + + + Zurich + + + Zürich + + + Zurich + + + Zurigo + + + Zurich + + + + + 8093 + + + CH + + + atlasinfo@ethz.ch + + + + + + + https://www.atlasderschweiz.ch + + + text/html + + + + + 09h00 - 10h30 / 11h00 - 12h30 / 14h00 - 18h00 GMT+1 + + + + + + + + + + + + Smithsonian Institution's Global Volcanism Program + + + Smithsonian Institution's Global Volcanism Program + + + Smithsonian Institution's Global Volcanism Program + + + Smithsonian Institution's Global Volcanism Program + + + Smithsonian Institution's Global Volcanism Program + + + + + + + + + Washington D.C. + + + Washington D.C. + + + Washington D.C. + + + Washington D.C. + + + Washington D.C. + + + + + US + + + gvp@si.edu + + + + + + + https://volcano.si.edu/ + + + text/html + + + + + + + + + + + + + + National Oceanic and Atmospheric Administration + + + National Oceanic and Atmospheric Administration + + + National Oceanic and Atmospheric Administration + + + National Oceanic and Atmospheric Administration + + + National Oceanic and Atmospheric Administration + + + + + + + + + Washington D.C. + + + Washington D.C. + + + Washington D.C. + + + Washington D.C. + + + Washington D.C. + + + + + US + + + haz.info@noaa.gov + + + + + + + http://www.noaa.gov/ + + + text/html + + + + + + + + + + + + + + + + + + + + + Global Switzerland + + + Globale Schweiz + + + La Suisse à l’international + + + Svizzera internazionale + + + Global Switzerland + + + + + Nature and Environment + + + Natur und Umwelt + + + Nature et Environnement + + + Natura e Ambiente + + + Nature and Environment + + + + + Geology and raw materials + + + Geologie und Rohstoffe + + + Géologie et matières premières + + + Geologia e materie prime + + + Geology and raw materials + + + + + + + + + + Atlas of Switzerland - Categories + + + Atlas der Schweiz - Kategorien + + + Atlas de la Suisse - Catégories + + + Atlante della Svizzera - Categorie + + + Atlas of Switzerland - Categories + + + + + + + 2016-04-01 + + + + + + + + + + + + + + Geology + + + Geologie + + + Géologie + + + Geologia + + + Geology + + + + + + + + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + GEMET - INSPIRE themes, version 1.0 + + + + + + + 2008-06-01 + + + + + + + + + + + + + + See end-user license agreement + + + Siehe Endbenutzer-Lizenzvertrag + + + Voir conditions générales d'utilisation + + + Vedi accordo di licenza con l'utente finale + + + See end-user license agreement + + + + + + + + + + + + There are no limitations on public access to spatial data sets and services. + + + + + + + + + + The conditions applying to access and use are unknown. + + + + + + + + + f6243e5a-3816-49ab-9eeb-14f0c16d8847 + + + + + + + + + + + + + + + + + 1000000 + + + + + + + + + + + + + + + + + + + + + + geoscientificInformation + + + geoscientificInformation_Geology + + + + + World + + + Welt + + + Monde + + + Mondo + + + World + + + + + + + -179.97 + + + 179.58 + + + -78.5 + + + 88.27 + + + + + + + + -10000-01-01T00:00:00 + 2021-12-31T23:59:59 + + + + + + + + + + + + + + Digital map in a web atlas + + + Digitale Karte in einem Web-Atlas + + + Carte digitale dans un atlas web + + + Mappa digitale in un web atlante + + + Digital map in a web atlas + + + + + - + + + + + + + + + https://www.atlasofswitzerland.ch/volcanic-eruptions-in-the-holocene/ + + + WWW:LINK-1.0-http--link + + + + + + + + + + + + + + + + + + + + + + Conformity_001 + + + INSPIRE + + + + + + + + + COMMISSION REGULATION (EU) No 1089/2010 of 23 November 2010 + implementing Directive 2007/2/EC of the European Parliament and of the + Council as regards interoperability of spatial data sets and services + + + VERORDNUNG (EG) Nr. 1089/2010 + DER KOMMISSION vom 23. November 2010 zur Durchführung der Richtlinie + 2007/2/EG des Europäischen Parlaments und des Rates hinsichtlich der + Interoperabilität von Geodatensätzen und -diensten + + + RÈGLEMENT (UE) No 1089/2010 + DE LA COMMISSION du 23 novembre 2010 portant modalités d'application + de la directive 2007/2/CE du Parlement européen et du Conseil en ce + qui concerne l'interopérabilité des séries et des services de + données géographiques + + + REGOLAMENTO (UE) N. 1089/2010 + DELLA COMMISSIONE del 23 novembre 2010 recante attuazione della + direttiva 2007/2/CE del Parlamento europeo e del Consiglio per + quanto riguarda l'interoperabilità dei set di dati territoriali e + dei servizi di dati territoriali + + + COMMISSION REGULATION (EU) No + 1089/2010 of 23 November 2010 implementing Directive 2007/2/EC of + the European Parliament and of the Council as regards + interoperability of spatial data sets and services + + + + + + + 2010-12-08 + + + + + + + + + + See the referenced specification + + + Siehe referenzierte Spezifikation + + + Voir la spécification référencée + + + Vedi la specifica riferimento + + + See the referenced specification + + + + + true + + + + + + + + + Source data: Smithsonian Institution's Global Volcanism Program, National Oceanic and Atmospheric Administration. Data processing: Atlas of Switzerland + + + Ursprungsdaten: Smithsonian Institution's Global Volcanism Program, National Oceanic and Atmospheric Administration. + Datenverarbeitung: Atlas der Schweiz + + + Données originales: Smithsonian Institution's Global Volcanism Program, National Oceanic and Atmospheric Administration. + Traitement de données: Atlas de la Suisse + + + Dati di origine: Smithsonian Institution's Global Volcanism Program, National Oceanic and Atmospheric Administration. + Trattamento dei dati: Atlante della Svizzera + + + Source data: Smithsonian Institution's Global Volcanism Program, National Oceanic and Atmospheric Administration. Data + processing: Atlas of Switzerland + + + + + + + + \ No newline at end of file diff --git a/csw-server/pom.xml b/csw-server/pom.xml index 45fea9a4303..35a58db23fb 100644 --- a/csw-server/pom.xml +++ b/csw-server/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/csw-server/src/main/java/org/fao/geonet/kernel/csw/services/getrecords/SearchController.java b/csw-server/src/main/java/org/fao/geonet/kernel/csw/services/getrecords/SearchController.java index 99ac7c50b37..3002391bbf6 100644 --- a/csw-server/src/main/java/org/fao/geonet/kernel/csw/services/getrecords/SearchController.java +++ b/csw-server/src/main/java/org/fao/geonet/kernel/csw/services/getrecords/SearchController.java @@ -571,10 +571,10 @@ private String convertCswFilterToEsQuery(Element xml, String filterVersion) { * 1) gmd-csw-postprocessing.xsl : Postprocessing xsl applied for CSW service when requesting iso (gmd) output * 2) csw-csw-postprocessing.xsl : Postprocessing xsl applied for CSW service when requesting ogc (csw) output * - * For a custom CSW service named csw-inspire + * For a custom sub-portal named inspire * - * 1) gmd-csw-inspire-postprocessing.xsl : Postprocessing xsl applied for custom CSW csw-inspire service when requesting iso output - * 2) csw-csw-inspire-postprocessing.xsl : Postprocessing xsl applied for custom CSW csw-inspire service when requesting ogc (csw) output + * 1) gmd-inspire-postprocessing.xsl : Postprocessing xsl applied for custom inspire sub-portal when requesting iso output + * 2) csw-inspire-postprocessing.xsl : Postprocessing xsl applied for custom inspire sub-portal when requesting ogc (csw) output * * @param context Service context * @param schemaManager schemamanager diff --git a/docs/changes2.1.x.txt b/docs/changes2.1.x.txt index 84a0c41255c..803c1ec16de 100644 --- a/docs/changes2.1.x.txt +++ b/docs/changes2.1.x.txt @@ -769,7 +769,7 @@ === Changes ================================================================================ -- Search for templates is hidden to simple registerd users (only allow from editors up) +- Search for templates is hidden to simple registered users (only allow from editors up) - Moved login info to the righ diff --git a/docs/changes2.2.x.txt b/docs/changes2.2.x.txt index c37ec5f926b..a8b2cd9c720 100644 --- a/docs/changes2.2.x.txt +++ b/docs/changes2.2.x.txt @@ -1098,7 +1098,7 @@ InterMap: === Changes ================================================================================ -- Search for templates is hidden to simple registerd users (only allow from +- Search for templates is hidden to simple registered users (only allow from editors up) - Moved login info to the righ diff --git a/docs/changes3.8.0-0.txt b/docs/changes3.8.0-0.txt index 832c2d67898..f367694b79f 100644 --- a/docs/changes3.8.0-0.txt +++ b/docs/changes3.8.0-0.txt @@ -292,7 +292,7 @@ - Support gmx:Anchor in extract relations xslt process - refactor - Support gmx:Anchor in extract relations xslt process - fix pdf generation from full view, flying saucer trouble with float: left -- in manage record priviledges help groups sorter to sort +- in manage record privileges help groups sorter to sort - Metadata editor - associated panel doesn't remove error red highlight when filling the url in thumbnail mode #3756 - Fix JS build. - Fixes empty owner select in admin's batch process @@ -439,4 +439,4 @@ - fixes #3207, add warning when layer not in capabilities (for wmts and wfs), with option to open service panel to select alternative layer (adding type to warning translation, so it can be used for multiple types). Also fixes bug with url having &. - Updated slf4j dependencies - Updated avalon-framework dependencies -- Updated json-path dependencies \ No newline at end of file +- Updated json-path dependencies diff --git a/docs/changes4.2.5-0.txt b/docs/changes4.2.5-0.txt new file mode 100644 index 00000000000..5c0e1a2dd77 --- /dev/null +++ b/docs/changes4.2.5-0.txt @@ -0,0 +1,112 @@ +================================================================================ +=== +=== GeoNetwork 4.2.5: List of changes +=== +================================================================================ +- Thesaurus / Region / Use ISO format for date (#7208) +- Transifex updates. +- Static pages / Fix links to load html pages content (#7203) +- Sort custom translations by translation key to display them in the UI grouped by translation +- API / Import record / Overwrite mode improvement. (#7178) +- Additional code refactor related to pull request 7124 to avoid code duplication (#7200) +- Editor / Thesaurus widget initialization improvement. +- Thesaurus / Multilingual title / Set title according to record language. +- Merge pull request #7124 from AstunTechnology/jeevesnodeawarelogout-port-fix +- Bulk publish / Publish privileges should be assigned to the approved version, not to the working copy version. +- [FP] Metadata workflow - publish an approved record with a draft copy, publishes the draft copy. Fixes #5556 (#5557) +- Fix load of static pages html content +- Remote INSPIRE ATOM Feed / code cleanup and optimisations for ATOM describe service (#7189) +- Exclude ATOM opensearch URLs from the portal filter checker. Related to #6970 (#7196) +- Add a Dutch translation for the datepicker (#7193) +- Merge pull request #7181 from pi-geosolutions/external-viewer-add-wfs-to-map +- Pages / Top toolbar / Add support for submenu (#7138) +- XslUtil / Add a function to get current node id (srv or subportal id) +- [WFS / external viewer] Remove trailing semi-colon +- [WFS / External viewer] Support localized title but fallback on link name +- Change line-height for username so that it is not truncated. (#7185) +- Update core/src/main/java/jeeves/config/springutil/JeevesNodeAwareLogoutSuccessHandler.java +- Update core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java +- Update core/src/main/java/org/fao/geonet/kernel/setting/SettingInfo.java +- Created new instances of SettingInfo.getSitePort in JeevesNodeAwareLogoutSuccessHandler and BaseMetadataManager to get around compilation error about not referring to a non-static method from a static context +- External map viewer / Fix add WFS to map empty variable +- Site information - return ES index information +- Updated Java 8 download link (#7177) +- Standard / Translations / Load contact role codelist from the schema (#7153) +- Configuration / Externalize Kibana configuration (#7098) +- Test / Fix for default language. (#7175) +- Record view / Keyword popup / Improve label. (#7152) +- Fixed broken link for Maven Reference +- CSW / DCAT output / Fix for multilingual records (#7161) +- Record view / Citation / Improve layout +- Remove Language table isdefault column and use DefaultLanguage bean instead +- Fixing SONAR warnings about SQL Injections. (#7146) +- Search / Full text / Add individual name and email. (#7167) +- Standard / ISO19115-3 / Preserve revision date type. (#7168) +- Standard / ISO19115-3 / Editor / Do not add default lang id (#7174) +- Standard / ISO19115-3 / Better label for metadata language. (#7173) +- Standard / ISO19115-3 / From 19139 conversion / Fix MD_VectorSpatialRepresentation namespace (#7172) +- Editor / Template field label fix (#7171) +- Standard / ISO19115-3 / Editor / Expand abstract for multilingual records (#7170) +- Move tooltip from reccusive input field to a single label (#7039) +- Minor formatting changes. (#7164) +- Do not make Danish language a default language. +- Support code elements with anchors in ATOM predefined feeds +- Support customising the metadata publication options (#7148) +- Update TESTING.md to include a hint to the it maven profile (#7149) +- missed file in pull/6494 (#7150) +- Map / Background layer / Fix active layer on load. (#7126) +- Map / Layer manager / Add support for multilingual layer title. (#7121) +- UI / Language picker / Display all entries on focus. Search by code and labels (#7076) +- Standard / ISO19115-3 / Formatter / JSONLD / Avoid error on multilingual URL +- Fixed typos in es README (#7151) +- FeatureInfo - lower case columns names - related to #6801 +- Fixed another broken link for Installing from source code (#7141) +- Send metadata mail notifications for public metadata when is re-approved (#7074) +- Update transifex translations +- Update metadata status values translations +- Workflow / Facets / Generic translations (#7135) +- Remove sort by translations, duplicated in en-core.json file +- Related resources - fix the check to display association information +- Indexing / Update objects definition to index a sort field that is case insensitive and ignore accents +- ISO19139 / Metadata editor / Fix display of contact individual name label in German (#7122) +- Workflow / Remove magic numbers in label and avoid issue when using custom status (#7104) +- Fixed broken link for Installing from source code +- Add Spring Security mappings for local INSPIRE ATOM feed services +- Allow multiple request matchers to decide if require CSRF (#7123) +- Changed logic to check actual value of port setting +- Using correct setting for default port, and better handling of null values +- Include the parent exception in the MetadataProcessingReport error so that we can see the real error in the logs. (#7119) +- Fix check of harvester log file path +- ISO19139 / Fix indexing of CRS codespace when contains a multilingual value +- Added some logic into JeevesNodeAwareLogoutSuccessHandler to deal with the case where no port is set in the settings manager +- Fix selection / filter Only my records (#7111) +- Return contact information in the search results, used in the results templates +- Index configuration / define dynamic template for conformTo_* fields pattern +- Update sortby-combo directive to use the field name and the sort order information. +- Metadata editor / include the metadata file store in the default configuration of the online resources. Fixes #7103 +- Copying the resources/config folder to the data dir +- Workflow / On Cancel / Properly remove draft from index (#7101) +- Remove code smells +- Fix javascript error when creating / editing online resources +- Fix microservices proxy detection +- Metadata workflow / send mail notifications for metadata status change to DRAFT when doing a rejection, not when creating a working copy +- Workflow improvements (#7011) +- DB / Only update schema on startup (#7100) +- Display footer rss endpoint(s) when OGC API Records service is enabled (#7094) +- SEO / Set HTML head title and description +- Build / Fix missing font awesome files in WAR +- Map / Define location of search map. (#7071) +- fixed links to debugging tomcat in eclipse and remote debugging (#7075) +- Add criteria-type.xml for iso19115-3.2018 +- Fix for broken icons in the editor, admin pages, file uploader and multiselect (#7093) +- Config / Add property file overlay. (#6954) +- Test / Fixes (#7091) +- Workflow / Fix typo in setting key. (#7070) +- UI / Dependency / CatController depends on facets also on signin. (#7084) +- i18n / NL / INSPIRE Themes / Fix language code +- Home page / Aggregations / Fix label for facet based on multilingual thesaurus. +- Standard / ISO19139 / Schematron / NL translation (#7063) +- DOI / Add European Union Publication Office format for DOI. (#6979) +- Update to Font Awesome 6.x (6.4) (#7007) +- Fix multiple default tab in a single view +- Update version to 4.2.5-SNAPSHOT \ No newline at end of file diff --git a/docs/manuals b/docs/manuals index eca2e28ce01..0460f61fb82 160000 --- a/docs/manuals +++ b/docs/manuals @@ -1 +1 @@ -Subproject commit eca2e28ce016e1507dff44533908df6e72086b4f +Subproject commit 0460f61fb824fab643da56705c0e4b47f4df7598 diff --git a/docs/pom.xml b/docs/pom.xml index e747888a31c..c12bcf0aa6a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 gn-docs diff --git a/doi/pom.xml b/doi/pom.xml index 298d7a135fb..26b76629e07 100644 --- a/doi/pom.xml +++ b/doi/pom.xml @@ -28,7 +28,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/doi/src/main/java/org/fao/geonet/doi/client/BaseDoiClient.java b/doi/src/main/java/org/fao/geonet/doi/client/BaseDoiClient.java index ded2ce7acbc..46cb4ab7b99 100644 --- a/doi/src/main/java/org/fao/geonet/doi/client/BaseDoiClient.java +++ b/doi/src/main/java/org/fao/geonet/doi/client/BaseDoiClient.java @@ -22,6 +22,7 @@ //============================================================================== package org.fao.geonet.doi.client; +import com.google.common.base.Function; import com.google.common.io.CharStreams; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.io.IOUtils; @@ -31,11 +32,18 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHeader; +import org.fao.geonet.ApplicationContextHolder; +import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.lib.Lib; import org.fao.geonet.utils.GeonetHttpRequestFactory; import org.fao.geonet.utils.Log; import org.springframework.http.client.ClientHttpResponse; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.InputStreamReader; import static org.fao.geonet.doi.client.DoiSettings.LOGGER_NAME; @@ -62,6 +70,7 @@ protected void create(String url, String body, String contentType, postMethod = new HttpPost(url); + ((HttpUriRequest) postMethod).addHeader( new BasicHeader("Content-Type", contentType + ";charset=UTF-8") ); Log.debug(LOGGER_NAME, " -- Request body: " + body); @@ -72,9 +81,8 @@ protected void create(String url, String body, String contentType, postMethod.setEntity(requestEntity); - httpResponse = requestFactory.execute( - postMethod, - new UsernamePasswordCredentials(username, password), AuthScope.ANY); + httpResponse = executeRequest(postMethod); + int status = httpResponse.getRawStatusCode(); Log.debug(LOGGER_NAME, " -- Request status code: " + status); @@ -92,7 +100,7 @@ protected void create(String url, String body, String contentType, successMessage, url)); } } catch (Exception ex) { - Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage()); + Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage(), ex); throw new DoiClientException(ex.getMessage()); } finally { @@ -116,9 +124,8 @@ protected String retrieve(String url) getMethod = new HttpGet(url); + httpResponse = executeRequest(getMethod); - httpResponse = requestFactory.execute(getMethod, - new UsernamePasswordCredentials(username, password), AuthScope.ANY); int status = httpResponse.getRawStatusCode(); Log.debug(LOGGER_NAME, " -- Request status code: " + status); @@ -137,7 +144,7 @@ protected String retrieve(String url) } } catch (Exception ex) { - Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage()); + Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage(), ex); throw new DoiClientException(ex.getMessage()); } finally { @@ -148,4 +155,26 @@ protected String retrieve(String url) IOUtils.closeQuietly(httpResponse); } } + + + protected ClientHttpResponse executeRequest(HttpUriRequest method) throws Exception { + final String requestHost = method.getURI().getHost(); + + final Function requestConfiguration = new Function() { + @Nullable + @Override + public Void apply(@Nonnull HttpClientBuilder input) { + final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + input.setDefaultCredentialsProvider(credentialsProvider); + + Lib.net.setupProxy(ApplicationContextHolder.get().getBean(SettingManager.class), input, requestHost); + input.useSystemProperties(); + + return null; + } + }; + + return requestFactory.execute(method, requestConfiguration); + } } diff --git a/doi/src/main/java/org/fao/geonet/doi/client/DoiDataciteClient.java b/doi/src/main/java/org/fao/geonet/doi/client/DoiDataciteClient.java index 768576468f1..589d7f137a9 100644 --- a/doi/src/main/java/org/fao/geonet/doi/client/DoiDataciteClient.java +++ b/doi/src/main/java/org/fao/geonet/doi/client/DoiDataciteClient.java @@ -1,5 +1,5 @@ //============================================================================= -//=== Copyright (C) 2001-2010 Food and Agriculture Organization of the +//=== Copyright (C) 2001-2023 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== @@ -169,9 +169,8 @@ public void deleteDoiMetadata(String doi) deleteMethod = new HttpDelete(createUrl("metadata/" + doi)); - httpResponse = requestFactory.execute( - deleteMethod, - new UsernamePasswordCredentials(username, password), AuthScope.ANY); + httpResponse = executeRequest(deleteMethod); + int status = httpResponse.getRawStatusCode(); Log.debug(LOGGER_NAME, " -- Request status code: " + status); @@ -186,7 +185,7 @@ public void deleteDoiMetadata(String doi) } } catch (Exception ex) { - Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage()); + Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage(), ex); throw new DoiClientException(ex.getMessage()); } finally { @@ -210,9 +209,8 @@ public void deleteDoi(String doi) deleteMethod = new HttpDelete(createUrl("doi/" + doi)); - httpResponse = requestFactory.execute( - deleteMethod, - new UsernamePasswordCredentials(username, password), AuthScope.ANY); + httpResponse = executeRequest(deleteMethod); + int status = httpResponse.getRawStatusCode(); Log.debug(LOGGER_NAME, " -- Request status code: " + status); @@ -227,7 +225,7 @@ public void deleteDoi(String doi) } } catch (Exception ex) { - Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage()); + Log.error(LOGGER_NAME, " -- Error (exception): " + ex.getMessage(), ex); throw new DoiClientException(ex.getMessage()); } finally { diff --git a/doi/src/main/java/org/fao/geonet/doi/client/DoiSettings.java b/doi/src/main/java/org/fao/geonet/doi/client/DoiSettings.java index 285ce55d4d0..1ebe6fe357b 100644 --- a/doi/src/main/java/org/fao/geonet/doi/client/DoiSettings.java +++ b/doi/src/main/java/org/fao/geonet/doi/client/DoiSettings.java @@ -1,5 +1,5 @@ //============================================================================= -//=== Copyright (C) 2001-2010 Food and Agriculture Organization of the +//=== Copyright (C) 2001-2023 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== @@ -41,5 +41,5 @@ public class DoiSettings { public static final String SETTING_PUBLICATION_DOI_LANDING_PAGE_TEMPLATE = "system/publication/doi/doilandingpagetemplate"; - protected static final String LOGGER_NAME = "DOI"; + protected static final String LOGGER_NAME = "geonetwork.doi"; } diff --git a/domain/pom.xml b/domain/pom.xml index ea18de4dbae..621d05563e7 100644 --- a/domain/pom.xml +++ b/domain/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/domain/src/main/java/org/fao/geonet/domain/ISODate.java b/domain/src/main/java/org/fao/geonet/domain/ISODate.java index 33c83aa016e..c2166fd2540 100644 --- a/domain/src/main/java/org/fao/geonet/domain/ISODate.java +++ b/domain/src/main/java/org/fao/geonet/domain/ISODate.java @@ -247,6 +247,7 @@ public void setDateAndTime(String isoDate) { * @return The date and time in ISO format. */ @JsonProperty("dateAndTimeUtc") + @XmlTransient public String getDateAndTimeUtc() { return internalDateTime.withZoneSameInstant(ZoneOffset.UTC).format(DateUtil.ISO_OFFSET_DATE_TIME_NANOSECONDS); } diff --git a/domain/src/main/java/org/fao/geonet/domain/InspireAtomFeed.java b/domain/src/main/java/org/fao/geonet/domain/InspireAtomFeed.java index fcaf2231067..19dec352860 100644 --- a/domain/src/main/java/org/fao/geonet/domain/InspireAtomFeed.java +++ b/domain/src/main/java/org/fao/geonet/domain/InspireAtomFeed.java @@ -1,25 +1,25 @@ -//============================================================================= -//=== Copyright (C) 2001-2010 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 -//============================================================================== +/* + * Copyright (C) 2001-2023 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.domain; @@ -29,10 +29,10 @@ import org.jdom.Namespace; import javax.persistence.*; - import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * INSPIRE Atom feed model class. @@ -42,7 +42,9 @@ @Entity @Access(AccessType.PROPERTY) @Table(name = "InspireAtomFeed", - indexes = { @Index(name = "idx_inspireatomfeed_metadataid", columnList = "metadataid") }) + indexes = { @Index(name = "idx_inspireatomfeed_metadataid", columnList = "metadataid"), + @Index(name = "idx_inspireatomfeed_atomDatasetid", columnList = "atomDatasetid"), + @Index(name = "idx_inspireatomfeed_atomDatasetns", columnList = "atomDatasetns")}) @SequenceGenerator(name = InspireAtomFeed.ID_SEQ_NAME, initialValue = 100, allocationSize = 1) public class InspireAtomFeed extends GeonetEntity implements Serializable { static final String ID_SEQ_NAME = "inspire_atom_feed_id_seq"; @@ -65,7 +67,7 @@ public class InspireAtomFeed extends GeonetEntity implements Serializable { public InspireAtomFeed() { - _entryList = new ArrayList(); + _entryList = new ArrayList<>(); } public static InspireAtomFeed build(Element atomDoc) { @@ -263,49 +265,30 @@ public void addEntry(InspireAtomFeedEntry entry) { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof InspireAtomFeed)) return false; + if (o == null || getClass() != o.getClass()) return false; InspireAtomFeed that = (InspireAtomFeed) o; if (_id != that._id) return false; if (_metadataId != that._metadataId) return false; - if (_atom != null ? !_atom.equals(that._atom) : that._atom != null) return false; - if (_atomDatasetid != null ? !_atomDatasetid.equals(that._atomDatasetid) : that._atomDatasetid != null) + if (!Objects.equals(_title, that._title)) return false; + if (!Objects.equals(_atom, that._atom)) return false; + if (!Objects.equals(_atomUrl, that._atomUrl)) return false; + if (!Objects.equals(_atomDatasetid, that._atomDatasetid)) return false; - if (_atomDatasetns != null ? !_atomDatasetns.equals(that._atomDatasetns) : that._atomDatasetns != null) + if (!Objects.equals(_atomDatasetns, that._atomDatasetns)) return false; - if (_atomUrl != null ? !_atomUrl.equals(that._atomUrl) : that._atomUrl != null) - return false; - if (_authorEmail != null ? !_authorEmail.equals(that._authorEmail) : that._authorEmail != null) - return false; - if (_authorName != null ? !_authorName.equals(that._authorName) : that._authorName != null) - return false; - if (_lang != null ? !_lang.equals(that._lang) : that._lang != null) return false; - if (_rights != null ? !_rights.equals(that._rights) : that._rights != null) return false; - if (_subtitle != null ? !_subtitle.equals(that._subtitle) : that._subtitle != null) - return false; - if (_title != null ? !_title.equals(that._title) : that._title != null) return false; - if (_entryList != null ? !_entryList.equals(that._entryList) : that._entryList != null) - return false; - - return true; + if (!Objects.equals(_subtitle, that._subtitle)) return false; + if (!Objects.equals(_rights, that._rights)) return false; + if (!Objects.equals(_lang, that._lang)) return false; + if (!Objects.equals(_authorName, that._authorName)) return false; + if (!Objects.equals(_authorEmail, that._authorEmail)) return false; + return Objects.equals(_entryList, that._entryList); } @Override public int hashCode() { - int result = _id; - result = 31 * result + _metadataId; - result = 31 * result + (_title != null ? _title.hashCode() : 0); - result = 31 * result + (_atom != null ? _atom.hashCode() : 0); - result = 31 * result + (_atomUrl != null ? _atomUrl.hashCode() : 0); - result = 31 * result + (_atomDatasetid != null ? _atomDatasetid.hashCode() : 0); - result = 31 * result + (_atomDatasetns != null ? _atomDatasetns.hashCode() : 0); - result = 31 * result + (_subtitle != null ? _subtitle.hashCode() : 0); - result = 31 * result + (_rights != null ? _rights.hashCode() : 0); - result = 31 * result + (_lang != null ? _lang.hashCode() : 0); - result = 31 * result + (_authorName != null ? _authorName.hashCode() : 0); - result = 31 * result + (_authorEmail != null ? _authorEmail.hashCode() : 0); - result = 31 * result + (_entryList != null ? _entryList.hashCode() : 0); - return result; + return Objects.hash(_id, _metadataId, _title, _atom, _atomUrl, _atomDatasetid, _atomDatasetns, _subtitle, + _rights, _lang, _authorName, _authorEmail, _entryList); } } diff --git a/domain/src/main/java/org/fao/geonet/domain/UserGroup.java b/domain/src/main/java/org/fao/geonet/domain/UserGroup.java index b3cff359332..a7cb835ea98 100644 --- a/domain/src/main/java/org/fao/geonet/domain/UserGroup.java +++ b/domain/src/main/java/org/fao/geonet/domain/UserGroup.java @@ -32,6 +32,7 @@ import java.io.Serializable; import java.util.IdentityHashMap; +import java.util.Objects; /** * The mapping between user, the groups a user is a part of and the profiles the user has for each @@ -145,4 +146,19 @@ protected Element asXml(IdentityHashMap alreadyEncoded) { .addContent(new Element("user").setText("" + getId().getUserId())) .addContent(new Element("profile").setText(getProfile().name())); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserGroup ug = (UserGroup) o; + return (getId().getGroupId() == (ug.getId().getGroupId()) && + getId().getUserId() == (ug.getId().getUserId()) && + getProfile().name().equals(ug.getProfile().name())); + } + + @Override + public int hashCode() { + return Objects.hash(getProfile().name(), getId().getUserId(), getId().getGroupId()); + } } diff --git a/domain/src/main/java/org/fao/geonet/repository/InspireAtomFeedRepository.java b/domain/src/main/java/org/fao/geonet/repository/InspireAtomFeedRepository.java index 73b55fc120e..af6e17f1364 100644 --- a/domain/src/main/java/org/fao/geonet/repository/InspireAtomFeedRepository.java +++ b/domain/src/main/java/org/fao/geonet/repository/InspireAtomFeedRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2023 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -26,6 +26,8 @@ import org.fao.geonet.domain.InspireAtomFeed; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.List; + /** * Repository class for InspireAtomFeed. Repository class for InspireAtomFeed. @@ -41,4 +43,12 @@ public interface InspireAtomFeedRepository extends GeonetRepository findAllByAtomDatasetid(final String atomDatasetid); } diff --git a/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustom.java b/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustom.java index 3157989fad8..bfe7e9ca02f 100644 --- a/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustom.java +++ b/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustom.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.List; +import java.util.Set; /** * Custom methods for loading {@link UserGroup} entities. @@ -61,4 +62,13 @@ public interface UserGroupRepositoryCustom { * @return the number of entities deleted */ int deleteAllByIdAttribute(SingularAttribute idAttribute, Collection ids); + + /** + * Update user with the new list of {@link UserGroup}. + * If the user already has all the groups specified then no change will be made. + * + * @param userId user id to have the groups updated + * @param newUserGroups the {@link UserGroup} to set + */ + void updateUserGroups(int userId, Set newUserGroups); } diff --git a/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustomImpl.java b/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustomImpl.java index 53c062397f2..226dddc940a 100644 --- a/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustomImpl.java +++ b/domain/src/main/java/org/fao/geonet/repository/UserGroupRepositoryCustomImpl.java @@ -26,10 +26,12 @@ */ package org.fao.geonet.repository; +import org.fao.geonet.ApplicationContextHolder; import org.fao.geonet.domain.UserGroup; import org.fao.geonet.domain.UserGroupId; import org.fao.geonet.domain.UserGroupId_; import org.fao.geonet.domain.UserGroup_; +import org.fao.geonet.repository.specification.UserGroupSpecs; import org.springframework.data.jpa.domain.Specification; import org.springframework.transaction.annotation.Transactional; @@ -42,7 +44,9 @@ import javax.persistence.metamodel.SingularAttribute; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Implementation object for methods in {@link UserGroupRepositoryCustom}. @@ -91,4 +95,19 @@ private List findIdsBy(Specification spec, SingularAttribute return _entityManager.createQuery(query).getResultList(); } + public void updateUserGroups(int userId, Set newUserGroups) { + UserGroupRepository userGroupRepository = ApplicationContextHolder.get().getBean(UserGroupRepository.class); + + List currentUserGroupLists = userGroupRepository.findAll(UserGroupSpecs.hasUserId(userId)); + Set currentUserGroups = new HashSet<>(currentUserGroupLists); + + // If the new user groups are not the same as what is in the database then update database so that they are the same. + if (!newUserGroups.equals(currentUserGroups)) { + userGroupRepository.deleteAll(UserGroupSpecs.hasUserId(userId)); + for (UserGroup ug : newUserGroups) { + userGroupRepository.save(ug); + } + } + } + } diff --git a/domain/src/test/java/org/fao/geonet/repository/UserGroupRepositoryTest.java b/domain/src/test/java/org/fao/geonet/repository/UserGroupRepositoryTest.java index d3e4eb4ce61..e76b5be47cd 100644 --- a/domain/src/test/java/org/fao/geonet/repository/UserGroupRepositoryTest.java +++ b/domain/src/test/java/org/fao/geonet/repository/UserGroupRepositoryTest.java @@ -33,16 +33,16 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.domain.Specification; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.springframework.data.jpa.domain.Specification.where; @@ -201,6 +201,28 @@ public void testDeleteAllWithUserIdsIn() { assertFalse(_repo.findById(ug1.getId()).isPresent()); } + @Test + public void testUpdateUserGroups() { + UserGroup ug1 = _repo.save(newUserGroup()); + User user = ug1.getUser(); + + UserGroup ug2 = newUserGroup(); + ug2.setUser(user); + + UserGroup ug3 = newUserGroup(); + ug3.setUser(user); + + Set userGroups = new HashSet<>(); + userGroups.add(ug2); + userGroups.add(ug3); + + _repo.updateUserGroups(ug1.getUser().getId(), userGroups); + + List userGroups1 = _repo.findAll(UserGroupSpecs.hasUserId(user.getId())); + + assertEquals(new HashSet<>(userGroups1), userGroups); + } + private UserGroup newUserGroup() { return getUserGroup(_inc, _userRepo, _groupRepo); } diff --git a/es/es-dashboards/pom.xml b/es/es-dashboards/pom.xml index 5775b8da422..765645b9ece 100644 --- a/es/es-dashboards/pom.xml +++ b/es/es-dashboards/pom.xml @@ -28,7 +28,7 @@ gn-es org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT diff --git a/es/pom.xml b/es/pom.xml index 6e3b7083024..57f045d9a81 100644 --- a/es/pom.xml +++ b/es/pom.xml @@ -5,7 +5,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 gn-es diff --git a/estest/pom.xml b/estest/pom.xml index d21ef19f15d..bdc38dbf185 100644 --- a/estest/pom.xml +++ b/estest/pom.xml @@ -5,7 +5,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/events/pom.xml b/events/pom.xml index 1cc94d20ab6..c8ed844a20c 100644 --- a/events/pom.xml +++ b/events/pom.xml @@ -28,7 +28,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT GeoNetwork Events diff --git a/harvesters/pom.xml b/harvesters/pom.xml index 6f46086e99f..cc970cc6b28 100644 --- a/harvesters/pom.xml +++ b/harvesters/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/arcsde/ArcSDEHarvester.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/arcsde/ArcSDEHarvester.java index 77032bc9985..8b6e7881742 100644 --- a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/arcsde/ArcSDEHarvester.java +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/arcsde/ArcSDEHarvester.java @@ -192,20 +192,23 @@ private void align(Map metadataList) throws Exception { // Check if the ESRI metadata has an embedded iso19139 metadata boolean hasIso19139Embedded = false; - if (iso19139Element != null) { - try { - schema = dataMan.autodetectSchema(iso19139Element, null); - } catch (NoSchemaMatchesException ex) { - // Ignore - } + if (schema == null) { - hasIso19139Embedded = (schema != null); - } + if (iso19139Element != null) { + try { + schema = dataMan.autodetectSchema(iso19139Element, null); + } catch (NoSchemaMatchesException ex) { + // Ignore + } + + hasIso19139Embedded = (schema != null); - log.info("Metadata has ISO13139 embedded - " + hasIso19139Embedded); + log.info("Metadata has ISO13139 embedded - " + hasIso19139Embedded); + } + } - // No schema detected or not iso19139 embedded, try to convert from default ESRI md to ISO1939 - if ((schema == null) || !hasIso19139Embedded) { + // No schema detected, try to convert from default ESRI md to ISO1939 + if (schema == null) { log.info("Convert metadata to ISO19139 - start"); // Extract picture if available diff --git a/healthmonitor/pom.xml b/healthmonitor/pom.xml index 0e3cf279943..5c6346e8363 100644 --- a/healthmonitor/pom.xml +++ b/healthmonitor/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/index/pom.xml b/index/pom.xml index 47a73af8dfc..a30e340d272 100644 --- a/index/pom.xml +++ b/index/pom.xml @@ -5,7 +5,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 gn-index diff --git a/index/src/main/java/org/fao/geonet/index/es/EsRestClient.java b/index/src/main/java/org/fao/geonet/index/es/EsRestClient.java index 5f561a15531..23001f417df 100644 --- a/index/src/main/java/org/fao/geonet/index/es/EsRestClient.java +++ b/index/src/main/java/org/fao/geonet/index/es/EsRestClient.java @@ -49,6 +49,7 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.client.*; +import org.elasticsearch.client.core.MainResponse; import org.elasticsearch.client.indices.AnalyzeRequest; import org.elasticsearch.client.indices.AnalyzeResponse; import org.elasticsearch.common.xcontent.XContentType; @@ -281,7 +282,7 @@ public SearchResponse query(String index, JsonNode jsonQuery, QueryBuilder postF final QueryBuilder query = QueryBuilders.wrapperQuery(String.valueOf(jsonQuery)); return query(index, query, postFilterBuilder, includedFields, scriptedFields, from, size, sort); } - + public SearchResponse query(String index, QueryBuilder queryBuilder, QueryBuilder postFilterBuilder, Set includedFields, Map scriptedFields, int from, int size, List> sort) throws Exception { @@ -502,4 +503,10 @@ public String getServerStatus() throws IOException { return response.getStatus().toString(); // return getClient().ping(RequestOptions.DEFAULT); } + + public String getServerVersion() throws IOException { + MainResponse.Version version = client.info(RequestOptions.DEFAULT).getVersion(); + + return version.getNumber(); + } } diff --git a/inspire-atom/pom.xml b/inspire-atom/pom.xml index a0d5f404f5b..6c4ead3d699 100644 --- a/inspire-atom/pom.xml +++ b/inspire-atom/pom.xml @@ -28,7 +28,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/inspire-atom/src/main/java/org/fao/geonet/inspireatom/harvester/InspireAtomHarvester.java b/inspire-atom/src/main/java/org/fao/geonet/inspireatom/harvester/InspireAtomHarvester.java index fae1e933e9a..6379dbf6e84 100644 --- a/inspire-atom/src/main/java/org/fao/geonet/inspireatom/harvester/InspireAtomHarvester.java +++ b/inspire-atom/src/main/java/org/fao/geonet/inspireatom/harvester/InspireAtomHarvester.java @@ -298,7 +298,7 @@ private void processDatasetsMetadataFeeds(final ServiceContext context, try { // Find the metadata UUID using the resource identifier gmd:MD_Identifier/gmd:code - metadataUuid = InspireAtomUtil.retrieveDatasetUuidFromIdentifier(ServiceContext.get(), + metadataUuid = InspireAtomUtil.retrieveDatasetUuidFromIdentifier( gc.getBean(EsSearchManager.class), datasetFeedInfo.identifier); String atomUrl = datasetFeedInfo.feedUrl; @@ -376,7 +376,7 @@ private void processDatasetsMetadataFeedsForService(final ServiceContext context String metadataUuid = ""; try { - metadataUuid = InspireAtomUtil.retrieveDatasetUuidFromIdentifier(context, + metadataUuid = InspireAtomUtil.retrieveDatasetUuidFromIdentifier( gc.getBean(EsSearchManager.class), atomDatasetId); String atomDatasetNs = datasetFeedInfo.namespace; diff --git a/inspire-atom/src/main/java/org/fao/geonet/inspireatom/util/InspireAtomUtil.java b/inspire-atom/src/main/java/org/fao/geonet/inspireatom/util/InspireAtomUtil.java index ad01cb304de..d879752e72e 100644 --- a/inspire-atom/src/main/java/org/fao/geonet/inspireatom/util/InspireAtomUtil.java +++ b/inspire-atom/src/main/java/org/fao/geonet/inspireatom/util/InspireAtomUtil.java @@ -1,5 +1,5 @@ //============================================================================= -//=== Copyright (C) 2001-2017 Food and Agriculture Organization of the +//=== Copyright (C) 2001-2023 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== @@ -24,7 +24,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import jeeves.server.ServiceConfig; import jeeves.server.context.ServiceContext; import org.apache.commons.lang.StringUtils; import org.apache.lucene.search.TotalHits; @@ -72,11 +71,6 @@ public class InspireAtomUtil { **/ private static final String EXTRACT_DATASETS = "extract-datasets.xsl"; - /** - * Xslt process to get if a metadata is a service or a dataset. - **/ - private static final String EXTRACT_MD_TYPE = "extract-type.xsl"; - /** * Xslt process to get the atom feed link from the metadata. **/ @@ -117,6 +111,10 @@ public class InspireAtomUtil { **/ public static final String LOCAL_DOWNLOAD_DATASET_URL_SUFFIX = "atom/download/dataset"; + private InspireAtomUtil() { + + } + /** * Issue an http request to retrieve the remote Atom feed document. * @@ -163,7 +161,7 @@ public static void filterDatasetFeedByCrs(final Element feed, final String crs) throws Exception { - List elementsToRemove = new ArrayList(); + List elementsToRemove = new ArrayList<>(); Iterator it = feed.getChildren().iterator(); @@ -191,7 +189,7 @@ public static void filterDatasetFeedByCrs(final Element feed, public static boolean isServiceMetadata(DataManager dm, String schema, Element md) throws Exception { java.nio.file.Path styleSheet = dm.getSchemaDir(schema).resolve("extract-type.xsl"); - Map paramsM = new HashMap(); + Map paramsM = new HashMap<>(); String mdType = Xml.transform(md, styleSheet, paramsM).getText().trim(); return "service".equalsIgnoreCase(mdType); @@ -236,7 +234,7 @@ public static List extractRelatedDatasetsInfoFromServiceFeed(fi java.nio.file.Path defaultStyleSheet = dataManager.getSchemaDir("iso19139").resolve(EXTRACT_DATASETS_FROM_SERVICE_XSLT); - Map params = new HashMap(); + Map params = new HashMap<>(); Element atomIndexFields = Xml.transform(serviceFeed, defaultStyleSheet, params); List datasetsInformation = new ArrayList<>(); @@ -288,7 +286,7 @@ private static Map processAtomFeedsInternal(DataManager dataMana List iso19139Metadata, String type, String atomProtocol) throws Exception { - Map metadataAtomFeeds = new HashMap(); + Map metadataAtomFeeds = new HashMap<>(); for (AbstractMetadata md : iso19139Metadata) { int id = md.getId(); @@ -387,8 +385,7 @@ public static List searchMetadataByTypeAndProtocol(ServiceCont } - public static String retrieveDatasetUuidFromIdentifier(ServiceContext context, - EsSearchManager searchMan, + public static String retrieveDatasetUuidFromIdentifier(EsSearchManager searchMan, String datasetIdCode) { String jsonQuery = "{" + " \"bool\": {" + @@ -516,34 +513,20 @@ private static Path getAtomFeedXSLStylesheet(final String schema, final DataMana .resolve(TRANSFORM_MD_TO_ATOM_FEED); } - public static Element getDatasetFeed(final ServiceContext context, final String spIdentifier, - final String spNamespace, final Map params, String requestedLanguage) throws Exception { + public static Element getMetadataFeedByResourceIdentifier(final ServiceContext context, final String spIdentifier, + final String spNamespace, final Map params, String requestedLanguage) throws Exception { - ServiceConfig config = new ServiceConfig(); EsSearchManager searchMan = context.getBean(EsSearchManager.class); // Search for the dataset identified by spIdentifier AbstractMetadata datasetMd = null; String jsonQuery = "{" + - " \"bool\": {" + - " \"must\": [" + - " {" + - " \"term\": {" + - " \"resourceType\": {" + - " \"value\": \"%s\"" + - " }" + - " }" + - " }, " + - " {" + - " \"term\": {" + - " \"resourceIdentifier.code\": {" + - " \"value\": \"%s\"" + - " }" + - " }" + - " }" + - " ]" + - " }" + + " \"term\": {" + + " \"resourceIdentifier.code\": {" + + " \"value\": \"%s\"" + + " }" + + " }" + "}"; ObjectMapper objectMapper = new ObjectMapper(); IMetadataUtils repo = context.getBean(IMetadataUtils.class); @@ -564,7 +547,7 @@ public static Element getDatasetFeed(final ServiceContext context, final String // searchResult = searcher.present(context, dsLuceneSearchParams.getRootElement(), config); // } // } - JsonNode esJsonQuery = objectMapper.readTree(String.format(jsonQuery, "dataset", spIdentifier)); + JsonNode esJsonQuery = objectMapper.readTree(String.format(jsonQuery, spIdentifier)); final SearchResponse result = searchMan.query( esJsonQuery, @@ -647,9 +630,9 @@ public static Element getDatasetFeed(final ServiceContext context, final String } public static Element prepareOpenSearchDescriptionEltBeforeTransform(final ServiceContext context, - final Map params, final String fileIdentifier, final String schema, - final Element serviceAtomFeed, final String defaultLanguage, - final DataManager dataManager) throws Exception { + final Map params, final String fileIdentifier, + final Element serviceAtomFeed, final String defaultLanguage + ) throws Exception { List keywords = retrieveKeywordsFromFileIdentifier(context, fileIdentifier); Namespace ns = serviceAtomFeed.getNamespace(); @@ -659,7 +642,7 @@ public static Element prepareOpenSearchDescriptionEltBeforeTransform(final Servi response.addContent(new Element("fileId").setText(fileIdentifier)); response.addContent(new Element("title").setText(serviceAtomFeed.getChildText("title", ns))); response.addContent(new Element("subtitle").setText(serviceAtomFeed.getChildText("subtitle", ns))); - List languages = new ArrayList(); + List languages = new ArrayList<>(); languages.add(XslUtil.twoCharLangCode(defaultLanguage)); Iterator linksChildren = (serviceAtomFeed.getChildren("link", ns)).iterator(); while (linksChildren.hasNext()) { @@ -686,7 +669,7 @@ public static Element prepareOpenSearchDescriptionEltBeforeTransform(final Servi response.addContent(datasetsEl); Namespace inspiredlsns = serviceAtomFeed.getNamespace("inspire_dls"); Iterator datasets = (serviceAtomFeed.getChildren("entry", ns)).iterator(); - List fileTypes = new ArrayList(); + List fileTypes = new ArrayList<>(); while (datasets.hasNext()) { Element dataset = datasets.next(); String datasetIdCode = dataset.getChildText("spatial_dataset_identifier_code", inspiredlsns); @@ -694,7 +677,7 @@ public static Element prepareOpenSearchDescriptionEltBeforeTransform(final Servi Element datasetAtomFeed = null; try { - datasetAtomFeed = InspireAtomUtil.getDatasetFeed(context, datasetIdCode, datasetIdNs, params, XslUtil.twoCharLangCode(defaultLanguage)); + datasetAtomFeed = InspireAtomUtil.getMetadataFeedByResourceIdentifier(context, datasetIdCode, datasetIdNs, params, XslUtil.twoCharLangCode(defaultLanguage)); } catch (Exception e) { Log.error(Geonet.ATOM, "No dataset metadata found with uuid:" + fileIdentifier); @@ -709,7 +692,7 @@ public static Element prepareOpenSearchDescriptionEltBeforeTransform(final Servi datasetEl.addContent(new Element("authorName").setText(authorName)); } } - Map downloadsCountByCrs = new HashMap(); + Map downloadsCountByCrs = new HashMap<>(); Iterator entries = (datasetAtomFeed.getChildren("entry", ns)).iterator(); while (entries.hasNext()) { Element entry = entries.next(); @@ -718,7 +701,7 @@ public static Element prepareOpenSearchDescriptionEltBeforeTransform(final Servi String term = category.getAttributeValue("term"); Integer count = downloadsCountByCrs.get(term); if (count == null) { - count = new Integer(0); + count = 0; } downloadsCountByCrs.put(term, count + 1); } @@ -821,15 +804,13 @@ private static Element buildDatasetInfo(final String identifier, final String na public static List retrieveKeywordsFromFileIdentifier(ServiceContext context, String uuid) { EsSearchManager searchManager = context.getBean(EsSearchManager.class); - List keywordsList = new ArrayList(); + List keywordsList = new ArrayList<>(); try { Map document = searchManager.getDocument(uuid); Object tags = document.get("tag"); if (tags instanceof List) { ArrayList> list = (ArrayList) tags; - list.forEach(tag -> { - keywordsList.add(tag.get("default")); - }); + list.forEach(tag -> keywordsList.add(tag.get("default"))); } } catch (Exception ex) { Log.error(Geonet.ATOM, ex.getMessage(), ex); diff --git a/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomPredefinedFeed.java b/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomPredefinedFeed.java index 33908bacbcd..f1e0f517eba 100644 --- a/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomPredefinedFeed.java +++ b/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomPredefinedFeed.java @@ -139,7 +139,7 @@ public HttpEntity localDatasetDescribe( if (StringUtils.isNotBlank(searchTerms)) { params.put("searchTerms", searchTerms.toLowerCase()); } - Element feed = InspireAtomUtil.getDatasetFeed(context, spIdentifier, spNamespace, params, language); + Element feed = InspireAtomUtil.getMetadataFeedByResourceIdentifier(context, spIdentifier, spNamespace, params, language); return writeOutResponse(Xml.getString(feed), "application", "atom+xml"); } @@ -247,7 +247,7 @@ public HttpEntity localDatasetDownload( if (StringUtils.isNotBlank(searchTerms)) { params.put("searchTerms", searchTerms.toLowerCase()); } - Element feed = InspireAtomUtil.getDatasetFeed(context, spIdentifier, spNamespace, params, language); + Element feed = InspireAtomUtil.getMetadataFeedByResourceIdentifier(context, spIdentifier, spNamespace, params, language); Map crsCounts = new HashMap();; Namespace ns = Namespace.getNamespace("http://www.w3.org/2005/Atom"); if (crs!=null) { @@ -368,7 +368,9 @@ private Element getOpenSearchDescription(ServiceContext context, final String uu String defaultLanguage = dm.extractDefaultLanguage(schema, md); Map params = getDefaultXSLParams(sm, context, XslUtil.twoCharLangCode(defaultLanguage)); - Element inputDoc = InspireAtomUtil.prepareOpenSearchDescriptionEltBeforeTransform(context, params, uuid, schema, InspireAtomUtil.convertDatasetMdToAtom("iso19139", InspireAtomUtil.prepareServiceFeedEltBeforeTransform(schema, md, dm), dm, params), defaultLanguage, dm); + Element inputDoc = InspireAtomUtil.prepareOpenSearchDescriptionEltBeforeTransform(context, params, uuid, + InspireAtomUtil.convertDatasetMdToAtom("iso19139", + InspireAtomUtil.prepareServiceFeedEltBeforeTransform(schema, md, dm), dm, params), defaultLanguage); return InspireAtomUtil.convertServiceMdToOpenSearchDescription(context, inputDoc, params); } diff --git a/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomServiceDescription.java b/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomServiceDescription.java index f901664e6e1..87a255411b2 100644 --- a/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomServiceDescription.java +++ b/inspire-atom/src/main/java/org/fao/geonet/services/inspireatom/AtomServiceDescription.java @@ -1,79 +1,65 @@ -//============================================================================= -//=== Copyright (C) 2001-2017 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 -//============================================================================== +/* + * Copyright (C) 2001-2023 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.services.inspireatom; import io.swagger.v3.oas.annotations.Parameter; 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 jeeves.interfaces.Service; -import jeeves.server.ServiceConfig; import jeeves.server.context.ServiceContext; - -import nonapi.io.github.classgraph.utils.Join; -import org.apache.commons.lang.NotImplementedException; -import org.fao.geonet.Util; +import org.apache.commons.lang.StringUtils; +import org.fao.geonet.GeonetContext; import org.fao.geonet.api.API; import org.fao.geonet.api.ApiParams; import org.fao.geonet.api.ApiUtils; import org.fao.geonet.api.exception.NotAllowedException; import org.fao.geonet.api.exception.ResourceNotFoundException; +import org.fao.geonet.constants.Geonet; import org.fao.geonet.domain.AbstractMetadata; -import org.fao.geonet.domain.ReservedOperation; +import org.fao.geonet.domain.InspireAtomFeed; +import org.fao.geonet.domain.InspireAtomFeedEntry; +import org.fao.geonet.exceptions.ResourceNotFoundEx; import org.fao.geonet.guiapi.search.XsltResponseWriter; import org.fao.geonet.inspireatom.InspireAtomService; +import org.fao.geonet.inspireatom.harvester.InspireAtomHarvester; import org.fao.geonet.inspireatom.model.DatasetFeedInfo; +import org.fao.geonet.inspireatom.util.InspireAtomUtil; +import org.fao.geonet.kernel.DataManager; import org.fao.geonet.kernel.setting.SettingManager; import org.fao.geonet.kernel.setting.Settings; import org.fao.geonet.repository.InspireAtomFeedRepository; import org.fao.geonet.utils.Log; -import org.apache.commons.lang.StringUtils; -import org.fao.geonet.GeonetContext; -import org.fao.geonet.constants.Geonet; -import org.fao.geonet.exceptions.MetadataNotFoundEx; -import org.fao.geonet.inspireatom.util.InspireAtomUtil; -import org.fao.geonet.inspireatom.harvester.InspireAtomHarvester; -import org.fao.geonet.domain.InspireAtomFeed; -import org.fao.geonet.domain.InspireAtomFeedEntry; -import org.fao.geonet.kernel.DataManager; -import org.fao.geonet.lib.Lib; import org.jdom.Element; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.fao.geonet.exceptions.ResourceNotFoundEx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import static org.fao.geonet.api.ApiParams.API_PARAM_RECORD_UUID; import static org.fao.geonet.inspireatom.util.InspireAtomUtil.retrieveKeywordsFromFileIdentifier; -import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; @RequestMapping(value = { @@ -127,9 +113,9 @@ public Element describe( throw new Exception("Inspire is disabled"); } - AbstractMetadata record; + AbstractMetadata metadataRecord; try { - record = ApiUtils.canViewRecord(metadataUuid, request); + metadataRecord = ApiUtils.canViewRecord(metadataUuid, request); } catch (ResourceNotFoundException e) { Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); throw e; @@ -138,9 +124,9 @@ record = ApiUtils.canViewRecord(metadataUuid, request); throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW); } - Element md = record.getXmlData(false); - String schema = record.getDataInfo().getSchemaId(); - String id = record.getId() + ""; + Element md = metadataRecord.getXmlData(false); + String schema = metadataRecord.getDataInfo().getSchemaId(); + String id = String.valueOf(metadataRecord.getId()); String atomProtocol = sm.getValue(Settings.SYSTEM_INSPIRE_ATOM_PROTOCOL); @@ -187,7 +173,7 @@ record = ApiUtils.canViewRecord(metadataUuid, request); // Dataset feeds referenced by service feed. List datasetsInformation = InspireAtomUtil.extractRelatedDatasetsInfoFromServiceFeed(inspireAtomFeed.getAtom(), dm); - // Get information from the the service atom feed. + // Get information from the service atom feed. String feedAuthorName = inspireAtomFeed.getAuthorName(); String feedTitle = inspireAtomFeed.getTitle(); String feedSubtitle = inspireAtomFeed.getSubtitle(); @@ -196,14 +182,14 @@ record = ApiUtils.canViewRecord(metadataUuid, request); List keywords = retrieveKeywordsFromFileIdentifier(context, metadataUuid); // Process datasets information - Element datasetsEl = processDatasetsInfo(datasetsInformation, metadataUuid, context); + Element datasetsEl = processDatasetsInfo(datasetsInformation, metadataUuid); Element response = new Element("response") .addContent(new Element("fileId").setText(metadataUuid)) .addContent(new Element("title").setText(feedTitle)) .addContent(new Element("subtitle").setText(feedSubtitle)) .addContent(new Element("lang").setText(feedLang)) - .addContent(new Element("keywords").setText(Join.join(", ", keywords))) + .addContent(new Element("keywords").setText(StringUtils.join(keywords, ", "))) .addContent(new Element("authorName").setText(feedAuthorName)) .addContent(new Element("url").setText(feedUrl)) .addContent(datasetsEl); @@ -220,28 +206,28 @@ record = ApiUtils.canViewRecord(metadataUuid, request); * * @param datasetsInformation List of dataset identifiers to process. * @param serviceIdentifier Service identifier. - * @param context Service context. * @return JDOM Element with the datasets information. * @throws Exception Exception. */ - private Element processDatasetsInfo(final List datasetsInformation, final String serviceIdentifier, - final ServiceContext context) + private Element processDatasetsInfo(final List datasetsInformation, final String serviceIdentifier) throws Exception { Element datasetsEl = new Element("datasets"); for (DatasetFeedInfo datasetFeedInfo : datasetsInformation) { - // Get the metadata uuid for the dataset - String datasetUuid = inspireAtomFeedRepository.retrieveDatasetUuidFromIdentifier(datasetFeedInfo.identifier); - - // If dataset metadata not found, ignore - if (StringUtils.isEmpty(datasetUuid)) { - Log.warning(Geonet.ATOM, "AtomServiceDescription for service metadata (" + serviceIdentifier + - "): metadata for dataset identifier " + datasetFeedInfo.identifier + " is not found, ignoring it."); + // Get the metadata id for the dataset + InspireAtomFeed inspireAtomFeed; + List inspireAtomFeedList = inspireAtomFeedRepository.findAllByAtomDatasetid(datasetFeedInfo.identifier); + if (!inspireAtomFeedList.isEmpty()) { + inspireAtomFeed = inspireAtomFeedList.get(0); + } else { + // If dataset metadata not found, ignore + Log.warning(Geonet.ATOM, String.format("AtomServiceDescription for service metadata (%s): metadata " + + "for dataset identifier %s was not found, ignoring it.", + serviceIdentifier, datasetFeedInfo.identifier)); continue; } - String id = dm.getMetadataId(datasetUuid); - InspireAtomFeed inspireAtomFeed = inspireAtomFeedRepository.findByMetadataId(Integer.parseInt(id)); + String datasetUuid = dm.getMetadataUuid(String.valueOf(inspireAtomFeed.getMetadataId())); String idNs = inspireAtomFeed.getAtomDatasetid(); String namespace = inspireAtomFeed.getAtomDatasetns(); @@ -266,10 +252,10 @@ private Element processDatasetsInfo(final List datasetsInformat // Get dataset download info // From INSPIRE spec: if a CRS has multiple downloads should be returned a link to feed document with the CRS downloads. - Map downloadsCountByCrs = new HashMap(); + Map downloadsCountByCrs = new HashMap<>(); for (InspireAtomFeedEntry entry : inspireAtomFeed.getEntryList()) { Integer count = downloadsCountByCrs.get(entry.getCrs()); - if (count == null) count = Integer.valueOf(0); + if (count == null) count = 0; downloadsCountByCrs.put(entry.getCrs(), count + 1); } diff --git a/jmeter/pom.xml b/jmeter/pom.xml index 8cf86c46460..26ef0211915 100644 --- a/jmeter/pom.xml +++ b/jmeter/pom.xml @@ -29,7 +29,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT diff --git a/listeners/pom.xml b/listeners/pom.xml index d619ac6c40d..6ddf3d2e232 100644 --- a/listeners/pom.xml +++ b/listeners/pom.xml @@ -28,7 +28,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT GeoNetwork Events diff --git a/listeners/src/main/java/org/fao/geonet/listener/metadata/draft/ApprovePublishedRecord.java b/listeners/src/main/java/org/fao/geonet/listener/metadata/draft/ApprovePublishedRecord.java index 441bf196029..823a76e2ea5 100644 --- a/listeners/src/main/java/org/fao/geonet/listener/metadata/draft/ApprovePublishedRecord.java +++ b/listeners/src/main/java/org/fao/geonet/listener/metadata/draft/ApprovePublishedRecord.java @@ -98,7 +98,10 @@ public void doAfterCommit(MetadataPublished event) { changeToApproved(event.getMd(), previousStatus); } - draftUtilities.replaceMetadataWithDraft(publishedMd); + // Don't replace the approved version with the draft copy, when publishing the approved version + if (!(event.getMd() instanceof Metadata)) { + draftUtilities.replaceMetadataWithDraft(publishedMd); + } } } catch (Exception e) { Log.error(Geonet.DATA_MANAGER, "Error upgrading workflow of " + event.getMd(), e); diff --git a/messaging/pom.xml b/messaging/pom.xml index c2db718542c..6d40fb77486 100644 --- a/messaging/pom.xml +++ b/messaging/pom.xml @@ -5,7 +5,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/oaipmh/pom.xml b/oaipmh/pom.xml index 78c9d838339..26398362730 100644 --- a/oaipmh/pom.xml +++ b/oaipmh/pom.xml @@ -30,7 +30,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT diff --git a/pom.xml b/pom.xml index 3355d5d4b21..2ed46c3d109 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ org.geonetwork-opensource geonetwork pom - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT GeoNetwork opensource GeoNetwork opensource is a standards based, Free and Open Source catalog application to manage spatially referenced diff --git a/release/build.properties b/release/build.properties index ba1eb590abf..481a9176cd1 100644 --- a/release/build.properties +++ b/release/build.properties @@ -5,7 +5,7 @@ homepage=https://geonetwork-opensource.org supportEmail=geonetwork-users@lists.sourceforge.net # Application version properties -version=4.2.5 +version=4.2.6 subVersion=SNAPSHOT # Java runtime properties diff --git a/release/pom.xml b/release/pom.xml index 88d0280d027..88b109f1e41 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -7,7 +7,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT gn-release diff --git a/schemas-test/pom.xml b/schemas-test/pom.xml index 81050b1f2d8..ee5d2a0f749 100644 --- a/schemas-test/pom.xml +++ b/schemas-test/pom.xml @@ -27,7 +27,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 jar diff --git a/schemas/csw-record/pom.xml b/schemas/csw-record/pom.xml index b810d6b4d96..3db4267fb98 100644 --- a/schemas/csw-record/pom.xml +++ b/schemas/csw-record/pom.xml @@ -5,7 +5,7 @@ gn-schemas org.geonetwork-opensource.schemas - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 gn-schema-csw-record diff --git a/schemas/dublin-core/pom.xml b/schemas/dublin-core/pom.xml index b989d7df5dd..a06ceefe96a 100644 --- a/schemas/dublin-core/pom.xml +++ b/schemas/dublin-core/pom.xml @@ -5,7 +5,7 @@ gn-schemas org.geonetwork-opensource.schemas - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/schemas/dublin-core/src/main/plugin/dublin-core/formatter/xsl-view/view.xsl b/schemas/dublin-core/src/main/plugin/dublin-core/formatter/xsl-view/view.xsl index f4fdfc7c987..d407b47b5eb 100644 --- a/schemas/dublin-core/src/main/plugin/dublin-core/formatter/xsl-view/view.xsl +++ b/schemas/dublin-core/src/main/plugin/dublin-core/formatter/xsl-view/view.xsl @@ -88,11 +88,18 @@ select="(dct:references|dc:relation)[ normalize-space(.) != '' and matches(., '.*(.gif|.png|.jpeg|.jpg)$', 'i')]"/> + + + {$schemaStrings/overview} + src="{.}" + onerror="{$imgOnError}"/> diff --git a/schemas/iso19110/pom.xml b/schemas/iso19110/pom.xml index 738f2edc14c..f5b64b7c5f9 100644 --- a/schemas/iso19110/pom.xml +++ b/schemas/iso19110/pom.xml @@ -28,7 +28,7 @@ gn-schemas org.geonetwork-opensource.schemas - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/schemas/iso19110/src/main/plugin/iso19110/index-fields/index.xsl b/schemas/iso19110/src/main/plugin/iso19110/index-fields/index.xsl index b468ca58be8..5b8454783da 100644 --- a/schemas/iso19110/src/main/plugin/iso19110/index-fields/index.xsl +++ b/schemas/iso19110/src/main/plugin/iso19110/index-fields/index.xsl @@ -110,6 +110,9 @@ "code": "", "link": "", "type": "" + + ,"cardinality": "" + ,"values": [ { diff --git a/schemas/iso19115-3.2018/pom.xml b/schemas/iso19115-3.2018/pom.xml index c031678e3af..0ac65af6600 100644 --- a/schemas/iso19115-3.2018/pom.xml +++ b/schemas/iso19115-3.2018/pom.xml @@ -6,7 +6,7 @@ gn-schemas org.geonetwork-opensource.schemas - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/fromJsonOpenDataSoft.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/fromJsonOpenDataSoft.xsl index c23687e7c04..a0df66ee81d 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/fromJsonOpenDataSoft.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/fromJsonOpenDataSoft.xsl @@ -482,7 +482,7 @@ + select="metas/records_count|dataset/metas/default/records_count"/> csv @@ -496,7 +496,7 @@ - shapefile + shp @@ -562,10 +562,9 @@ + '/exports/', $format, '?use_labels=true')" /> diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/thesaurus-transformation.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/thesaurus-transformation.xsl index bb7004ee347..a4e8b14ed11 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/thesaurus-transformation.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/thesaurus-transformation.xsl @@ -234,7 +234,10 @@ - + @@ -243,6 +246,7 @@ + @@ -253,18 +257,26 @@ codeListValue="{$thesauri/thesaurus[key = $currentThesaurus]/dname}" /> + + - + - + diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/formatter/xsl-view/view.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/formatter/xsl-view/view.xsl index bfaca7e88a4..abb49d93770 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/formatter/xsl-view/view.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/formatter/xsl-view/view.xsl @@ -50,7 +50,7 @@ - + @@ -171,8 +171,9 @@ - - + + @@ -191,8 +192,9 @@ - - + + @@ -247,19 +249,27 @@ - - {$schemaStrings/overview} + - -
- - - -
-
+ +
+ {$schemaStrings/overview} + + +
+ + + +
+
+
@@ -309,7 +319,7 @@ select="mdb:identificationInfo/*/mri:citation/*/cit:otherCitationDetails"/> --> - + @@ -414,7 +424,7 @@ -
+
@@ -569,7 +579,6 @@
- @@ -586,16 +595,16 @@ match="gex:EX_Extent" priority="100">
-

- +

+ - -

-
- + +

+
+
@@ -605,48 +614,96 @@ -
-

- - -

+ + + + + + + + +
+

+ + +

+ + + +
+
+
+ - - - - - - - - - - - - - - - , - - - - - - - - - - - - , + + + + + + + + + + + + - + + + , + - + + + + + + + + + + , + + + + + + + + + + + + + + + + + +   + + + +
@@ -669,7 +726,7 @@ cit:administrativeArea|cit:postalCode|cit:country)">
-
+
@@ -682,7 +739,7 @@
- -
+
+
+ - - + + + + + + ( - + ) - @@ -817,13 +885,13 @@
- - +

@@ -839,6 +907,7 @@ match="mri:descriptiveKeywords[ normalize-space(string-join(*/mri:keyword//text(), '')) = '' or count(*/mri:keyword) = 0]" priority="200"/> +

- @@ -962,8 +1031,8 @@

- +
@@ -989,8 +1058,8 @@ msr:MD_PixelOrientationCode|srv:SV_ParameterDirection| reg:RE_AmendmentType)">
  • - +
  • @@ -1059,12 +1128,32 @@ + + + + + - - - + + + + + + + + + + + + + + + + + + { "code": "", "codeSpace": "", @@ -815,6 +815,16 @@ + + + + + + + + + ", "link": "", "type": "" + + ,"cardinality": "" + ,"values": [ { diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/layout-custom-fields-keywords.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/layout-custom-fields-keywords.xsl index 419682de890..739222a33a5 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/layout-custom-fields-keywords.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/layout-custom-fields-keywords.xsl @@ -90,11 +90,12 @@ + + select="if ($thesaurusList/thesaurus[@key = substring-after($thesaurusIdentifier, 'geonetwork.thesaurus.')]) + then $thesaurusList/thesaurus[@key = substring-after($thesaurusIdentifier, 'geonetwork.thesaurus.')] + else $listOfThesaurus/thesaurus[title = $thesaurusTitle]"/> @@ -140,13 +141,18 @@ then $overrideLabel else (mri:thesaurusName/*/cit:title/(gco:CharacterString|lan:PT_FreeText/lan:textGroup/lan:LocalisedCharacterString|gcx:Anchor))[1]"/> - + + - + select="if ($thesaurusList/thesaurus[@key = $thesaurusKey]) + then $thesaurusList/thesaurus[@key = $thesaurusKey] + else if ($listOfThesaurus/thesaurus[key = $thesaurusKey]) + then $listOfThesaurus/thesaurus[key = $thesaurusKey] + else if ($listOfThesaurus/thesaurus[multilingualTitles/multilingualTitle/title = $thesaurusTitle]) + then $listOfThesaurus/thesaurus[multilingualTitles/multilingualTitle/title = $thesaurusTitle] + else $listOfThesaurus/thesaurus[title = $thesaurusTitle]"/> diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/utility-fn.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/utility-fn.xsl index ca10e621241..e81c1b4cf13 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/utility-fn.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/layout/utility-fn.xsl @@ -47,7 +47,7 @@ diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/onlinesrc-add.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/onlinesrc-add.xsl index 61ee24404dd..ccdd293aaa6 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/onlinesrc-add.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/process/onlinesrc-add.xsl @@ -268,7 +268,7 @@ - + diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/update-fixed-info.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/update-fixed-info.xsl index 7c0d9e5640a..c7bc7832b0d 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/update-fixed-info.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/update-fixed-info.xsl @@ -177,7 +177,7 @@ - + diff --git a/schemas/iso19139/pom.xml b/schemas/iso19139/pom.xml index 7e4e4c9346a..411709eaa0a 100644 --- a/schemas/iso19139/pom.xml +++ b/schemas/iso19139/pom.xml @@ -5,7 +5,7 @@ gn-schemas org.geonetwork-opensource.schemas - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/schemas/iso19139/src/main/plugin/iso19139/convert/thesaurus-transformation.xsl b/schemas/iso19139/src/main/plugin/iso19139/convert/thesaurus-transformation.xsl index 8df2248199b..a27b85bcb18 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/convert/thesaurus-transformation.xsl +++ b/schemas/iso19139/src/main/plugin/iso19139/convert/thesaurus-transformation.xsl @@ -293,12 +293,15 @@ + select="geonet:add-thesaurus-info($currentThesaurus, $listOfLanguage[1], + $withAnchor, $withThesaurusAnchor, + /root/gui/thesaurus/thesauri, not(/root/request/keywordOnly))"/> + @@ -311,18 +314,28 @@ codeListValue="{$thesauri/thesaurus[key = $currentThesaurus]/dname}"/> + + + + - + - + diff --git a/schemas/iso19139/src/main/plugin/iso19139/formatter/xsl-view/view.xsl b/schemas/iso19139/src/main/plugin/iso19139/formatter/xsl-view/view.xsl index f20d2f8a819..3f13daa000f 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/formatter/xsl-view/view.xsl +++ b/schemas/iso19139/src/main/plugin/iso19139/formatter/xsl-view/view.xsl @@ -160,8 +160,9 @@ - - + + @@ -172,8 +173,9 @@ - - + + @@ -221,20 +223,27 @@ - - {$schemaStrings/overview} - - -
    - - - -
    -
    + + +
    + {$schemaStrings/overview} + + +
    + + + +
    +
    +
    @@ -622,16 +631,16 @@ - + - - - + - + - + - + () @@ -666,7 +675,12 @@
    - +
    diff --git a/schemas/iso19139/src/main/plugin/iso19139/index-fields/index.xsl b/schemas/iso19139/src/main/plugin/iso19139/index-fields/index.xsl index 090ca87e747..bbc4c321f47 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/index-fields/index.xsl +++ b/schemas/iso19139/src/main/plugin/iso19139/index-fields/index.xsl @@ -29,7 +29,7 @@ xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:srv="http://www.isotc211.org/2005/srv" xmlns:gml="http://www.opengis.net/gml/3.2" - xmlns:gml31="http://www.opengis.net/gml" + xmlns:gml320="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:gn-fn-index="http://geonetwork-opensource.org/xsl/functions/index" xmlns:index="java:org.fao.geonet.kernel.search.EsSearchManager" @@ -333,7 +333,7 @@ - + { "code": "", "codeSpace": "", @@ -650,7 +650,7 @@ @@ -668,6 +668,10 @@ + + + + ,--> - + + select="gml:beginPosition + |gml:begin/gml:TimeInstant/gml:timePosition + |gml320:beginPosition + |gml320:begin/gml320:TimeInstant/gml320:timePosition"/> + select="gml:endPosition + |gml:end/gml:TimeInstant/gml:timePosition + |gml320:endPosition + |gml320:end/gml320:TimeInstant/gml320:timePosition"/> diff --git a/schemas/iso19139/src/main/plugin/iso19139/layout/layout-custom-fields-keywords.xsl b/schemas/iso19139/src/main/plugin/iso19139/layout/layout-custom-fields-keywords.xsl index c16ed552ee5..80c82f1e00d 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/layout/layout-custom-fields-keywords.xsl +++ b/schemas/iso19139/src/main/plugin/iso19139/layout/layout-custom-fields-keywords.xsl @@ -118,11 +118,12 @@ + + select="if ($thesaurusList/thesaurus[@key = substring-after($thesaurusIdentifier, 'geonetwork.thesaurus.')]) + then $thesaurusList/thesaurus[@key = substring-after($thesaurusIdentifier, 'geonetwork.thesaurus.')] + else $listOfThesaurus/thesaurus[title = $thesaurusTitle]"/> @@ -170,12 +171,18 @@ - + + + select="if ($thesaurusList/thesaurus[@key = $thesaurusKey]) + then $thesaurusList/thesaurus[@key = $thesaurusKey] + else if ($listOfThesaurus/thesaurus[key = $thesaurusKey]) + then $listOfThesaurus/thesaurus[key = $thesaurusKey] + else if ($listOfThesaurus/thesaurus[multilingualTitles/multilingualTitle/title = $thesaurusTitle]) + then $listOfThesaurus/thesaurus[multilingualTitles/multilingualTitle/title = $thesaurusTitle] + else $listOfThesaurus/thesaurus[title = $thesaurusTitle]"/> diff --git a/schemas/iso19139/src/main/plugin/iso19139/loc/ger/codelists.xml b/schemas/iso19139/src/main/plugin/iso19139/loc/ger/codelists.xml index bb34ecdb566..a715df4a283 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/loc/ger/codelists.xml +++ b/schemas/iso19139/src/main/plugin/iso19139/loc/ger/codelists.xml @@ -1452,7 +1452,7 @@ otherRestrictions - + Andere Einschränkungen, die oben nicht aufgeführt sind diff --git a/schemas/iso19139/src/main/plugin/iso19139/process/onlinesrc-add.xsl b/schemas/iso19139/src/main/plugin/iso19139/process/onlinesrc-add.xsl index dafdcd1590b..6947108375b 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/process/onlinesrc-add.xsl +++ b/schemas/iso19139/src/main/plugin/iso19139/process/onlinesrc-add.xsl @@ -518,7 +518,7 @@ Insert is made in first transferOptions found. - + diff --git a/schemas/iso19139/src/main/plugin/iso19139/update-fixed-info-keywords.xsl b/schemas/iso19139/src/main/plugin/iso19139/update-fixed-info-keywords.xsl index 6f8c837b513..6a0e9d630f3 100644 --- a/schemas/iso19139/src/main/plugin/iso19139/update-fixed-info-keywords.xsl +++ b/schemas/iso19139/src/main/plugin/iso19139/update-fixed-info-keywords.xsl @@ -250,6 +250,7 @@ getRecordPopularity( AbstractMetadata metadata; try { metadata = ApiUtils.canViewRecord(metadataUuid, request); + + // If the workflow is enabled, don't use the working copy to increase the popularity + if (metadata instanceof MetadataDraft) { + metadata = metadataRepository.findOneByUuid(metadataUuid); + } } catch (ResourceNotFoundException e) { Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); throw e; @@ -563,6 +571,11 @@ public ResponseEntity increaseRecordPopularity( AbstractMetadata metadata; try { metadata = ApiUtils.canViewRecord(metadataUuid, request); + + // If the workflow is enabled, don't use the working copy to increase the popularity + if (metadata instanceof MetadataDraft) { + metadata = metadataRepository.findOneByUuid(metadataUuid); + } } catch (ResourceNotFoundException e) { Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); throw e; @@ -570,9 +583,9 @@ public ResponseEntity increaseRecordPopularity( Log.debug(API.LOG_MODULE_NAME, e.getMessage(), e); throw new NotAllowedException(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW); } - ServiceContext context = ApiUtils.createServiceContext(request); + ServiceContext serviceContext = ApiUtils.createServiceContext(request); - dataManager.increasePopularity(context, metadata.getId() + ""); + dataManager.increasePopularity(serviceContext, metadata.getId() + ""); return new ResponseEntity<>(metadata.getDataInfo().getPopularity() + "", HttpStatus.CREATED); @@ -625,9 +638,9 @@ public RelatedResponse getAssociatedResources( } String language = languageUtils.getIso3langCode(request.getLocales()); - final ServiceContext context = ApiUtils.createServiceContext(request); + final ServiceContext serviceContext = ApiUtils.createServiceContext(request); - return getAssociatedResources(language, context, md, type, start, rows); + return getAssociatedResources(language, serviceContext, md, type, start, rows); } @io.swagger.v3.oas.annotations.Operation( @@ -694,7 +707,7 @@ public FeatureResponse getFeatureCatalog( private boolean isIncludedAttributeTable(RelatedResponse.Fcat fcat) { return fcat != null && fcat.getItem() != null - && fcat.getItem().size() > 0 + && !fcat.getItem().isEmpty() && fcat.getItem().get(0).getFeatureType() != null && fcat.getItem().get(0).getFeatureType().getAttributeTable() != null && fcat.getItem().get(0).getFeatureType().getAttributeTable().getElement() != null; diff --git a/services/src/main/java/org/fao/geonet/api/records/MetadataIndexApi.java b/services/src/main/java/org/fao/geonet/api/records/MetadataIndexApi.java index b84cf0c2005..57303c9be00 100644 --- a/services/src/main/java/org/fao/geonet/api/records/MetadataIndexApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/MetadataIndexApi.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2023 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -36,6 +36,7 @@ import org.fao.geonet.api.ApiUtils; import org.fao.geonet.kernel.DataManager; import org.fao.geonet.kernel.SelectionManager; +import org.fao.geonet.kernel.datamanager.IMetadataUtils; import org.fao.geonet.kernel.search.index.BatchOpsMetadataReindexer; import org.fao.geonet.kernel.setting.SettingManager; import org.springframework.beans.factory.annotation.Autowired; @@ -66,6 +67,9 @@ public class MetadataIndexApi { @Autowired SettingManager settingManager; + @Autowired + IMetadataUtils metadataUtils; + @io.swagger.v3.oas.annotations.Operation( summary = "Index a set of records", description = "Index a set of records provided either by a bucket or a list of uuids") @@ -96,28 +100,19 @@ JSONObject index( ) String bucket, @Parameter(hidden = true) - HttpSession httpSession, - @Parameter(hidden = true) - HttpServletRequest request + HttpSession httpSession ) throws Exception { - ServiceContext serviceContext = ApiUtils.createServiceContext(request); UserSession session = ApiUtils.getUserSession(httpSession); - SelectionManager selectionManager = - SelectionManager.getManager(serviceContext.getUserSession()); - Set records = ApiUtils.getUuidsParameterOrSelection(uuids, bucket, session); Set ids = Sets.newHashSet(); int index = 0; for (String uuid : records) { try { - final String metadataId = dataManager.getMetadataId(uuid); - if (metadataId != null) { - ids.add(Integer.valueOf(metadataId)); - } + metadataUtils.findAllByUuid(uuid).forEach(m -> ids.add(m.getId())); } catch (Exception e) { try { ids.add(Integer.valueOf(uuid)); diff --git a/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java b/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java index 7a4aab50c24..70f0872e85d 100644 --- a/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/MetadataSharingApi.java @@ -558,7 +558,8 @@ private void setOperations( java.util.Optional allGroupOpsAfter = privileges.stream().filter(p -> p.getGroup() == ReservedGroup.all.getId()).findFirst(); - boolean publishedAfter = allGroupOpsAfter.get().getOperations().get(ReservedOperation.view.name()); + // If we cannot find it then default to before value so that it will fail the next condition. + boolean publishedAfter = allGroupOpsAfter.isPresent()?allGroupOpsAfter.get().getOperations().getOrDefault(ReservedOperation.view.name(), publishedBefore):publishedBefore; if (publishedBefore != publishedAfter) { MetadataPublicationNotificationInfo metadataNotificationInfo = new MetadataPublicationNotificationInfo(); @@ -1214,19 +1215,42 @@ private MetadataProcessingReport shareSelection(String[] uuids, String bucket, S } List privileges = sharing.getPrivileges(); + List allGroupPrivileges = new ArrayList<>(); try { - setOperations(sharing, dataManager, context, appContext, metadata, operationMap, privileges, - ApiUtils.getUserSession(session).getUserIdAsInt(), skipAllReservedGroup, report, request, - metadataListToNotifyPublication, notifyByEmail); + if (metadata instanceof MetadataDraft) { + // If the metadata is a working copy, publish privileges (ALL and INTRANET groups) + // should be applied to the approved version. + Metadata md = this.metadataRepository.findOneByUuid(metadata.getUuid()); + + if (md != null) { + setOperations(sharing, dataManager, context, appContext, md, operationMap, allGroupPrivileges, + ApiUtils.getUserSession(session).getUserIdAsInt(), skipAllReservedGroup, report, request, + metadataListToNotifyPublication, notifyByEmail); + + report.incrementProcessedRecords(); + listOfUpdatedRecords.add(String.valueOf(md.getId())); + } else { + setOperations(sharing, dataManager, context, appContext, metadata, operationMap, privileges, + ApiUtils.getUserSession(session).getUserIdAsInt(), skipAllReservedGroup, report, request, + metadataListToNotifyPublication, notifyByEmail); + + report.incrementProcessedRecords(); + listOfUpdatedRecords.add(String.valueOf(metadata.getId())); + } + + } else { + setOperations(sharing, dataManager, context, appContext, metadata, operationMap, privileges, + ApiUtils.getUserSession(session).getUserIdAsInt(), skipAllReservedGroup, report, request, + metadataListToNotifyPublication, notifyByEmail); + + report.incrementProcessedRecords(); + listOfUpdatedRecords.add(String.valueOf(metadata.getId())); + } } catch (NotAllowedException ex) { report.addMetadataError(metadata, ex.getMessage()); report.incrementUnchangedRecords(); - continue; } - - report.incrementProcessedRecords(); - listOfUpdatedRecords.add(String.valueOf(metadata.getId())); } } diff --git a/services/src/main/java/org/fao/geonet/api/records/MetadataTagApi.java b/services/src/main/java/org/fao/geonet/api/records/MetadataTagApi.java index a38870e8df1..11a2cd603d4 100644 --- a/services/src/main/java/org/fao/geonet/api/records/MetadataTagApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/MetadataTagApi.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2023 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -38,7 +38,9 @@ import org.fao.geonet.api.processing.report.SimpleMetadataProcessingReport; import org.fao.geonet.constants.Geonet; import org.fao.geonet.domain.AbstractMetadata; +import org.fao.geonet.domain.Metadata; import org.fao.geonet.domain.MetadataCategory; +import org.fao.geonet.domain.MetadataDraft; import org.fao.geonet.domain.utils.ObjectJSONUtils; import org.fao.geonet.events.history.RecordCategoryChangeEvent; import org.fao.geonet.kernel.AccessManager; @@ -192,8 +194,16 @@ private void indexTags(AbstractMetadata metadata) throws Exception { } fields.put(Geonet.IndexFieldNames.CAT, categories); } - searchManager.updateFields(metadata.getUuid(), fields, - Sets.newHashSet(new String[] {Geonet.IndexFieldNames.CAT})); + + if (metadata instanceof MetadataDraft) { + searchManager.updateFields(metadata.getUuid() + "-draft", fields, + Sets.newHashSet(new String[] {Geonet.IndexFieldNames.CAT})); + } else { + searchManager.updateFields(metadata.getUuid(), fields, + Sets.newHashSet(new String[] {Geonet.IndexFieldNames.CAT})); + } + + } @io.swagger.v3.oas.annotations.Operation( diff --git a/services/src/main/java/org/fao/geonet/api/records/attachments/AttachmentsApi.java b/services/src/main/java/org/fao/geonet/api/records/attachments/AttachmentsApi.java index a0874f7162d..361f542d6bb 100644 --- a/services/src/main/java/org/fao/geonet/api/records/attachments/AttachmentsApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/attachments/AttachmentsApi.java @@ -49,6 +49,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import org.springframework.util.MultiValueMap; +import org.springframework.util.StreamUtils; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.PathVariable; @@ -254,22 +255,21 @@ public MetadataResource putResourceFromURL( @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Record attachment."), @ApiResponse(responseCode = "403", description = "Operation not allowed. " + "User needs to be able to download the resource.")}) - @ResponseBody public void getResource( @Parameter(description = "The metadata UUID", required = true, example = "43d7c186-2187-4bcd-8843-41e575a5ef56") @PathVariable String metadataUuid, @Parameter(description = "The resource identifier (ie. filename)", required = true) @PathVariable String resourceId, @Parameter(description = "Use approved version or not", example = "true") @RequestParam(required = false, defaultValue = "true") Boolean approved, @Parameter(description = "Size (only applies to images). From 1px to 2048px.", example = "200") @RequestParam(required = false) Integer size, @Parameter(hidden = true) HttpServletRequest request, - @Parameter(hidden = true) HttpServletResponse response) throws Exception { + @Parameter(hidden = true) HttpServletResponse response + ) throws Exception { ServiceContext context = ApiUtils.createServiceContext(request); ApiUtils.canViewRecord(metadataUuid, request); - ServletOutputStream out = response.getOutputStream(); if (store instanceof FMEStore - || (store instanceof ResourceLoggerStore && ((ResourceLoggerStore) store).getDecoratedStore() instanceof FMEStore)) { + || (store instanceof ResourceLoggerStore && ((ResourceLoggerStore) store).getDecoratedStore() instanceof FMEStore)) { response.setHeader("Content-Disposition", "inline; filename=\"" + resourceId + "\""); response.setHeader("Cache-Control", "no-cache"); @@ -278,32 +278,19 @@ public void getResource( try (Store.ResourceHolder file = store.getResource(context, metadataUuid, resourceId, approved)) { response.setHeader("Content-Disposition", "inline; filename=\"" + file.getMetadata().getFilename() + "\""); response.setHeader("Cache-Control", "no-cache"); - - Path path = file.getPath(); - String contentType = getFileContentType(path); + String contentType = getFileContentType(file.getPath()); response.setHeader("Content-Type", contentType); if (contentType.startsWith("image/") && size != null) { if (size >= MIN_IMAGE_SIZE && size <= MAX_IMAGE_SIZE) { - BufferedImage image = ImageIO.read(path.toFile()); + BufferedImage image = ImageIO.read(file.getPath().toFile()); BufferedImage resized = ImageUtil.resize(image, size); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - ImageIO.write(resized, "png", output); - output.flush(); - byte[] imagesB = output.toByteArray(); - output.close(); - out.write(imagesB); - out.flush(); - out.close(); + ImageIO.write(resized, "png", response.getOutputStream()); } else { - throw new IllegalArgumentException(String.format( - "Image can only be resized from %d to %d. You requested %d.", - MIN_IMAGE_SIZE, MAX_IMAGE_SIZE, size)); + StreamUtils.copy(Files.newInputStream(file.getPath()), response.getOutputStream()); } } else { - IOUtils.copy(Files.newInputStream(path, StandardOpenOption.READ), out); - out.flush(); - out.close(); + StreamUtils.copy(Files.newInputStream(file.getPath()), response.getOutputStream()); } } } diff --git a/services/src/main/java/org/fao/geonet/api/records/editing/MetadataEditingApi.java b/services/src/main/java/org/fao/geonet/api/records/editing/MetadataEditingApi.java index b75f845233f..e38140c0809 100644 --- a/services/src/main/java/org/fao/geonet/api/records/editing/MetadataEditingApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/editing/MetadataEditingApi.java @@ -373,6 +373,10 @@ public void saveEdits( boolean reindex = false; + String lang = String.valueOf(languageUtils.parseAcceptLanguage(request.getLocales())); + ResourceBundle messages = ResourceBundle.getBundle("org.fao.geonet.api.Messages", + new Locale(lang)); + // Save validation if the forceValidationOnMdSave is enabled if (forceValidationOnMdSave) { validator.doValidate(metadata, context.getLanguage()); @@ -412,7 +416,7 @@ public void saveEdits( metadataStatus.setChangeDate(new ISODate()); metadataStatus.setUserId(session.getUserIdAsInt()); metadataStatus.setStatusValue(statusValue); - metadataStatus.setChangeMessage("Save and submit metadata"); + metadataStatus.setChangeMessage(messages.getString("metadata_save_submit_text")); List listOfStatusChange = new ArrayList<>(1); listOfStatusChange.add(metadataStatus); @@ -434,7 +438,7 @@ public void saveEdits( metadataStatus.setChangeDate(new ISODate()); metadataStatus.setUserId(session.getUserIdAsInt()); metadataStatus.setStatusValue(statusValue); - metadataStatus.setChangeMessage("Save and approve metadata"); + metadataStatus.setChangeMessage(messages.getString("metadata_save_approve_text")); List listOfStatusChange = new ArrayList<>(1); listOfStatusChange.add(metadataStatus); diff --git a/services/src/main/java/org/fao/geonet/api/records/formatters/FormatterApi.java b/services/src/main/java/org/fao/geonet/api/records/formatters/FormatterApi.java index 15e2db9f089..9145f38336a 100644 --- a/services/src/main/java/org/fao/geonet/api/records/formatters/FormatterApi.java +++ b/services/src/main/java/org/fao/geonet/api/records/formatters/FormatterApi.java @@ -285,7 +285,7 @@ public void getRecordFormattedBy( long roundedChangeDate = changeDateAsTime / 1000 * 1000; if (request.checkNotModified(language, roundedChangeDate) && context.getBean(CacheConfig.class).allowCaching(key)) { - if (!skipPopularityBool) { + if (!skipPopularityBool && approved) { context.getBean(DataManager.class).increasePopularity(context, String.valueOf(metadata.getId())); } return; @@ -310,7 +310,7 @@ public void getRecordFormattedBy( bytes = context.getBean(FormatterCache.class).get(key, validator, formatMetadata, false); } if (bytes != null) { - if (!skipPopularityBool) { + if (!skipPopularityBool && approved) { context.getBean(DataManager.class).increasePopularity(context, String.valueOf(metadata.getId())); } writeOutResponse(context, metadataUuid, diff --git a/services/src/main/java/org/fao/geonet/api/records/formatters/XsltFormatter.java b/services/src/main/java/org/fao/geonet/api/records/formatters/XsltFormatter.java index 05523f50dfa..112f7444859 100644 --- a/services/src/main/java/org/fao/geonet/api/records/formatters/XsltFormatter.java +++ b/services/src/main/java/org/fao/geonet/api/records/formatters/XsltFormatter.java @@ -24,14 +24,24 @@ package org.fao.geonet.api.records.formatters; import org.fao.geonet.ApplicationContextHolder; +import org.fao.geonet.constants.Geonet; import org.fao.geonet.kernel.SchemaManager; +import org.fao.geonet.kernel.search.JSONLocCacheLoader; import org.fao.geonet.kernel.setting.SettingManager; +import org.fao.geonet.utils.Log; import org.fao.geonet.utils.Xml; import org.jdom.Element; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import static org.fao.geonet.api.records.formatters.SchemaLocalizations.loadSchemaLocalizations; @@ -51,6 +61,16 @@ @Component public class XsltFormatter implements FormatterImpl { + private final Map translationElements = new HashMap<>(); + + private final ConfigurableApplicationContext configurableApplicationContext; + + @Autowired + public XsltFormatter(ConfigurableApplicationContext configurableApplicationContext) { + this.configurableApplicationContext = configurableApplicationContext; + } + + /** * @param schema Use all to return all schemas translations * @param schemasToLoadList @@ -97,9 +117,33 @@ public String format(FormatterParams fparams) throws Exception { root.addContent(new Element("lang").setText(fparams.context.getLanguage())); root.addContent(new Element("url").setText(fparams.url)); - // FIXME: This is a hack to mimic what Jeeves service are doing. - // Some XSLT are used by both formatters and Jeeves and Spring MVC services - Element translations = new Element("translations"); + + if (!translationElements.containsKey(fparams.context.getLanguage())) { + // Get translation keys and add them to the cache + Element translations = new Element("translations"); + Map translationMap = new JSONLocCacheLoader(configurableApplicationContext, fparams.context.getLanguage()).call(); + for (Map.Entry entry : translationMap.entrySet()) { + // Attempt to only use name that are valid element names. + // https://www.w3.org/TR/REC-xml/#NT-Name + // https://www.w3.org/TR/REC-xml/#NT-NameStartChar + // Skip keys that are not alphanumeric only including "." - otherwise certain chars like ':?+...' can cause problem when creating element as they are invalid element names + // i.e. some properties look like the following + // "cron-0 0 12 * * ?": "Fire at 12pm (noon) every day" + // "system/feedback"="Feedback" + + if (entry.getKey().matches("[a-zA-Z0-9\\.]+")) { + try { + translations.addContent(new Element(entry.getKey()).setText(entry.getValue())); + } catch (Exception e) { + // If errors are generated here then it may mean that the regular expression needs to be updated. + Log.error(Geonet.GEONETWORK, "Failed to add translation key for \"" + entry.getKey() + "\"=\"" + entry.getValue() + "\". " + e.getMessage()); + } + } + } + + translationElements.put(fparams.context.getLanguage(), translations); + } + root.addContent((Element)translationElements.get(fparams.context.getLanguage()).clone()); Element gui = new Element("gui"); String baseUrl = settingManager.getBaseURL(); String context = baseUrl.replace(settingManager.getServerURL(), ""); @@ -107,6 +151,7 @@ public String format(FormatterParams fparams) throws Exception { context.substring(0, context.length() - 1) )); gui.addContent(new Element("nodeUrl").setText(settingManager.getNodeURL())); + gui.addContent(new Element("nodeId").setText(settingManager.getNodeId())); gui.addContent(new Element("baseUrl").setText(baseUrl)); gui.addContent(new Element("serverUrl").setText(settingManager.getServerURL())); gui.addContent(new Element("language").setText(fparams.context.getLanguage())); diff --git a/services/src/main/java/org/fao/geonet/api/site/SiteInformation.java b/services/src/main/java/org/fao/geonet/api/site/SiteInformation.java index 66cbb1f0baf..3adae15438e 100644 --- a/services/src/main/java/org/fao/geonet/api/site/SiteInformation.java +++ b/services/src/main/java/org/fao/geonet/api/site/SiteInformation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2023 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -29,7 +29,7 @@ import org.apache.commons.dbcp2.BasicDataSource; import org.fao.geonet.GeonetContext; import org.fao.geonet.constants.Geonet; -import org.fao.geonet.kernel.GeonetworkDataDirectory; +import org.fao.geonet.kernel.search.EsSearchManager; import org.fao.geonet.utils.Log; import org.fao.geonet.utils.TransformerFactoryFactory; @@ -49,11 +49,11 @@ */ public class SiteInformation { final Properties properties = System.getProperties(); - private HashMap catProperties = new HashMap(); - private HashMap indexProperties = new HashMap(); - private HashMap systemProperties = new HashMap(); - private HashMap databaseProperties = new HashMap(); - private HashMap versionProperties = new HashMap(); + private Map catProperties = new HashMap<>(); + private Map indexProperties = new HashMap<>(); + private Map systemProperties = new HashMap<>(); + private Map databaseProperties = new HashMap<>(); + private Map versionProperties = new HashMap<>(); public SiteInformation(final ServiceContext context, final GeonetContext gc) { if (context.getUserSession().isAuthenticated()) { @@ -68,44 +68,44 @@ public SiteInformation(final ServiceContext context, final GeonetContext gc) { } catch (IOException e) { Log.error(Geonet.GEONETWORK, e.getMessage(), e); } - loadVersionInfo(context); + loadVersionInfo(); loadSystemInfo(); } } @JsonProperty(value = "catalogue") - public HashMap getCatProperties() { + public Map getCatProperties() { return catProperties; } - public void setCatProperties(HashMap catProperties) { + public void setCatProperties(Map catProperties) { this.catProperties = catProperties; } @JsonProperty(value = "index") - public HashMap getIndexProperties() { + public Map getIndexProperties() { return indexProperties; } - public void setIndexProperties(HashMap indexProperties) { + public void setIndexProperties(Map indexProperties) { this.indexProperties = indexProperties; } @JsonProperty(value = "main") - public HashMap getSystemProperties() { + public Map getSystemProperties() { return systemProperties; } - public void setSystemProperties(HashMap systemProperties) { + public void setSystemProperties(Map systemProperties) { this.systemProperties = systemProperties; } @JsonProperty(value = "database") - public HashMap getDatabaseProperties() { + public Map getDatabaseProperties() { return databaseProperties; } - public void setDatabaseProperties(HashMap databaseProperties) { + public void setDatabaseProperties(Map databaseProperties) { this.databaseProperties = databaseProperties; } @@ -114,7 +114,7 @@ public Map getVersionProperties() { return versionProperties; } - public void setVersionProperties(HashMap versionProperties) { + public void setVersionProperties(Map versionProperties) { this.versionProperties = versionProperties; } @@ -162,8 +162,11 @@ private void loadSystemInfo() { * Compute information about index. */ private void loadIndexInfo(ServiceContext context) throws IOException { - final GeonetworkDataDirectory dataDirectory = context.getBean(GeonetworkDataDirectory.class); - // TODOES - give information about the index status ? + EsSearchManager esSearchManager = context.getBean(EsSearchManager.class); + + indexProperties.put("es.url", esSearchManager.getClient().getServerUrl()); + indexProperties.put("es.version", esSearchManager.getClient().getServerVersion()); + indexProperties.put("es.index", esSearchManager.getDefaultIndex()); } /** @@ -203,7 +206,7 @@ private void loadDatabaseInfo(ServiceContext context) throws SQLException { /** * Compute information about git commit. */ - private void loadVersionInfo(ServiceContext context) { + private void loadVersionInfo() { Properties prop = new Properties(); try (InputStream input = getClass().getResourceAsStream("/git.properties")) { diff --git a/services/src/main/java/org/fao/geonet/api/sld/SldApi.java b/services/src/main/java/org/fao/geonet/api/sld/SldApi.java index f19f4626f66..047925870ae 100644 --- a/services/src/main/java/org/fao/geonet/api/sld/SldApi.java +++ b/services/src/main/java/org/fao/geonet/api/sld/SldApi.java @@ -2,9 +2,10 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; - +import jeeves.transaction.TransactionManager; +import jeeves.transaction.TransactionTask; import org.apache.commons.lang3.StringUtils; -import org.fao.geonet.api.API; +import org.fao.geonet.ApplicationContextHolder; import org.fao.geonet.api.exception.ResourceNotFoundException; import org.fao.geonet.constants.Geonet; import org.fao.geonet.domain.TextFile; @@ -25,13 +26,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; import org.springframework.web.bind.annotation.*; import javax.mail.internet.ParseException; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.xml.transform.TransformerException; - import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -40,8 +39,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Map; import java.util.List; +import java.util.Map; +import java.util.Optional; @Service @RequestMapping(value = { @@ -52,14 +52,14 @@ public class SldApi { public static final String LOGGER = Geonet.GEONETWORK + ".api.sld"; + @Autowired TextFileRepository fileRepository; @Autowired SettingManager settingManager; @io.swagger.v3.oas.annotations.Operation(summary = "Test form", hidden = true) - @RequestMapping(value = "/sldform", - method = RequestMethod.GET) + @GetMapping(value = "/sldform") @ResponseBody @ResponseStatus(value = HttpStatus.OK) public String getSLDTestingForm() { @@ -71,75 +71,46 @@ public String getSLDTestingForm() { "Layers :" + "
    " + "JSON Custom filters : " + - "
    " + + "
    " + "" + "" + ""; } @io.swagger.v3.oas.annotations.Operation(summary = "Get the list of SLD available") - @RequestMapping(value = "/sld", - method = RequestMethod.GET, + @GetMapping(value = "/sld", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @ResponseStatus(value = HttpStatus.OK) - public List getSLD(HttpServletRequest request) { + public List getSLD() { List files = fileRepository.findAll(); List response = new ArrayList<>(files.size()); - String pathPrefix = request.getContextPath() + request.getServletPath(); for (TextFile file : files) { response.add( settingManager.getNodeURL() + "api/tools/ogc/sld/" + file.getId() + ".xml"); @@ -150,8 +121,7 @@ public List getSLD(HttpServletRequest request) { @io.swagger.v3.oas.annotations.Operation(summary = "Remove all SLD files", description = "Clean all SLD generated previously") - @RequestMapping(value = "/sld", - method = RequestMethod.DELETE, + @DeleteMapping(value = "/sld", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @ResponseStatus(value = HttpStatus.OK) @@ -161,9 +131,8 @@ public void deteleSLD() { @io.swagger.v3.oas.annotations.Operation(summary = "Generate a SLD with a new filter", - description = "Get the currend SLD for the requested layers, add new filters in, save the SLD and return the new SLD URL.") - @RequestMapping(value = "/sld", - method = RequestMethod.POST, + description = "Get the current SLD for the requested layers, add new filters in, save the SLD and return the new SLD URL.") + @PostMapping(value = "/sld", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseStatus(value = HttpStatus.CREATED) @@ -178,47 +147,66 @@ String buildSLD( @RequestParam("layers") String layers, @Parameter(description = "The filters in JSON", required = true) - @RequestParam("filters") String filters, - HttpServletRequest request) throws ServiceException, TransformerException, JSONException, ParseException, IOException, JDOMException, URISyntaxException { + @RequestParam("filters") String filters) throws ServiceException, JSONException, ParseException, IOException, JDOMException, URISyntaxException { - try { - Map hash = SLDUtil.parseSLD(new URI(serverURL), layers); + Map hash = SLDUtil.parseSLD(new URI(serverURL), layers); - Element root = Xml.loadString(hash.get("content"), false); + Element root = Xml.loadString(hash.get("content"), false); - if (root.getName().equals("ServiceExceptionReport")) { - throw new ServiceException("The WMS GetStyle request failed."); - } - Filter customFilter = SLDUtil.generateCustomFilter(new JSONObject(filters)); - SLDUtil.insertFilter(root, customFilter); + if (root.getName().equals("ServiceExceptionReport")) { + throw new ServiceException("The WMS GetStyle request failed."); + } + Filter customFilter = SLDUtil.generateCustomFilter(new JSONObject(filters)); + SLDUtil.insertFilter(root, customFilter); - String charset = hash.get("charset"); - Format format = Format.getPrettyFormat(); - if (charset != null && charset != "") { - format.setEncoding(charset); + String charset = hash.get("charset"); + Format format = Format.getPrettyFormat(); + if (StringUtils.isNoneEmpty(charset)) { + format.setEncoding(charset); + } + XMLOutputter outputter = new XMLOutputter(format); + Document doc = new Document(root); + String sldDoc = outputter.outputString(doc); + + TextFile sld = new TextFile(); + sld.setContent(sldDoc); + sld.setMimeType("application/xml"); + + TransactionManager.runInTransaction("sldApi", ApplicationContextHolder.get(), + TransactionManager.TransactionRequirement.CREATE_NEW, + TransactionManager.CommitBehavior.ALWAYS_COMMIT, + false, new TransactionTask() { + @Override + public Void doInTransaction(TransactionStatus transaction) throws Throwable { + fileRepository.saveAndFlush(sld); + return null; + } } - XMLOutputter outputter = new XMLOutputter(format); - Document doc = new Document(root); - String sldDoc = outputter.outputString(doc); + ); - TextFile sld = new TextFile(); - sld.setContent(sldDoc); - sld.setMimeType("application/xml"); - fileRepository.save(sld); + return settingManager.getNodeURL() + "api/tools/ogc/sld/" + sld.getId() + ".xml"; + } - String pathPrefix = request.getContextPath() + request.getServletPath(); - String url = settingManager.getNodeURL() + "api/tools/ogc/sld/" + sld.getId() + ".xml"; + @io.swagger.v3.oas.annotations.Operation(summary = "Generate an OGC filter", + description = "From a JSON filter, return an OGC filter expression.") + @PostMapping(value = "/filter", + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_XML_VALUE) + @ResponseStatus(value = HttpStatus.CREATED) + public + @ResponseBody + String buildFilter( + @Parameter(description = "The filters in JSON", + required = true) + @RequestParam("filters") String filters) throws JSONException, IOException { - return url; - } catch (Exception e) { - throw e; - } + Filter customFilter = SLDUtil.generateCustomFilter(new JSONObject(filters)); + return SLDUtil.encodeFilter(customFilter); } @io.swagger.v3.oas.annotations.Operation(summary = "Download a SLD", description = "") - @RequestMapping(value = "/sld/{id:\\d+}.{extension}", - method = RequestMethod.GET, + @GetMapping(value = "/sld/{id:\\d+}.{extension}", produces = MediaType.APPLICATION_XML_VALUE) @ResponseStatus(value = HttpStatus.OK) public void downloadSLD( @@ -228,21 +216,26 @@ public void downloadSLD( @PathVariable("extension") String extension, HttpServletResponse response) throws ResourceNotFoundException { try { - TextFile file = fileRepository.findById(id).get(); + Optional file = fileRepository.findById(id); // Validate that the file id found matches the extension - if (!StringUtils.isEmpty(extension)) { + if (file.isPresent() && !StringUtils.isEmpty(extension)) { Path path = new File(id + "." + extension).toPath(); String extensionMimeType = Files.probeContentType(path); - if (!file.getMimeType().equals(extensionMimeType)) { + if (!file.get().getMimeType().equals(extensionMimeType)) { throw new ResourceNotFoundException(String.format( "SLD '%s' with extension '%s' not found. ", id, extension)); } + + response.setContentType(file.get().getMimeType() + "; charset=utf-8"); + PrintWriter writer = response.getWriter(); + writer.write(file.get().getContent()); + writer.flush(); + } else { + throw new ResourceNotFoundException(String.format( + "SLD '%s' not found. ", + id)); } - response.setContentType(file.getMimeType() + "; charset=utf-8"); - PrintWriter writer = response.getWriter(); - writer.write(file.getContent()); - writer.flush(); } catch (Exception e) { throw new ResourceNotFoundException(String.format( "SLD '%s' not found. ", diff --git a/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java b/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java index 14af8f64d3c..0ed2fce83f4 100644 --- a/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java +++ b/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java @@ -39,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -107,7 +108,9 @@ public List getCustomTranslations( @RequestParam(required = false) final List type, ServletRequest request ) throws Exception { - return translationsRepository.findAll(); + final Sort sort = SortUtils.createSort(Translations_.fieldName); + + return translationsRepository.findAll(sort); } @io.swagger.v3.oas.annotations.Operation( diff --git a/slave/pom.xml b/slave/pom.xml index c56016fdac4..f573f43604f 100644 --- a/slave/pom.xml +++ b/slave/pom.xml @@ -28,7 +28,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT GeoNetwork Slave diff --git a/software_development/ECLIPSE.md b/software_development/ECLIPSE.md index 611bba94f74..d82032dbf4d 100644 --- a/software_development/ECLIPSE.md +++ b/software_development/ECLIPSE.md @@ -14,7 +14,7 @@ In order to import the source code, follow instructions below: 2. In new dialog Select **Maven** > **Existing Maven Projects** 3. Press *Next* - ![Import existing projects into Eclipse](../eclipse-import-existing-projects.png) + ![Import existing projects into Eclipse](eclipse-import-existing-projects.png) 4. In **Select root directory** field enter the location of your source code: diff --git a/eclipse-import-existing-projects.png b/software_development/eclipse-import-existing-projects.png similarity index 100% rename from eclipse-import-existing-projects.png rename to software_development/eclipse-import-existing-projects.png diff --git a/web-ui/pom.xml b/web-ui/pom.xml index 82eed9e5ab6..e5bce69c474 100644 --- a/web-ui/pom.xml +++ b/web-ui/pom.xml @@ -30,7 +30,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT org.geonetwork-opensource diff --git a/web-ui/src/main/resources/WEB-INF/classes/web-ui-wro-sources.xml b/web-ui/src/main/resources/WEB-INF/classes/web-ui-wro-sources.xml index d1fdf7217a2..8d7932387c3 100644 --- a/web-ui/src/main/resources/WEB-INF/classes/web-ui-wro-sources.xml +++ b/web-ui/src/main/resources/WEB-INF/classes/web-ui-wro-sources.xml @@ -81,6 +81,7 @@ + + -1; }, isOwned: function () { - return this.owner === "true"; + return this.owner === true; }, getOwnerId: function () { return this.ownerId; diff --git a/web-ui/src/main/resources/catalog/components/common/map/mapService.js b/web-ui/src/main/resources/catalog/components/common/map/mapService.js index 31c8624e7cd..9caacc5a25b 100644 --- a/web-ui/src/main/resources/catalog/components/common/map/mapService.js +++ b/web-ui/src/main/resources/catalog/components/common/map/mapService.js @@ -91,14 +91,14 @@ } }, - // As this is a authorized mapservice lets use the authization load function for loading the images/tiles + // As this is a authorized mapservice lets use the authorization load function for loading the images/tiles authorizationLoadFunction: function (tile, src) { var srcUrl = src; var mapservice = this.getMapservice(srcUrl); if (mapservice !== null) { // If we are using a proxy then adjust the url. if (mapservice.useProxy) { - // Proxy calls should have been handled withtout load function . So it this occures just log a warning and set the proxy url + // Proxy calls should have been handled without load function . So it this occurs just log a warning and set the proxy url console.log( "Warning: attempting to get Using authorized Map service to get tile/image via proxy - this should have already been handled." ); @@ -892,7 +892,46 @@ createOlWMS: function (map, layerParams, layerOptions) { var options = layerOptions || {}; - var loadFunction; + var convertGetMapRequestToPost = function (url, onLoadCallback) { + var p = url.split("?"); + var action = p[0]; + var params = p[1].split("&"); + var data = new FormData(); + params.map(function (p) { + var token = p.split("="); + data.append(token[0], decodeURIComponent(token[1])); + }); + $http + .post(action, data, { + responseType: "arraybuffer", + transformRequest: angular.identity, + headers: { "Content-Type": undefined } + }) + .then(onLoadCallback); + }; + + var loadFunction = function (imageTile, src) { + $http.head(src).then( + function (r) { + imageTile.getImage().src = src; + }, + function (r) { + if (r.status === 414) { + // Request URI too large, try POST + convertGetMapRequestToPost(src, function (response) { + var arrayBufferView = new Uint8Array(response.data); + var blob = new Blob([arrayBufferView], { type: "image/png" }); + var urlCreator = window.URL || window.webkitURL; + var imageUrl = urlCreator.createObjectURL(blob); + imageTile.getImage().src = imageUrl; + }); + } else { + // Other HEAD errors, default to OL image src set + imageTile.getImage().src = src; + } + } + ); + }; // If this is an authorized mapservice then we need to adjust the url or add auth headers // Only check http url and exclude any urls like data: which should not be changed. Also, there is not need to check prox urls. @@ -1184,6 +1223,7 @@ if (url.slice(-1) === "?") { url = url.substring(0, url.length - 1); } + var layer = this.createOlWMS(map, layerParam, { url: url, label: getCapLayer.Title || getCapLayer.Name, @@ -1219,11 +1259,21 @@ } } if (dimension.name == "time") { + var dimensionValues = []; + + var dimensionList = dimension.values.split(","); + + for (var i = 0; i < dimensionList.length; i++) { + var wmsTimeInterval = new WMSTimeInterval(dimensionList[i].trim()); + + dimensionValues = dimensionValues.concat( + wmsTimeInterval.getValues() + ); + } + layer.set("time", { units: dimension.units, - values: dimension.values.split(",").map(function (e) { - return e.trim(); - }) + values: dimensionValues }); if (dimension.default) { @@ -2497,4 +2547,81 @@ function (value) { return typeof value === "number" && isFinite(value) && Math.floor(value) === value; }; + + /** + * Parses a time interval with the following formats and creates a list of dates for the time interval. + * + * DATE + * DATE/DATE + * DATE/PERIOD + * DATE/DATE/PERIOD + * + * @param value time interval value + */ + function WMSTimeInterval(interval) { + this.interval = interval; + } + + WMSTimeInterval.prototype._isValidDate = function (value) { + return moment(value).isValid(); + }; + + WMSTimeInterval.prototype._isValidDuration = function (value) { + return moment.isDuration(moment.duration(value)); + }; + + WMSTimeInterval.prototype._processInterval = function (startDate, endDate, duration) { + var timeIntervalValues = []; + + var durationValue = moment.duration(duration); + timeIntervalValues.push(startDate); + + var nextValue = moment(startDate).add(durationValue).utc().format(); + + if (!endDate) { + timeIntervalValues.push(nextValue); + } else { + while (moment(nextValue).isBefore(moment(endDate))) { + timeIntervalValues.push(nextValue); + nextValue = moment(nextValue).add(durationValue).utc().format(); + } + } + + return timeIntervalValues; + }; + + WMSTimeInterval.prototype.getValues = function () { + var timeIntervalValues = []; + var intervalTokens = this.interval.split("/"); + + if (intervalTokens.length == 1 && this._isValidDate(this.interval)) { + timeIntervalValues.push(this.interval); + } else if (intervalTokens.length == 2) { + var isValidStartDate = this._isValidDate(intervalTokens[0]); + var isValidEndDate = this._isValidDate(intervalTokens[1]); + var isValidDuration = this._isValidDuration(intervalTokens[1]); + + if (isValidStartDate && isValidEndDate) { + // DATE/DATE + timeIntervalValues = intervalTokens; + } else if (isValidStartDate && isValidDuration) { + // DATE/PERIOD + timeIntervalValues = timeIntervalValues.concat( + this._processInterval(intervalTokens[0], undefined, intervalTokens[1]) + ); + } + } else if ( + intervalTokens.length == 3 && + this._isValidDate(intervalTokens[0]) && + this._isValidDate(intervalTokens[1]) && + this._isValidDuration(intervalTokens[2]) + ) { + // DATE/DATE/PERIOD + timeIntervalValues = timeIntervalValues.concat( + this._processInterval(intervalTokens[0], intervalTokens[1], intervalTokens[2]) + ); + } + + return timeIntervalValues; + }; })(); diff --git a/web-ui/src/main/resources/catalog/components/common/map/wmsQueue.js b/web-ui/src/main/resources/catalog/components/common/map/wmsQueue.js index 6f400e5f91b..99c0d6544d1 100644 --- a/web-ui/src/main/resources/catalog/components/common/map/wmsQueue.js +++ b/web-ui/src/main/resources/catalog/components/common/map/wmsQueue.js @@ -45,14 +45,6 @@ this.queue = queue; this.errors = errors; - var clear = function (map) { - var type = (map && map.get && map.get("type")) || "viewer"; - if (queue[type]) { - queue[type].queue.length = 0; - queue[type].errors.length = 0; - } - }; - var getMapType = function (map) { var type = (map && map.get && map.get("type")) || "viewer"; if (queue[type] === undefined) { @@ -133,6 +125,21 @@ }; return getLayerIndex(queue[getMapType(map)], layer) >= 0; }; + + /** + * + * @param {ol.Map} map + */ + this.clear = function (map) { + var type = (map && map.get && map.get("type")) || "viewer"; + if (queue[type]) { + queue[type] = []; + } + + if (errors[type]) { + errors[type] = []; + } + }; } ]); })(); diff --git a/web-ui/src/main/resources/catalog/components/common/openlayers/olMapDirective.js b/web-ui/src/main/resources/catalog/components/common/openlayers/olMapDirective.js index d3a831bd218..7a7f6ea10f0 100644 --- a/web-ui/src/main/resources/catalog/components/common/openlayers/olMapDirective.js +++ b/web-ui/src/main/resources/catalog/components/common/openlayers/olMapDirective.js @@ -58,7 +58,15 @@ var prop = attrs[attr]; var map = scope.$eval(prop); - map.setTarget(element[0]); + var target = element[0]; + + var resizeObserver = new ResizeObserver(function () { + map.updateSize(); + resizeObserver.unobserve(target); + }); + + map.setTarget(target); + resizeObserver.observe(target); } }; }; diff --git a/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js b/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js index deb8646dd5d..89bd0973dcf 100644 --- a/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js +++ b/web-ui/src/main/resources/catalog/components/edit/FieldsDirective.js @@ -368,7 +368,7 @@ tooltipsMode === "onhover" ? "hover" : isField ? "focus" : "click" }); - if (tooltipsMode === "" || tooltipsMode === "onfocus") { + if (tooltipsMode !== "onhover") { // Remove first the event, to avoid ending with multiple events // every time a new popup is displayed. $(document) diff --git a/web-ui/src/main/resources/catalog/components/edit/editorhelper/EditorHelperDirective.js b/web-ui/src/main/resources/catalog/components/edit/editorhelper/EditorHelperDirective.js index 5b0f2745a1d..30c9d43ff94 100644 --- a/web-ui/src/main/resources/catalog/components/edit/editorhelper/EditorHelperDirective.js +++ b/web-ui/src/main/resources/catalog/components/edit/editorhelper/EditorHelperDirective.js @@ -137,6 +137,15 @@ // Load the config from the textarea containing the helpers scope.config = angular.fromJson($("#" + scope.ref + "_config")[0].value); + if (scope.mode == "") { + scope.config.defaultSelected = { + "@value": "", + "#text": $translate.instant("recommendedValues"), + disabled: true + }; + } else { + scope.config.defaultSelected = {}; + } // If only one option, convert to an array if (!$.isArray(scope.config.option)) { @@ -146,6 +155,11 @@ scope.config.option = scope.config; } + if (scope.mode == "") { + // Add on top the recommended values option + scope.config.option.unshift(scope.config.defaultSelected); + } + // Add record formats if any scope.isProtocol = attrs.tooltip.indexOf && attrs.tooltip.indexOf("protocol|") !== -1; @@ -180,7 +194,8 @@ } // Set the initial value - scope.config.selected = {}; + scope.config.selected = scope.config.defaultSelected; + scope.config.value = field.type === "number" ? parseFloat(field.value) : field.value; scope.config.layout = diff --git a/web-ui/src/main/resources/catalog/components/edit/editorhelper/partials/editorhelper.html b/web-ui/src/main/resources/catalog/components/edit/editorhelper/partials/editorhelper.html index dfddebdb478..c5c5a43561b 100644 --- a/web-ui/src/main/resources/catalog/components/edit/editorhelper/partials/editorhelper.html +++ b/web-ui/src/main/resources/catalog/components/edit/editorhelper/partials/editorhelper.html @@ -17,7 +17,7 @@ type="radio" data-ng-click="select(o)" name="ignore_{{ref}}" - data-ng-checked="o['@value'] === config.value" + data-ng-checked="o['@value'] == config.value" /> {{o['#text']}} @@ -70,9 +70,7 @@ class="form-control" data-ng-model="config.selected" data-ng-options="o as o['#text'] disable when o.disabled for o in config.option" - > - - + >
    diff --git a/web-ui/src/main/resources/catalog/components/edit/onlinesrc/partials/addOnlinesrc.html b/web-ui/src/main/resources/catalog/components/edit/onlinesrc/partials/addOnlinesrc.html index 386af08f24b..405682e079b 100644 --- a/web-ui/src/main/resources/catalog/components/edit/onlinesrc/partials/addOnlinesrc.html +++ b/web-ui/src/main/resources/catalog/components/edit/onlinesrc/partials/addOnlinesrc.html @@ -396,11 +396,12 @@
    diff --git a/web-ui/src/main/resources/catalog/components/history/GnHistoryService.js b/web-ui/src/main/resources/catalog/components/history/GnHistoryService.js index 5dd39c83b85..adb333c1459 100644 --- a/web-ui/src/main/resources/catalog/components/history/GnHistoryService.js +++ b/web-ui/src/main/resources/catalog/components/history/GnHistoryService.js @@ -89,7 +89,7 @@ filters.push("owner=" + filter.ownerFilter.id); } if (filter.recordFilter) { - filters.push("record=" + filter.recordFilter); + filters.push("recordIdentifier=" + filter.recordFilter); } if (filter.uuid) { filters.push("uuid=" + filter.uuid); diff --git a/web-ui/src/main/resources/catalog/components/index/partials/datafilterview.html b/web-ui/src/main/resources/catalog/components/index/partials/datafilterview.html index a18337ef07b..62a4ed34705 100644 --- a/web-ui/src/main/resources/catalog/components/index/partials/datafilterview.html +++ b/web-ui/src/main/resources/catalog/components/index/partials/datafilterview.html @@ -12,7 +12,13 @@
    -
    +
    diff --git a/web-ui/src/main/resources/catalog/components/metadataactions/partials/relatedEditorList.html b/web-ui/src/main/resources/catalog/components/metadataactions/partials/relatedEditorList.html index 900e85e49e0..62432af53df 100644 --- a/web-ui/src/main/resources/catalog/components/metadataactions/partials/relatedEditorList.html +++ b/web-ui/src/main/resources/catalog/components/metadataactions/partials/relatedEditorList.html @@ -22,10 +22,10 @@ data-ng-href="{{record.remoteUrl}}" rel="noopener noreferrer" target="_blank" - title="{{record.resourceTitle}}" + title="{{record.resourceTitle || record.uuid}}" > - {{record.resourceTitle}} + {{record.resourceTitle || record.uuid}} {{page.label}}
    {{c.role | translate}}
    -
    - - {{c.organisation}} - - {{c.organisation}} -
    +
    {{c.organisation}}
    + + + {{c.organisation}} + +
    -
    {{c.address}}
    +
    + + {{c.address}} +
    call {{c.phone}} @@ -64,12 +67,7 @@ data-org-key="c.email | getMailDomain" />
    -
    - - {{c.organisation}} - - {{c.organisation}} -
    +
    {{c.organisation}}
    @@ -80,10 +78,18 @@
    + + + {{c.organisation}} + +
    -
    {{c.address}}
    +
    + + {{c.address}} +
    call {{c.phone}} @@ -118,12 +124,7 @@ />
    -
    - - {{c.organisation}} - - {{c.organisation}} -
    +
    {{c.organisation}}
    {{r | translate}}
    @@ -133,10 +134,18 @@
    + + + {{c.organisation}} + +
    -
    {{c.address}}
    +
    + + {{c.address}} +
    call {{c.phone}} diff --git a/web-ui/src/main/resources/catalog/components/search/searchmanager/LocationService.js b/web-ui/src/main/resources/catalog/components/search/searchmanager/LocationService.js index 6e320914da0..c04e9b9ff4b 100644 --- a/web-ui/src/main/resources/catalog/components/search/searchmanager/LocationService.js +++ b/web-ui/src/main/resources/catalog/components/search/searchmanager/LocationService.js @@ -99,13 +99,13 @@ this.getFormatterPath = function (defaultFormatter) { var tokens = $location.path().split("/"); if (tokens.length > 2 && tokens[3] === "formatters") { - return "../api/records/" + $location.url().split(/^metadraf|metadata\//)[1]; + return "../api/records/" + $location.url().split(/^\/(metadraf|metadata)\//)[2]; } else if (tokens.length > 2 && tokens[3] === "main") { return undefined; // Angular view } else if (defaultFormatter) { return ( "../api/records/" + - $location.url().split(/^metadraf|metadata\//)[1] + + $location.url().split(/^\/(metadraf|metadata)\//)[2] + defaultFormatter ); } else { diff --git a/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html b/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html index c12139fbc25..cfc1025bcca 100644 --- a/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html +++ b/web-ui/src/main/resources/catalog/components/viewer/layermanager/partials/layermanageritem.html @@ -177,7 +177,9 @@
    Attribution
    -1; - }; - - /** - * Check if the WFS url provided return a response. - */ - scope.checkWFSUrl = function () { - if (scope.url && scope.url != "") { - return gnWfsService - .getCapabilities(gnGlobalSettings.getNonProxifiedUrl(scope.url)) - .then( - function (capabilities) { - scope.isInitialized = true; - scope.isWfsAvailable = true; - scope.capabilities = capabilities; - scope.featureType = gnWfsService.getTypeName( - capabilities, - scope.typename - ); - if (scope.featureType) { - scope.formats = gnWfsService.getOutputFormat(capabilities); - } - }, - function (r) { - console.warn(r); - scope.isInitialized = true; - scope.isWfsAvailable = false; - } - ); - } - }; - - scope.downloadFormatChange = function (o) { - var df = o.downloadFormat; - if (df) { - scope.download(df.split("#")[0], df.split("#")[1] == "true"); - } - }; - - if (!scope.initOnDemand) { - scope.init(); - } - } - }; - } - ]); - - module.directive("gnNoMapWfsDownload", [ "gnWfsService", function (gnWfsService) { return { restrict: "A", scope: {}, - templateUrl: "../../catalog/components/" + "viewer/wfs/partials/download.html", - link: function (scope, element, attrs, ctrls) { + templateUrl: "../../catalog/components/viewer/wfs/partials/download.html", + link: function (scope, element, attrs) { scope.initOnDemand = attrs["initOnDemand"] == "true" || false; scope.isWfsAvailable = false; scope.isInitialized = false; @@ -180,26 +41,16 @@ scope.init = function () { // Get WFS URL from attrs or try by substituting WFS in WMS URLs. - scope.url = attrs["url"]; - scope.typename = attrs["typename"]; + scope.url = attrs["url"].replace(/wms/i, "wfs"); + scope.typenames = attrs["typename"].split(","); scope.formats = []; scope.checkWFSUrl(); }; - // TODO: Choose a projection ? - scope.download = function (format, mapExtentOnly) { - scope.downloadFeatureType( - format, - scope.featureType, - scope.typename, - mapExtentOnly - ); - }; - scope.downloadFeatureType = function ( - format, featureType, featureTypeName, + format, mapExtentOnly ) { if (mapExtentOnly) { @@ -242,10 +93,13 @@ scope.isInitialized = true; scope.isWfsAvailable = true; scope.capabilities = capabilities; - scope.featureType = gnWfsService.getTypeName( - capabilities, - scope.typename - ); + scope.featureTypes = []; + scope.typenames.forEach(function (typename) { + var type = gnWfsService.getTypeName(capabilities, typename); + if (type) { + scope.featureTypes.push({ label: typename, type: type }); + } + }); scope.formats = gnWfsService.getOutputFormat(capabilities); }, function (r) { diff --git a/web-ui/src/main/resources/catalog/components/viewer/wfs/partials/download.html b/web-ui/src/main/resources/catalog/components/viewer/wfs/partials/download.html index 4e3de14b318..57a773944d5 100644 --- a/web-ui/src/main/resources/catalog/components/viewer/wfs/partials/download.html +++ b/web-ui/src/main/resources/catalog/components/viewer/wfs/partials/download.html @@ -9,7 +9,7 @@
    -

    +

    wfsDownloadDataInstruction

    @@ -17,7 +17,7 @@ {{'Unable_to_connect_to_service'|translate}}

    -
    +
    - - +
    + + +
    -
    +
    wfsNoOutputFormats
    -
    -
    -
    -
    - wfsTypenameNotAvailable -
    -
    +
    -
    - - -   - + +
    + -
    - + + + + + + diff --git a/workers/camelPeriodicProducer/pom.xml b/workers/camelPeriodicProducer/pom.xml index a894771f5b7..4a42762ebef 100644 --- a/workers/camelPeriodicProducer/pom.xml +++ b/workers/camelPeriodicProducer/pom.xml @@ -5,7 +5,7 @@ gn-workers org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerControllerTest.java b/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerControllerTest.java index b035fb0d8d5..259b506a59a 100644 --- a/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerControllerTest.java +++ b/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerControllerTest.java @@ -31,6 +31,7 @@ import org.fao.geonet.harvester.wfsfeatures.model.WFSHarvesterParameter; import org.fao.geonet.repository.MessageProducerRepository; import org.fao.geonet.repository.MetadataRepository; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,8 +76,6 @@ public class MessageProducerControllerTest { @Autowired MetadataSavedQueryApi savedQueryApi; - private QuartzComponent quartzComponent; - @Autowired MessageProducerFactory messageProducerFactory; @@ -84,9 +83,11 @@ public class MessageProducerControllerTest { public void init() throws Exception { testCamelNetwork.getContext().start(); messageProducerFactory.routeBuilder = testCamelNetwork; - quartzComponent = new QuartzComponent(testCamelNetwork.getContext()); - messageProducerFactory.quartzComponent = quartzComponent; - quartzComponent.start(); + } + + @After + public void destroy() { + messageProducerRepository.deleteAll(); } @Test diff --git a/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerTest.java b/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerTest.java index 220928078ba..c219c0a43c4 100644 --- a/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerTest.java +++ b/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/MessageProducerTest.java @@ -27,6 +27,7 @@ import org.apache.camel.component.quartz2.QuartzComponent; import org.apache.camel.component.quartz2.QuartzEndpoint; import org.fao.geonet.kernel.setting.SettingManager; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,19 +73,10 @@ public class MessageProducerTest extends AbstractJUnit4SpringContextTests { @Autowired MessageProducerFactory toTest; - private QuartzComponent quartzComponent; - - @Before - public void init() throws Exception { - quartzComponent = new QuartzComponent(testCamelNetwork.getContext()); - quartzComponent.start(); - } - @Test public void registerAndStart() throws Exception { testCamelNetwork.getContext().start(); toTest.routeBuilder = testCamelNetwork; - toTest.quartzComponent = quartzComponent; TestMessage testMessage = new TestMessage("testMsg1"); MessageProducer messageProducer1 = new MessageProducer<>(); @@ -139,11 +131,10 @@ public void registerAndStart() throws Exception { public void registerAndStartWithoutCronExpression() throws Exception { testCamelNetwork.getContext().start(); toTest.routeBuilder = testCamelNetwork; - toTest.quartzComponent = quartzComponent; TestMessage testMessage = new TestMessage("testMsg1"); MessageProducer messageProducer = new MessageProducer<>(); - messageProducer.setId(1L); + messageProducer.setId(3L); messageProducer.setTarget(testCamelNetwork.getMessageConsumer().getUri()); messageProducer.setMessage(testMessage); messageProducer.setCronExpession(null); @@ -153,16 +144,16 @@ public void registerAndStartWithoutCronExpression() throws Exception { .filter(x -> x.getEndpointKey().compareTo( "quartz2://" + settingManager.getSiteId() + "-" + messageProducer.getId()) == 0).findFirst().get(); - CronTrigger trigger = (CronTrigger) quartzComponent.getScheduler().getTrigger(endpoint.getTriggerKey()); + CronTrigger trigger = (CronTrigger) toTest.quartzComponent.getScheduler().getTrigger(endpoint.getTriggerKey()); assertEquals(NEVER, trigger.getCronExpression()); messageProducer.setCronExpession(EVERY_SECOND); toTest.changeMessageAndReschedule(messageProducer); - trigger = (CronTrigger) quartzComponent.getScheduler().getTrigger(endpoint.getTriggerKey()); + trigger = (CronTrigger) toTest.quartzComponent.getScheduler().getTrigger(endpoint.getTriggerKey()); assertEquals(EVERY_SECOND, trigger.getCronExpression()); - toTest.destroy(1L); + toTest.destroy(3L); } diff --git a/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/TestCamelNetwork.java b/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/TestCamelNetwork.java index 54703be7901..c456f957e0b 100644 --- a/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/TestCamelNetwork.java +++ b/workers/camelPeriodicProducer/src/test/java/org/fao/geonet/camelPeriodicProducer/TestCamelNetwork.java @@ -24,18 +24,30 @@ package org.fao.geonet.camelPeriodicProducer; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.quartz2.QuartzComponent; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; public class TestCamelNetwork extends RouteBuilder { private MessageProducerTest.MessageConsumer messageConsumer; private MessageProducerControllerTest.MessageConsumer wfsHarvesterParamConsumer; + @Autowired + public QuartzComponent quartzComponent; public TestCamelNetwork() { messageConsumer = new MessageProducerTest.MessageConsumer("direct:consumer"); wfsHarvesterParamConsumer = new MessageProducerControllerTest.MessageConsumer("direct:wfsHravesterParamConsumer"); } + @PostConstruct + public void init() throws Exception { + quartzComponent.start(); + quartzComponent.setCamelContext(this.getContext()); + } + public MessageProducerTest.MessageConsumer getMessageConsumer() { return messageConsumer; } diff --git a/workers/pom.xml b/workers/pom.xml index a61e48932cf..eff05b4a485 100644 --- a/workers/pom.xml +++ b/workers/pom.xml @@ -28,7 +28,7 @@ geonetwork org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/workers/wfsfeature-harvester/pom.xml b/workers/wfsfeature-harvester/pom.xml index 69db134c239..1a88a53ae8a 100644 --- a/workers/wfsfeature-harvester/pom.xml +++ b/workers/wfsfeature-harvester/pom.xml @@ -28,7 +28,7 @@ gn-workers org.geonetwork-opensource - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT 4.0.0 diff --git a/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/EsWFSFeatureIndexer.java b/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/EsWFSFeatureIndexer.java index a50170324a1..9c7cf432909 100644 --- a/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/EsWFSFeatureIndexer.java +++ b/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/EsWFSFeatureIndexer.java @@ -252,7 +252,9 @@ public CompletableFuture indexFeatures(Exchange exchange) throws Exception String url = state.getParameters().getUrl(); String typeName = state.getParameters().getTypeName(); - String resolvedTypeName = state.getResolvedTypeName(); + List resolvedTypeNames = state.getResolvedTypeNames(); + String strategyId = state.getStrategyId(); + Map tokenizedFields = state.getParameters().getTokenizedFields(); WFSDataStore wfs = state.getWfsDatastore(); Map featureAttributes = state.getFields(); @@ -284,155 +286,168 @@ public CompletableFuture indexFeatures(Exchange exchange) throws Exception BulkResutHandler brh = new AsyncBulkResutHandler(phaser, typeName, url, nbOfFeatures, report, state.getParameters().getMetadataUuid()); long begin = System.currentTimeMillis(); -// FeatureIterator features = wfs.getFeatureSource(typeName).getFeatures(query).features(); - SimpleFeatureCollection fc = wfs.getFeatureSource(resolvedTypeName).getFeatures(); - ReprojectingFeatureCollection rfc = new ReprojectingFeatureCollection(fc, CRS.decode("urn:ogc:def:crs:OGC:1.3:CRS84")); + String epsg = "urn:ogc:def:crs:OGC:1.3:CRS84"; + // TODO: With QGIS server, this can be required + // for proper coordinate ordering. + // Not 100% sure if it always apply or related only + // to Ifremer setup + // if (strategyId.contains("qgis")) { + // epsg = "EPSG:4326"; + // } - FeatureIterator features = rfc.features(); + for (String featureType : resolvedTypeNames) { + SimpleFeatureCollection fc = wfs.getFeatureSource(featureType).getFeatures(); - try { - while (features.hasNext()) { - String featurePointer = String.format("%s#%s", typeName, nbOfFeatures); - try { - SimpleFeature feature = null; + ReprojectingFeatureCollection rfc = new ReprojectingFeatureCollection(fc, CRS.decode(epsg)); + + FeatureIterator features = rfc.features(); + + try { + while (features.hasNext()) { + String featurePointer = String.format("%s#%s", featureType, nbOfFeatures); try { - feature = features.next(); - featurePointer = String.format("%s/id:%s", featurePointer, feature.getID()); - } catch (Exception e) { - if (e.getCause() instanceof IOException - || e.getCause() instanceof DataSourceException) { + SimpleFeature feature = null; + try { + feature = features.next(); + featurePointer = String.format("%s/id:%s", featurePointer, feature.getID()); + } catch (Exception e) { + if (e.getCause() instanceof IOException + || e.getCause() instanceof DataSourceException) { + String msg = String.format( + "Error while getting feature %s. Exception is: %s. Harvesting task will be stopped. This is probably a problem with the data source or some network related issues. Try to relaunch it later.", + featurePointer, + e.getMessage() + ); + LOGGER.warn(msg); + report.put("error_ss", msg); + break; + } String msg = String.format( - "Error while getting feature %s. Exception is: %s. Harvesting task will be stopped. This is probably a problem with the data source or some network related issues. Try to relaunch it later.", - featurePointer, - e.getMessage() + "Error on reading %s. Exception is: %s", + featurePointer, e.getMessage() ); LOGGER.warn(msg); report.put("error_ss", msg); - break; + continue; } - String msg = String.format( - "Error on reading %s. Exception is: %s", - featurePointer, e.getMessage() - ); - LOGGER.warn(msg); - report.put("error_ss", msg); - continue; - } - ObjectNode rootNode = protoNode.deepCopy(); - titleResolver.setTitle(rootNode, feature); - - for (String attributeName : featureAttributes.keySet()) { - Object attributeValue = feature.getAttribute(attributeName); - if (attributeValue == null) { - - } else if (tokenizedFields != null && tokenizedFields.get(attributeName) != null) { - String rawValue = (String) attributeValue; - String value = rawValue.startsWith(CDATA_START) ? - rawValue.replaceFirst(CDATA_START_REGEX, "").substring(0, rawValue.length() - CDATA_END.length() - CDATA_START.length()) : - rawValue; - - String separator = tokenizedFields.get(attributeName); - String[] tokens = value.split(separator); - ArrayNode arrayNode = jacksonMapper.createArrayNode(); - for (String token : tokens) { - arrayNode.add(token.trim()); - } - rootNode.putPOJO(getDocumentFieldName(attributeName), arrayNode); - } else if (getDocumentFieldName(attributeName).equals("geom")) { - Geometry geom = (Geometry) feature.getDefaultGeometry(); - - if (applyPrecisionModel) { - if (geom.isValid()) { - PrecisionModel precisionModel = new PrecisionModel(Math.pow(10, numberOfDecimals - 1)); - geom = GeometryPrecisionReducer.reduce(geom, precisionModel); - // numberOfDecimals is equal to - // precisionModel.getMaximumSignificantDigits() + ObjectNode rootNode = protoNode.deepCopy(); + titleResolver.setTitle(rootNode, feature); + rootNode.put("featureType", featureType); + + for (String attributeName : featureAttributes.keySet()) { + Object attributeValue = feature.getAttribute(attributeName); + if (attributeValue == null) { + + } else if (tokenizedFields != null && tokenizedFields.get(attributeName) != null) { + String rawValue = (String) attributeValue; + String value = rawValue.startsWith(CDATA_START) ? + rawValue.replaceFirst(CDATA_START_REGEX, "").substring(0, rawValue.length() - CDATA_END.length() - CDATA_START.length()) : + rawValue; + + String separator = tokenizedFields.get(attributeName); + String[] tokens = value.split(separator); + ArrayNode arrayNode = jacksonMapper.createArrayNode(); + for (String token : tokens) { + arrayNode.add(token.trim()); + } + rootNode.putPOJO(getDocumentFieldName(attributeName), arrayNode); + } else if (getDocumentFieldName(attributeName).equals("geom")) { + Geometry geom = (Geometry) feature.getDefaultGeometry(); + + if (applyPrecisionModel) { + if (geom.isValid()) { + PrecisionModel precisionModel = new PrecisionModel(Math.pow(10, numberOfDecimals - 1)); + geom = GeometryPrecisionReducer.reduce(geom, precisionModel); + // numberOfDecimals is equal to + // precisionModel.getMaximumSignificantDigits() + } else { + String msg = String.format( + "Feature %s: Cannot apply precision reducer on invalid geometry. Check the geometry validity. The feature will be indexed but with no geometry.", + featurePointer); + LOGGER.warn(msg); + report.put("error_ss", msg); + break; + } + } + + // An issue here is that GeometryJSON conversion may over simplify + // the geometry by truncating coordinates based on numberOfDecimals + // which on default constructor is set to 4. This may lead to + // invalid geometry and Elasticsearch will fail parsing the GeoJSON + // with the following type of error: + // Caused by: org.locationtech.spatial4j.exception.InvalidShapeException: + // Provided shape has duplicate + // consecutive coordinates at: (-3.9997, 48.7463, NaN) + // + // To avoid this, it may be relevant to apply the reduction model + // preserving topology. + String gjson = new GeometryJSON(numberOfDecimals).toString(geom); + + JsonNode jsonNode = jacksonMapper.readTree(gjson.getBytes(StandardCharsets.UTF_8)); + rootNode.set(getDocumentFieldName(attributeName), jsonNode); + + boolean isPoint = geom instanceof Point; + if (isPoint) { + Coordinate point = geom.getCoordinate(); + rootNode.put("location", String.format("%s,%s", point.y, point.x)); } else { + report.setPointOnlyForGeomsFalse(); + } + + // Populate bbox coordinates to be able to compute + // global bbox of search results + final BoundingBox bbox = feature.getBounds(); + rootNode.put("bbox_xmin", bbox.getMinX()); + rootNode.put("bbox_ymin", bbox.getMinY()); + rootNode.put("bbox_xmax", bbox.getMaxX()); + rootNode.put("bbox_ymax", bbox.getMaxY()); + } else if (attributeValue instanceof Instant) { + try { + Position position = ((DefaultInstant) attributeValue).getPosition(); + + if (position != null && position.getDate() != null) { + rootNode.put(getDocumentFieldName(attributeName), + position.getDate().toInstant().toString()); + } + } catch (Exception instantException) { String msg = String.format( - "Feature %s: Cannot apply precision reducer on invalid geometry. Check the geometry validity. The feature will be indexed but with no geometry.", - featurePointer); + "Feature %s: Cannot read attribute %s, value %s. Exception is: %s", + featurePointer, attributeName, attributeValue, instantException.getMessage()); LOGGER.warn(msg); report.put("error_ss", msg); - break; } - } - - // An issue here is that GeometryJSON conversion may over simplify - // the geometry by truncating coordinates based on numberOfDecimals - // which on default constructor is set to 4. This may lead to - // invalid geometry and Elasticsearch will fail parsing the GeoJSON - // with the following type of error: - // Caused by: org.locationtech.spatial4j.exception.InvalidShapeException: - // Provided shape has duplicate - // consecutive coordinates at: (-3.9997, 48.7463, NaN) - // - // To avoid this, it may be relevant to apply the reduction model - // preserving topology. - String gjson = new GeometryJSON(numberOfDecimals).toString(geom); - - JsonNode jsonNode = jacksonMapper.readTree(gjson.getBytes(StandardCharsets.UTF_8)); - rootNode.put(getDocumentFieldName(attributeName), jsonNode); - - boolean isPoint = geom instanceof Point; - if (isPoint) { - Coordinate point = geom.getCoordinate(); - rootNode.put("location", String.format("%s,%s", point.y, point.x)); } else { - report.setPointOnlyForGeomsFalse(); - } + String value = attributeValue.toString(); + rootNode.put(getDocumentFieldName(attributeName), + value.startsWith(CDATA_START) ? + value.replaceFirst(CDATA_START_REGEX, "").substring(0, value.length() - CDATA_END.length() - CDATA_START.length()) : + value - // Populate bbox coordinates to be able to compute - // global bbox of search results - final BoundingBox bbox = feature.getBounds(); - rootNode.put("bbox_xmin", bbox.getMinX()); - rootNode.put("bbox_ymin", bbox.getMinY()); - rootNode.put("bbox_xmax", bbox.getMaxX()); - rootNode.put("bbox_ymax", bbox.getMaxY()); - } else if (attributeValue instanceof Instant) { - try { - Position position = ((DefaultInstant) attributeValue).getPosition(); - if (position != null && position.getDate() != null) { - rootNode.put(getDocumentFieldName(attributeName), - position.getDate().toInstant().toString()); - } - } catch (Exception instantException) { - String msg = String.format( - "Feature %s: Cannot read attribute %s, value %s. Exception is: %s", - featurePointer, attributeName, attributeValue, instantException.getMessage()); - LOGGER.warn(msg); - report.put("error_ss", msg); + ); } - } else { - String value = attributeValue.toString(); - rootNode.put(getDocumentFieldName(attributeName), - value.startsWith(CDATA_START) ? - value.replaceFirst(CDATA_START_REGEX, "").substring(0, value.length() - CDATA_END.length() - CDATA_START.length()) : - value - - ); } - } - nbOfFeatures++; - brh.addAction(rootNode, feature); + nbOfFeatures++; + brh.addAction(rootNode, feature); - } catch (Exception ex) { - String msg = String.format( - "Feature %s: Error is: %s", - featurePointer, ex.getMessage() - ); - LOGGER.warn(msg); - report.put("error_ss", msg); - } + } catch (Exception ex) { + String msg = String.format( + "Feature %s: Error is: %s", + featurePointer, ex.getMessage() + ); + LOGGER.warn(msg); + report.put("error_ss", msg); + } - if (brh.getBulkSize() >= featureCommitInterval) { - brh.launchBulk(client); - brh = new AsyncBulkResutHandler(phaser, typeName, url, nbOfFeatures, report, state.getParameters().getMetadataUuid()); + if (brh.getBulkSize() >= featureCommitInterval) { + brh.launchBulk(client); + brh = new AsyncBulkResutHandler(phaser, typeName, url, nbOfFeatures, report, state.getParameters().getMetadataUuid()); + } } + } finally { + features.close(); } - } finally { - features.close(); } if (brh.getBulkSize() > 0) { @@ -707,7 +722,7 @@ private void initFeatureAttributeToDocumentFieldNamesMapping(Map if (attributeType.equals("geometry")) { featureAttributeToDocumentFieldNames.put(attributeName, "geom"); } else { - boolean isTree = treeFields != null ? treeFields.contains(attributeName) : false; + boolean isTree = treeFields != null && treeFields.contains(attributeName); featureAttributeToDocumentFieldNames.put( attributeName, String.join("", diff --git a/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSDataStoreWithStrategyInvestigator.java b/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSDataStoreWithStrategyInvestigator.java index 2555c81f167..4849f8a7fa2 100644 --- a/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSDataStoreWithStrategyInvestigator.java +++ b/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSDataStoreWithStrategyInvestigator.java @@ -23,12 +23,12 @@ package org.fao.geonet.harvester.wfsfeatures.worker; -import org.geotools.http.HTTPClient; import org.geotools.data.wfs.WFSDataStore; import org.geotools.data.wfs.WFSDataStoreFactory; import org.geotools.data.wfs.internal.WFSConfig; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.type.FeatureTypeFactoryImpl; +import org.geotools.http.HTTPClient; import org.geotools.ows.ServiceException; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory; diff --git a/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSHarvesterExchangeState.java b/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSHarvesterExchangeState.java index d9fb8d1811d..bce6be6bd92 100644 --- a/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSHarvesterExchangeState.java +++ b/workers/wfsfeature-harvester/src/main/java/org/fao/geonet/harvester/wfsfeatures/worker/WFSHarvesterExchangeState.java @@ -51,7 +51,7 @@ public class WFSHarvesterExchangeState implements Serializable { private transient Logger logger = LogManager.getLogger(WFSHarvesterRouteBuilder.LOGGER_NAME); private transient Map fields = new LinkedHashMap<>(); private transient WFSDataStore wfsDatastore = null; - private String resolvedTypeName = null; + private List resolvedTypeNames = new ArrayList<>(); private String strategyId = null; @@ -88,8 +88,8 @@ public WFSDataStore getWfsDatastore() { return wfsDatastore; } - public String getResolvedTypeName() { - return resolvedTypeName; + public List getResolvedTypeNames() { + return resolvedTypeNames; } @@ -170,38 +170,50 @@ public void initDataStore() throws Exception { logger.info("Reading feature type '{}' schema structure.", parameters.getTypeName()); - SimpleFeatureType sft = null; - try { - sft = wfsDatastore.getSchema(parameters.getTypeName()); - resolvedTypeName = parameters.getTypeName(); - } catch (IOException e) { - String[] typeNames = wfsDatastore.getTypeNames(); - String typeNamesList = Arrays.stream(typeNames).collect(Collectors.joining(", ")); - logger.info(String.format( - "Type '%s' not found in data store. Available types are %s. Trying to found a match ignoring namespace.", - parameters.getTypeName(), - typeNamesList - )); - Optional typeFound = Arrays.stream(typeNames) - .filter(t -> t.endsWith(parameters.getTypeName())).findFirst(); - if (typeFound.isPresent()) { - resolvedTypeName = typeFound.get(); - logger.info("Found a type '{}'.", resolvedTypeName); - sft = wfsDatastore.getSchema(resolvedTypeName); - } else { - throw new NoSuchElementException(String.format( - "No type found for '%s' (with or without namespace match).", - parameters.getTypeName() + String typeSeparator = ","; + List featureTypeList = Arrays.asList(parameters.getTypeName().split(typeSeparator)); + + String[] datastoreTypeNames = wfsDatastore.getTypeNames(); + String datastoreTypeNamesList = Arrays.stream(datastoreTypeNames) + .collect(Collectors.joining(", ")); + + for (String type : featureTypeList) { + SimpleFeatureType sft = null; + String resolvedFeatureTypeName = null; + try { + sft = wfsDatastore.getSchema(type); + resolvedFeatureTypeName = type; + } catch (IOException e) { + logger.info(String.format( + "Type '%s' not found in data store. Available types are %s. Trying to found a match ignoring namespace.", + parameters.getTypeName(), + datastoreTypeNamesList )); + Optional typeFound = Arrays.stream(datastoreTypeNames) + .filter(t -> t.endsWith(type)).findFirst(); + if (typeFound.isPresent()) { + resolvedFeatureTypeName = typeFound.get(); + logger.info("Found a type '{}'.", resolvedFeatureTypeName); + sft = wfsDatastore.getSchema(resolvedFeatureTypeName); + } else { + throw new NoSuchElementException(String.format( + "No type found for '%s' (with or without namespace match).", + parameters.getTypeName() + )); + } } + if (sft != null) { + List attributesDesc = sft.getAttributeDescriptors(); + + for (AttributeDescriptor desc : attributesDesc) { + if (!fields.containsKey(desc.getName().getLocalPart())) { + fields.put(desc.getName().getLocalPart(), + OwsUtils.getTypeFromFeatureType(desc)); + } + } + } + resolvedTypeNames.add(resolvedFeatureTypeName); } - - List attributesDesc = sft.getAttributeDescriptors(); - - for (AttributeDescriptor desc : attributesDesc) { - fields.put(desc.getName().getLocalPart(), OwsUtils.getTypeFromFeatureType(desc)); - } - logger.info("Successfully analyzed {} attributes in schema.", fields.size()); } } diff --git a/wro4j/pom.xml b/wro4j/pom.xml index 336048c4601..b90fdf76cd5 100644 --- a/wro4j/pom.xml +++ b/wro4j/pom.xml @@ -7,7 +7,7 @@ org.geonetwork-opensource geonetwork - 4.2.5-SNAPSHOT + 4.2.6-SNAPSHOT