diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 00000000..ff65085d
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,68 @@
+#!/usr/bin/env groovy
+pipeline {
+ agent { label 'docker' }
+
+ environment {
+ MAVEN_IMAGE = 'maven:3.8-openjdk-17-slim'
+ DOCKER_BUILDKIT = '1'
+ PROJECT_TITLE = 'TCDI Admin'
+ REPO = 'registry.developmentgateway.org/'
+ }
+
+ stages {
+
+ stage('Compile') {
+ steps {
+ script {
+ def args = "-e JAVA_TOOL_OPTIONS=-Duser.home=$WORKSPACE_TMP"
+ withDockerContainer(image: env.MAVEN_IMAGE, args: args) {
+ // TODO: checkstyle
+ sh 'mvn -B clean package -DskipTests -Dcheckstyle.skip && mkdir forms/target/deps'
+ dir('forms/target/deps') {
+ sh 'jar -xf ../*.jar'
+ }
+ }
+ }
+ }
+ } // Compile
+
+ stage('Package & Publish') {
+ steps {
+ script {
+ def tag = ['main', 'master'].contains(env.BRANCH_NAME) ?
+ 'latest' :
+ env.BRANCH_NAME.replaceAll('[^\\p{Alnum}-_]', '_').toLowerCase()
+ withEnv(["TAG=$tag"]) {
+ def dc = 'docker-compose'
+ sh "$dc build admin && $dc push admin"
+ }
+ }
+ }
+ } // Package & Publish
+
+ stage('Deploy') {
+ when { branch 'develop' }
+ agent { label 'ansible' }
+ steps {
+ script {
+ def tag = ['main', 'master'].contains(env.BRANCH_NAME) ?
+ 'latest' :
+ env.BRANCH_NAME.replaceAll('[^\\p{Alnum}-_]', '_').toLowerCase()
+ ansiblePlaybook(
+ credentialsId: 'Deploy',
+ become: true,
+ playbook: 'deploy.yml',
+ skippedTags: 'provision',
+ extraVars: [
+ project_title: env.PROJECT_TITLE,
+ repo: env.REPO,
+ tag: tag,
+ pull: "true"
+ ]
+ )
+ } // script
+ } // steps
+ }
+
+ } // stages
+}
diff --git a/README.md b/README.md
index d61ed239..0fedd451 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# dg-toolkit
+# tcdi-admin based on dg-toolkit
[![Build Status](https://travis-ci.org/devgateway/dg-toolkit.svg?branch=master)](https://travis-ci.org/devgateway/dg-toolkit)
@@ -113,7 +113,7 @@ frolvlad/alpine-oraclejdk8 slim 00d8610f052e 2 weeks ago
The image can be started with
```
-$docker run -p 8090:8090 -t devgateway/toolkit/forms
+$docker run -p 8080:8080 -t devgateway/toolkit/forms
```
That's it, congrats!
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index 86fcf6c4..0a8e36a1 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -7,6 +7,8 @@
+
+
\ No newline at end of file
diff --git a/checkstyle.xml b/checkstyle.xml
index 7416a841..3d334b4b 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -10,12 +10,13 @@
-->
+
+
+
-
-
@@ -43,10 +44,7 @@
-
-
-
-
+
@@ -58,6 +56,7 @@
+
@@ -97,12 +96,12 @@
-
+
@@ -112,12 +111,11 @@
-
+
-
diff --git a/checkstyle/.gitignore b/checkstyle/.gitignore
deleted file mode 100644
index b83d2226..00000000
--- a/checkstyle/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target/
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml
deleted file mode 100644
index fc9c04af..00000000
--- a/checkstyle/pom.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- 4.0.0
-
- checkstyle
- org.devgateway.toolkit
- 1.0
-
-
- UTF-8
- 8
- 8
- 3.0.0
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- ${maven-checkstyle-plugin.version}
-
-
-
-
diff --git a/checkstyle/src/main/java/org/devgateway/toolkit/checks/CachableQueryAnnotationCheck.java b/checkstyle/src/main/java/org/devgateway/toolkit/checks/CachableQueryAnnotationCheck.java
deleted file mode 100644
index 1671cdfd..00000000
--- a/checkstyle/src/main/java/org/devgateway/toolkit/checks/CachableQueryAnnotationCheck.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.devgateway.toolkit.checks;
-
-import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
-import com.puppycrawl.tools.checkstyle.api.DetailAST;
-import com.puppycrawl.tools.checkstyle.api.TokenTypes;
-import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
-
-/**
- * Checks that the methods of all classes annotated with @CacheableHibernateQueryResult
- * are explicitly annotated to cache or not the query result.
- *
- * Anytime a new method is defined this will catch a potential missing caching definition.
- *
- * @author Nadejda Mandrescu
- */
-public class CachableQueryAnnotationCheck extends AbstractCheck {
- private static final String ERROR_MESSAGE =
- "@CacheableHibernateQueryResult must annotate its methods explicitly either with " +
- "@CacheHibernateQueryResult or @NoCacheHibernateQueryResult";
-
- @Override
- public int[] getDefaultTokens() {
- return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
- }
-
- @Override
- public void visitToken(DetailAST ast) {
- if (AnnotationUtility.containsAnnotation(ast, "CacheableHibernateQueryResult")) {
- DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
- DetailAST methodDef = objBlock.findFirstToken(TokenTypes.METHOD_DEF);
- while (methodDef != null) {
- if (methodDef.getType() == TokenTypes.METHOD_DEF) {
- if (!(AnnotationUtility.containsAnnotation(methodDef, "CacheHibernateQueryResult")
- || AnnotationUtility.containsAnnotation(methodDef, "NoCacheHibernateQueryResult"))) {
- log(methodDef.getLineNo(), ERROR_MESSAGE);
- }
- }
- methodDef = methodDef.getNextSibling();
- }
- }
- }
-}
diff --git a/checkstyle/src/main/resources/packagenames.xml b/checkstyle/src/main/resources/packagenames.xml
deleted file mode 100644
index bb173ad8..00000000
--- a/checkstyle/src/main/resources/packagenames.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/deploy.yml b/deploy.yml
new file mode 100644
index 00000000..48de8636
--- /dev/null
+++ b/deploy.yml
@@ -0,0 +1,84 @@
+---
+- name: Deploy
+ hosts:
+ - "{{ project_title | regex_replace('[^\\w_]', '_') | lower }}_{{ tag }}"
+ vars:
+ product: "{{ project_title | regex_replace('[^\\w_]', '_') | lower }}"
+ systemd_service:
+ Unit.Description: "{{ project_title }}"
+ Unit.After: docker.socket docker.service
+ Unit.BindsTo: docker.socket docker.service
+ Install.WantedBy: multi-user.target
+ Service.Type: exec
+ Service.ExecStart: /usr/bin/docker-compose --no-ansi up
+ Service.WorkingDirectory: "/opt/devgateway/{{ product }}"
+ Service.Environment: "TAG={{ tag }} REPO={{ repo }}"
+
+ tasks:
+
+ - name: Install packages
+ ansible.builtin.package:
+ name:
+ - firewalld
+ - docker-compose
+ tags:
+ - provision
+
+ - name: Open firewall ports
+ ansible.posix.firewalld:
+ service: http
+ state: enabled
+ permanent: true
+ immediate: true
+ zone: public
+ tags:
+ - provision
+
+ - name: Configure Systemd unit
+ community.general.ini_file:
+ path: /etc/systemd/system/{{ product }}.service
+ create: true
+ no_extra_spaces: true
+ section: "{{ item.key.split('.')[0] }}"
+ option: "{{ item.key.split('.')[1] }}"
+ value: "{{ item.value }}"
+ loop: "{{ systemd_service | dict2items }}"
+ loop_control:
+ label: "{{ item.key }}"
+ notify:
+ - Reload Systemd
+
+ - name: Install Compose file
+ ansible.builtin.copy:
+ src: docker-compose.yml
+ dest: "{{ systemd_service['Service.WorkingDirectory'] }}/"
+ notify:
+ - Restart stack
+
+ - name: Update images
+ ansible.builtin.command:
+ chdir: "{{ systemd_service['Service.WorkingDirectory'] }}"
+ cmd: /usr/bin/docker-compose pull --quiet
+ environment:
+ TAG: "{{ tag }}"
+ REPO: "{{ repo }}"
+ notify:
+ - Restart stack
+ when: pull | default(false)
+
+ - name: Enable Compose service
+ ansible.builtin.service:
+ name: "{{ product }}"
+ enabled: true
+ state: started
+
+ handlers:
+
+ - name: Reload Systemd
+ ansible.builtin.systemd:
+ daemon_reload: true
+
+ - name: Restart stack
+ ansible.builtin.service:
+ name: "{{ product }}"
+ state: restarted
diff --git a/dev_services.bat b/dev_services.bat
new file mode 100644
index 00000000..e20c201f
--- /dev/null
+++ b/dev_services.bat
@@ -0,0 +1,3 @@
+set TAG=staging
+
+docker-compose up postgres admin
\ No newline at end of file
diff --git a/dev_services.sh b/dev_services.sh
new file mode 100644
index 00000000..cee88436
--- /dev/null
+++ b/dev_services.sh
@@ -0,0 +1,2 @@
+set TAG=staging
+docker-compose up postgres admin
\ No newline at end of file
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 00000000..58ac7b02
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+
+ PROP_FILE="tcdi-admin.properties"
+ echo "Writing to $PROP_FILE:"
+
+ PROPERTIES="$(cat <<-EOF
+ server.port
+ spring.application.name
+ spring.liquibase.enabled
+ spring.datasource.jdbc-url
+ spring.datasource.url
+ spring.mail.host
+ spring.jpa.hibernate.ddl-auto
+ EOF
+ )"
+
+ env
+
+ echo "$PROPERTIES" | while IFS=read PROPERTY; do
+ VAR_NAME="$(echo "$PROPERTY" | tr '[:lower:].-' '[:upper:]__')"
+ eval VALUE="\$$VAR_NAME"
+ if [ -n "$VALUE" ]; then
+ echo "$PROPERTY=$VALUE"
+ fi
+ done | tee -a "$PROP_FILE"
+
+ JAR="tcdi-admin-forms-0.0.1-SNAPSHOT.jar"
+ JAVA_OPTS="-Dspring.config.location=file://$PROP_FILE"
+ #exec /bin/sh -c "java -jar '$JAR' $JAVA_OPTS $@" nobody
+ exec /bin/bash
+ ;;
+*)
+ exec $@
+ ;;
\ No newline at end of file
diff --git a/forms/Dockerfile b/forms/Dockerfile
new file mode 100644
index 00000000..71b58f2b
--- /dev/null
+++ b/forms/Dockerfile
@@ -0,0 +1,10 @@
+FROM openjdk:17-jdk-slim
+WORKDIR /opt/devgateway/tcdi/admin
+COPY target/deps/BOOT-INF/lib lib
+COPY target/deps/META-INF META-INF
+COPY entrypoint.sh ./
+COPY target/deps/BOOT-INF/classes .
+#USER nobody
+EXPOSE 8080
+ENTRYPOINT ["/opt/devgateway/tcdi/admin/entrypoint.sh"]
+CMD ["admin"]
diff --git a/forms/entrypoint.sh b/forms/entrypoint.sh
new file mode 100755
index 00000000..ce870333
--- /dev/null
+++ b/forms/entrypoint.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+ PROP_FILE="/etc/$1.properties"
+ truncate -s 0 $PROP_FILE
+ echo "..................... Writing to $PROP_FILE: ............... "
+
+ while IFS='=' read -r -d '' n v; do
+ if [[ $n == SPRING* ]]; then
+ VAR_NAME="$(echo "$n" | tr '[:upper:]_' '[:lower:].')"
+ echo "$VAR_NAME=$v" >> $PROP_FILE
+ fi
+ done < <(env -0)
+
+ while IFS='=' read -r -d '' n v; do
+ if [[ $n == TCDI_* ]]; then
+ VAR_NAME="$(echo "$n" | tr '[:upper:]_' '[:lower:].' | cut -c 6-)"
+ echo "$VAR_NAME=$v" >> $PROP_FILE
+ fi
+ done < <(env -0)
+
+
+ echo "................. Properties ................."
+ cat $PROP_FILE
+ echo "................. End sou ................."
+ JAVA_ARGS="$(tr '\n' ' ' <<-EOF
+ -cp .:lib/*
+ org.devgateway.toolkit.forms.wicket.FormsWebApplication
+ EOF
+ )"
+
+ JAVA_OPTS="-Dspring.config.location=file://$PROP_FILE"
+ exec java $JAVA_ARGS $JAVA_OPTS $@ nobody
+ ;;
+*)
+ exec $@
+ ;;
+
diff --git a/forms/pom.xml b/forms/pom.xml
index a98d10a9..2f1c3553 100644
--- a/forms/pom.xml
+++ b/forms/pom.xml
@@ -15,36 +15,40 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- forms
+ tcdi-admin-formsjarWicket Forms
- DGToolkit Forms
+ TCDI Admin Forms
- org.devgateway.toolkit
- toolkit
+ org.devgateway.tcdi
+ tcdi-admin0.0.1-SNAPSHOTUTF-8
- 1.8
- 8.10.0
- 8.10.0
- 2.0.11
- 1.13
- 2.0.19
- v20200406
- 3.1
+ 17
+ 10.2.0
+ 10.0.0
+ 5.0.4
+ 4.0.4
+ 1.17
+ 3.0.3
+ v20240317
+ 3.3.02.4.8
- 1.86.0
+ 2.2.0
+ 6.5.2
+ 3.1.9
+ 4.1.3
- org.devgateway.toolkit
- persistence
+ org.devgateway.tcdi
+ tcdi-admin-persistence0.0.1-SNAPSHOT
@@ -55,49 +59,15 @@
- org.springframework.boot
- spring-boot-devtools
- true
-
-
-
- org.devgateway.toolkit
- persistence-mongodb
- 0.0.1-SNAPSHOT
-
-
-
- org.devgateway.toolkit
- ui
- 0.0.1-SNAPSHOT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.devgateway.toolkit
- web
+ org.devgateway.tcdi
+ tcdi-admin-web0.0.1-SNAPSHOTcom.thoughtworks.xstreamxstream
- 1.4.18
+ 1.4.20
@@ -177,6 +147,12 @@
${wicket.webjars.version}
+
+ org.webjars
+ font-awesome
+ ${font.awesome.version}
+
+
org.apache.wicketwicket-devutils
@@ -189,6 +165,12 @@
${wicketstuff.version}
+
+ org.wicketstuff
+ wicketstuff-editable-grid
+ ${wicketstuff.version}
+
+
org.apache.wicket
@@ -261,7 +243,7 @@
de.agilecoders.wicketwicket-bootstrap-less
- ${wicket.bootstrap.version}
+ ${wicket.bootstrap.less.version}
@@ -278,14 +260,97 @@
org.apache.tikatika-core
- 2.0.0
+ 3.0.0org.apache.tikatika-parsers
- 2.0.0
+ 3.0.0pom
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ ${jersey.version}
+
+
+
+ org.glassfish.jersey.core
+ jersey-common
+ ${jersey.version}
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ ${jersey.version}
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-jackson
+ ${jersey.version}
+
+
+ org.glassfish.jersey.media
+ jersey-media-multipart
+ ${jersey.version}
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+ ${netflix.eureka.version}
+
+
+ org.glassfish.jersey.core
+ jersey-client
+
+
+
+ org.glassfish.jersey.core
+ jersey-common
+
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-jackson
+
+
+ org.glassfish.jersey.media
+ jersey-media-multipart
+
+
+
+
+
+ com.sun.mail
+ jakarta.mail
+ 2.0.1
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 6.1.0
+ provided
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jersey
+ 3.3.4
+
@@ -366,10 +431,17 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.8.0
+ 3.11.0${java.version}
+
+
+ org.hibernate.orm
+ hibernate-jpamodelgen
+ ${hibernate.version}
+
+
@@ -378,6 +450,13 @@
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/EurekaClientConfig.java b/forms/src/main/java/org/devgateway/toolkit/forms/EurekaClientConfig.java
new file mode 100644
index 00000000..2256c2b7
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/EurekaClientConfig.java
@@ -0,0 +1,31 @@
+package org.devgateway.toolkit.forms;
+
+import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
+import com.netflix.discovery.shared.transport.jersey.TransportClientFactories;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.SearchStrategy;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
+import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs;
+import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactories;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@EnableDiscoveryClient
+@Configuration
+public class EurekaClientConfig {
+ @Bean
+ @ConditionalOnClass(name = { "org.springframework.web.client.RestTemplate", "org.glassfish.jersey.client.JerseyClient" })
+ @ConditionalOnMissingBean(value = { AbstractDiscoveryClientOptionalArgs.class }, search = SearchStrategy.CURRENT)
+ public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
+ return new RestTemplateDiscoveryClientOptionalArgs(eurekaClientHttpRequestFactorySupplier);
+ }
+
+ @Bean
+ @ConditionalOnClass(name = { "org.springframework.web.client.RestTemplate", "org.glassfish.jersey.client.JerseyClient" })
+ @ConditionalOnMissingBean(value = { TransportClientFactories.class }, search = SearchStrategy.CURRENT)
+ public RestTemplateTransportClientFactories restTemplateTransportClientFactories(RestTemplateDiscoveryClientOptionalArgs optionalArgs) {
+ return new RestTemplateTransportClientFactories(optionalArgs);
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java b/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java
index 9b97fa58..6ca51b71 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java
@@ -13,23 +13,29 @@
import org.devgateway.toolkit.persistence.spring.CustomJPAUserDetailsService;
import org.devgateway.toolkit.web.spring.WebSecurityConfig;
+import org.devgateway.toolkit.web.util.SettingsUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
-import static org.devgateway.toolkit.web.WebConstants.FORMS_BASE_PATH;
+import java.util.Arrays;
+import java.util.stream.Collectors;
@Configuration
@EnableWebSecurity
-@Order(1) // this ensures the forms security comes first
+@Order(1)
public class FormsSecurityConfig extends WebSecurityConfig {
/**
@@ -37,30 +43,32 @@ public class FormsSecurityConfig extends WebSecurityConfig {
*/
private static final String UNIQUE_SECRET_REMEMBER_ME_KEY = "secret";
- /**
- * We ensure the superclass configuration is being applied Take note the
- * {@link FormsSecurityConfig} extends {@link WebSecurityConfig} which has
- * configuration for the dg-toolkit/web module. We then apply ant matchers
- * and ignore security for css/js/images resources, and wicket mounted
- * resources
- */
- @Override
- public void configure(final WebSecurity web) throws Exception {
- super.configure(web);
- web.ignoring().antMatchers("/img/**", "/css*/**", "/js*/**", "/assets*/**", "/favicon.ico", "/resources/**",
- "/resources/public/**");
- web.ignoring().antMatchers(
- FORMS_BASE_PATH + "/wicket/resource/**/*.js",
- FORMS_BASE_PATH + "/wicket/resource/**/*.css",
- FORMS_BASE_PATH + "/wicket/resource/**/*.png",
- FORMS_BASE_PATH + "/wicket/resource/**/*.jpg",
- FORMS_BASE_PATH + "/wicket/resource/**/*.woff",
- FORMS_BASE_PATH + "/wicket/resource/**/*.woff2",
- FORMS_BASE_PATH + "/wicket/resource/**/*.ttf",
- FORMS_BASE_PATH + "/wicket/resource/**/*.svg",
- FORMS_BASE_PATH + "/wicket/resource/**/*.gif");
+ @Autowired
+ private SettingsUtils settingsUtils;
+
+ @Autowired
+ protected CustomJPAUserDetailsService customJPAUserDetailsService;
+
+ @Value("${allowedApiEndpoints}")
+ private String[] allowedApiEndpoints;
+
+ @Value("${roleHierarchy}")
+ private String roleHierarchyStringRepresentation;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ private String[] getAllowedAPIEndpointsWithBasePath() {
+ if (allowedApiEndpoints != null) {
+ return Arrays.stream(allowedApiEndpoints)
+ .map(s -> settingsUtils.getFormsBasePath() + s)
+ .collect(Collectors.toList()).toArray(new String[allowedApiEndpoints.length]);
+ }
+
+ return new String[]{};
}
+
/**
* This bean defines the same key in the
* {@link RememberMeAuthenticationProvider}
@@ -86,26 +94,52 @@ public AbstractRememberMeServices rememberMeServices() {
return rememberMeServices;
}
- @Override
- protected void configure(final HttpSecurity http) throws Exception {
- super.configure(http);
-
- // we do not allow anyonymous token. When
- // enabled this basically means any guest
- // user will have an annoymous default role
- http.anonymous().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).
- // we let Wicket create and manage sessions, so we disable
- // session creation by spring
- and().csrf().disable(); // csrf protection interferes with some
- // wicket stuff
-
- // we enable http rememberMe cookie for autologin
- // http.rememberMe().key(UNIQUE_SECRET_REMEMBER_ME_KEY);
+ /**
+ * We ensure the superclass configuration is being applied Take note the
+ * {@link FormsSecurityConfig} extends {@link WebSecurityConfig} which has
+ * configuration for the dg-toolkit/web module. We then apply ant matchers
+ * and ignore security for css/js/images resources, and wicket mounted
+ * resources
+ */
+ @Bean
+ public SecurityFilterChain formsSecurityFilterChain(HttpSecurity http) throws Exception {
+ String formsBasePath = settingsUtils.getFormsBasePath();
- // resolved the error Refused to display * in a frame because it set
- // 'X-Frame-Options' to 'DENY'.
- http.headers().contentTypeOptions().and().xssProtection().and().cacheControl().and()
- .httpStrictTransportSecurity().and().frameOptions().sameOrigin();
+ http.securityContext(securityContext ->
+ securityContext.securityContextRepository(httpSessionSecurityContextRepository())
+ )
+ .authorizeHttpRequests(authz -> authz
+ .requestMatchers(formsBasePath + "/monitoring/**").hasRole("ADMIN")
+ .requestMatchers(formsBasePath + "/img/**", formsBasePath + "/css*/**",
+ formsBasePath + "/js*/**", formsBasePath + "/assets*/**",
+ formsBasePath + "/favicon.ico", formsBasePath + "/resources/**",
+ formsBasePath + "/resources/public/**",
+ formsBasePath + "/wicket/resource/**/*.js", formsBasePath + "/wicket/resource/**/*.css",
+ formsBasePath + "/wicket/resource/**/*.png", formsBasePath + "/wicket/resource/**/*.jpg",
+ formsBasePath + "/wicket/resource/**/*.woff", formsBasePath + "/wicket/resource/**/*.woff2",
+ formsBasePath + "/wicket/resource/**/*.ttf", formsBasePath + "/wicket/resource/**/*.svg",
+ formsBasePath + "/wicket/resource/**/*.gif", formsBasePath + "/forgotPassword/**"
+ ).permitAll() // Ignore static resources
+ .requestMatchers(formsBasePath + "/error/**", "/error/**").permitAll()
+ .requestMatchers(formsBasePath + "/**").authenticated()
+ )
+ .formLogin(form ->
+ form.loginPage(formsBasePath + "/login").permitAll()
+ )
+ .rememberMe(rememberMe ->
+ rememberMe.key(UNIQUE_SECRET_REMEMBER_ME_KEY).alwaysRemember(true)
+ )
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.NEVER))
+ .csrf(csrf -> csrf.disable())
+ .anonymous(anonymous -> anonymous.disable()) // Disable anonymous users
+ .headers(headers ->
+ headers.frameOptions(frameOptions -> frameOptions.sameOrigin())
+ .contentTypeOptions(Customizer.withDefaults())
+ .xssProtection(Customizer.withDefaults())
+ .contentSecurityPolicy(csp -> csp.policyDirectives("script-src 'self'; style-src 'self'"))
+ .cacheControl(Customizer.withDefaults())
+ );
+ return http.build();
}
}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/JavaMelodyConfiguration.java b/forms/src/main/java/org/devgateway/toolkit/forms/JavaMelodyConfiguration.java
index 2d714e31..574aff10 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/JavaMelodyConfiguration.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/JavaMelodyConfiguration.java
@@ -29,8 +29,8 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
-import javax.servlet.DispatcherType;
-import javax.servlet.ServletContext;
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.ServletContext;
import java.util.Arrays;
import java.util.EventListener;
import java.util.HashSet;
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/WarInitializer.java b/forms/src/main/java/org/devgateway/toolkit/forms/WarInitializer.java
index ab641f75..a00580a0 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/WarInitializer.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/WarInitializer.java
@@ -17,9 +17,8 @@
/**
* Allows the app to deployed as Servlet 3.0 WAR
- *
- * @author mpostelnicu
*
+ * @author mpostelnicu
*/
public class WarInitializer extends SpringBootServletInitializer {
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java
index e2cae919..81a5eede 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java
@@ -26,7 +26,7 @@ private WebConstants() {
}
- public static final int PAGE_SIZE = 10;
+ public static final Integer DEFAULT_PAGE_SIZE = 20;
public static final int SELECT_PAGE_SIZE = 25;
public static final int PAGE_SIZE_NO_LIMIT = 999999;
@@ -36,15 +36,25 @@ private WebConstants() {
= "if(typeof disableFormLeavingConfirmation === 'function') disableFormLeavingConfirmation();";
public static final String PARAM_PRINT = "print";
+ public static final String PARAM_YEAR = "year";
public static final String PARAM_ID = "id";
+
+ public static final String PARAM_SERVICE = "service";
+
+ public static final String PARAM_ENTITY = "entity";
public static final String V_POSITION = "vPosition";
public static final String MAX_HEIGHT = "maxPosition";
public static final String PARAM_REVISION_ID = "revisionId";
public static final String PARAM_ENTITY_CLASS = "class";
+ public static final String PARAM_AUTO_SAVE = "autosave";
public static final String LANGUAGE_PARAM = "lang";
+ public static final String SERVICE_DATA_TYPE = "data";
+
+ public static final String SERVICE_TETSIM_TYPE = "tetsim";
+
public static final class StringValidators {
public static final StringValidator MAXIMUM_LENGTH_VALIDATOR_STD_DEFAULT_TEXT =
StringValidator.maximumLength(DBConstants.STD_DEFAULT_TEXT_LENGTH);
@@ -54,6 +64,8 @@ public static final class StringValidators {
StringValidator.maximumLength(DBConstants.MAX_DEFAULT_TEXT_AREA);
}
+ public static final int MAXIMUM_PERCENTAGE = 100;
+
// add more languages here. It is pointless to make this dynamic because the
// wicket i18n is in .properties files so we need
// to change the src code anyway.
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/application-dev.properties b/forms/src/main/java/org/devgateway/toolkit/forms/application-dev.properties
new file mode 100644
index 00000000..16915911
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/application-dev.properties
@@ -0,0 +1,8 @@
+server.port = 8080
+eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
+eureka.client.registerWithEureka=true
+eureka.client.fetchRegistry=true
+logging.level.org.springframework=DEBUG
+eureka.instance.metadata-map.type=data
+
+spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/application.properties b/forms/src/main/java/org/devgateway/toolkit/forms/application.properties
index 642e6623..2ccb1823 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/application.properties
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/application.properties
@@ -10,10 +10,22 @@
# Development Gateway - initial API and implementation
###############################################################################
spring.profiles.active=reports
-server.port = 8090
+spring.application.name=tcdi-admin
+server.port = 8080
server.servlet.session.timeout=7d
-javamelody.enabled=true
+javamelody.enabled=false
javamelody.spring-monitoring-enabled=true
javamelody.init-parameters.log=true
javamelody.advisor-auto-proxy-creator-enabled=false
spring.aop.proxy-target-class=true
+
+# The presence of either of those properties switches on the RemoteIpValve.
+# This presence of valve will fix issue with reverse proxy
+server.tomcat.remote-ip-header=x-forwarded-for
+server.tomcat.protocol-header=x-forwarded-proto
+
+eureka.client.serviceUrl.defaultZone=http://eureka:8761/eureka/
+eureka.client.registerWithEureka=true
+eureka.client.fetchRegistry=true
+
+spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ClientConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ClientConstants.java
new file mode 100644
index 00000000..0ceccbe5
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ClientConstants.java
@@ -0,0 +1,29 @@
+package org.devgateway.toolkit.forms.client;
+
+public class ClientConstants {
+
+ public static final class JobStatus {
+ public static final String COMPLETED = "COMPLETED";
+ public static final String ERROR = "ERROR";
+ }
+
+ public final static String PATH_HEALTH = "/actuator/health";
+ public final static String PATH_JOBS = "/admin/jobs";
+ public final static String PATH_DATASETS = "/admin/datasets";
+
+ public final static String PATH_DIMENSIONS = "/admin/dimensions";
+
+ public final static String PATH_MEASURES = "/admin/measures";
+
+ public static final String PATH_CATEGORIES = "/admin/categories";
+
+ public final static String PATH_FILTERS = "/admin/filters";
+
+ public final static String PATH_TEMPLATE_DOWNLOAD = "/admin/template/download";
+
+
+ public final static String PATH_CODE = "/code";
+
+ public final static String CODE_PREFIX = "tcdi-";
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/DataSetClientException.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/DataSetClientException.java
new file mode 100644
index 00000000..8fb4642d
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/DataSetClientException.java
@@ -0,0 +1,7 @@
+package org.devgateway.toolkit.forms.client;
+
+public class DataSetClientException extends Throwable {
+ public DataSetClientException(final String message) {
+ super(message);
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/DatasetClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/DatasetClient.java
new file mode 100644
index 00000000..66514d9e
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/DatasetClient.java
@@ -0,0 +1,219 @@
+package org.devgateway.toolkit.forms.client;
+
+import org.apache.commons.io.FileUtils;
+import org.devgateway.toolkit.persistence.dao.data.Dataset;
+import org.devgateway.toolkit.persistence.dto.ServiceDimension;
+import org.devgateway.toolkit.persistence.dto.ServiceMeasure;
+import org.glassfish.jersey.client.JerseyClient;
+import org.glassfish.jersey.client.JerseyClientBuilder;
+import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
+import org.glassfish.jersey.media.multipart.FormDataMultiPart;
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.Response;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
+import static jakarta.ws.rs.core.MediaType.MULTIPART_FORM_DATA_TYPE;
+import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
+import static jakarta.ws.rs.core.Response.Status.Family.SUCCESSFUL;
+import static org.devgateway.toolkit.forms.client.ClientConstants.CODE_PREFIX;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_CODE;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_DATASETS;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_DIMENSIONS;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_HEALTH;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_JOBS;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_MEASURES;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_TEMPLATE_DOWNLOAD;
+
+
+public class DatasetClient {
+
+ private final JerseyClient client;
+
+ private final String baseUrl;
+
+ public DatasetClient(final String baseUrl) {
+ this.baseUrl = baseUrl;
+ this.client = JerseyClientBuilder.createClient().register(MultiPartFeature.class);
+ }
+
+ public DatasetJobStatus getDatasetStatus(String jobId) throws DataSetClientException {
+ if (isUp()) {
+ Response jobStatusResponse = client.target(baseUrl)
+ .path(PATH_JOBS)
+ .path(jobId)
+ .request(APPLICATION_JSON)
+ .get();
+
+ if (jobStatusResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return jobStatusResponse.readEntity(DatasetJobStatus.class);
+ }
+
+ throw new DataSetClientException(jobStatusResponse.toString());
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public DatasetJobStatus unpublishDataset(String code) throws DataSetClientException {
+ if (isUp()) {
+ Response jobStatusResponse = client.target(baseUrl)
+ .path(PATH_DATASETS)
+ .path(code)
+ .request()
+ .delete();
+
+ if (jobStatusResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return jobStatusResponse.readEntity(DatasetJobStatus.class);
+ }
+
+ throw new DataSetClientException(jobStatusResponse.toString());
+ } else {
+ throw new RuntimeException(("Service is not up"));
+ }
+ }
+
+ public DatasetJobStatus publishDataset(Dataset dataset, byte[] datasetContent) throws DataSetClientException {
+ if (isUp()) {
+ File tempUploadFile;
+ try {
+ tempUploadFile = File.createTempFile(dataset.getYear() + "_tetsim", "csv");
+ tempUploadFile.deleteOnExit();
+
+ FileUtils.writeByteArrayToFile(tempUploadFile, datasetContent);
+ FileDataBodyPart fileDataBodyPart = new FileDataBodyPart("file", tempUploadFile, APPLICATION_OCTET_STREAM_TYPE);
+ fileDataBodyPart.setContentDisposition(FormDataContentDisposition.name("file").fileName(tempUploadFile.getName()).build());
+
+ FormDataMultiPart multiPart = new FormDataMultiPart();
+ multiPart.field("name", "TETSIM dataset " + dataset.getYear());
+ multiPart.field("code", CODE_PREFIX + dataset.getId());
+ multiPart.field("file", tempUploadFile.getName(), TEXT_PLAIN_TYPE)
+ .bodyPart(fileDataBodyPart);
+
+ Response jobStatusResponse = client.target(baseUrl)
+ .path(PATH_DATASETS)
+ .request()
+ .post(Entity.entity(multiPart, MULTIPART_FORM_DATA_TYPE));
+
+ if (jobStatusResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return jobStatusResponse.readEntity(DatasetJobStatus.class);
+ }
+
+ throw new DataSetClientException(jobStatusResponse.toString());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public DatasetJobStatus publishDataset(String name, String code, File file) throws DataSetClientException {
+ if (isUp()) {
+ FileDataBodyPart fileDataBodyPart = new FileDataBodyPart("file", file, APPLICATION_OCTET_STREAM_TYPE);
+ fileDataBodyPart.setContentDisposition(FormDataContentDisposition.name("file")
+ .fileName(file.getName())
+ .build());
+
+ FormDataMultiPart multiPart = new FormDataMultiPart();
+ multiPart.field("name", name);
+ multiPart.field("code", code);
+ multiPart.field("file", file.getName(), TEXT_PLAIN_TYPE).bodyPart(fileDataBodyPart);
+
+ Response jobStatusResponse = client.target(baseUrl)
+ .path(PATH_DATASETS)
+ .request()
+ .post(Entity.entity(multiPart, MULTIPART_FORM_DATA_TYPE));
+
+ if (jobStatusResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return jobStatusResponse.readEntity(DatasetJobStatus.class);
+ }
+
+ throw new DataSetClientException(jobStatusResponse.toString());
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public DatasetJobStatus getDatasetJobStatus(String code) {
+ Response jobStatusResponse = client.target(baseUrl)
+ .path(PATH_JOBS)
+ .path(PATH_CODE)
+ .path(code)
+ .request(APPLICATION_JSON).get();
+
+ if (jobStatusResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return jobStatusResponse.readEntity(DatasetJobStatus.class);
+ }
+
+ return null;
+ }
+
+ public boolean isUp() {
+ Response healthResponse = client.target(baseUrl).path(PATH_HEALTH)
+ .request(APPLICATION_JSON).get();
+
+ if (healthResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return healthResponse.readEntity(ServiceHealthStatus.class).isUp();
+ }
+
+ return false;
+ }
+
+ public List getDimensions() {
+ if (isUp()) {
+ Response dimensionsResponse = client.target(baseUrl)
+ .path(PATH_DIMENSIONS)
+ .request(APPLICATION_JSON).get();
+
+ if (dimensionsResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return dimensionsResponse.readEntity(new GenericType>() {});
+ }
+
+ return null;
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public List getMeasures() {
+ if (isUp()) {
+ Response measuresResponse = client.target(baseUrl)
+ .path(PATH_MEASURES)
+ .request(APPLICATION_JSON).get();
+
+ if (measuresResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return measuresResponse.readEntity(new GenericType>() {});
+ }
+
+ return null;
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public byte[] getTemplateDownload() {
+ if (isUp()) {
+ Response response = client.target(baseUrl)
+ .path(PATH_TEMPLATE_DOWNLOAD)
+ .request(APPLICATION_OCTET_STREAM)
+ .get();
+
+ if (response.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return response.readEntity(byte[].class);
+ }
+
+ return null;
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/DatasetJobStatus.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/DatasetJobStatus.java
new file mode 100644
index 00000000..6687724a
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/DatasetJobStatus.java
@@ -0,0 +1,78 @@
+package org.devgateway.toolkit.forms.client;
+
+import java.time.ZonedDateTime;
+
+public class DatasetJobStatus {
+
+ private Long id;
+
+ private ZonedDateTime createdDate;
+
+ private ZonedDateTime endDate;
+
+ private String status;
+
+ private String code;
+
+ private String message;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public ZonedDateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(final ZonedDateTime createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public ZonedDateTime getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(final ZonedDateTime endDate) {
+ this.endDate = endDate;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(final String status) {
+ this.status = status;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(final String code) {
+ this.code = code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(final String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return "DatasetJobStatus{" +
+ "id=" + id +
+ ", createdDate=" + createdDate +
+ ", endDate=" + endDate +
+ ", status='" + status + '\'' +
+ ", code='" + code + '\'' +
+ ", message='" + message + '\'' +
+ '}';
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceCategoryClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceCategoryClient.java
new file mode 100644
index 00000000..129e0471
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceCategoryClient.java
@@ -0,0 +1,29 @@
+package org.devgateway.toolkit.forms.client;
+
+import org.devgateway.toolkit.persistence.dto.ServiceCategory;
+
+import jakarta.ws.rs.core.GenericType;
+import java.util.List;
+
+public class ServiceCategoryClient extends ServiceEntityClient {
+
+ public ServiceCategoryClient(final String baseUrl) {
+ super(baseUrl);
+ }
+
+ @Override
+ public String getEntitiesPath() {
+ return ClientConstants.PATH_CATEGORIES;
+ }
+
+ @Override
+ protected GenericType getGenericType() {
+ return new GenericType() {};
+ }
+
+ @Override
+ protected GenericType> getGenericListType() {
+ return new GenericType>() {};
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceDatasetClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceDatasetClient.java
new file mode 100644
index 00000000..fc02833c
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceDatasetClient.java
@@ -0,0 +1,49 @@
+package org.devgateway.toolkit.forms.client;
+
+import org.devgateway.toolkit.persistence.dto.ServiceDataset;
+
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.Response;
+import java.util.List;
+
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static jakarta.ws.rs.core.Response.Status.Family.SUCCESSFUL;
+
+public class ServiceDatasetClient extends ServiceEntityClient {
+
+ public ServiceDatasetClient(final String baseUrl) {
+ super(baseUrl);
+ }
+
+ @Override
+ public String getEntitiesPath() {
+ return ClientConstants.PATH_DATASETS;
+ }
+
+ @Override
+ protected GenericType getGenericType() {
+ return new GenericType() {};
+ }
+
+ @Override
+ protected GenericType> getGenericListType() {
+ return new GenericType>() {};
+ }
+
+ public void delete(final ServiceDataset entity) {
+ if (isUp()) {
+ Response response = client.target(baseUrl)
+ .path(getEntitiesPath())
+ .path(entity.getCode().toString())
+ .request(APPLICATION_JSON)
+ .delete();
+
+ if (response.getStatusInfo().getFamily() != SUCCESSFUL) {
+ throw new RuntimeException("Error in deleting the dataset");
+ }
+ } else {
+ throw new RuntimeException(("Service is not up"));
+ }
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceDimensionClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceDimensionClient.java
new file mode 100644
index 00000000..e5f4e06f
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceDimensionClient.java
@@ -0,0 +1,29 @@
+package org.devgateway.toolkit.forms.client;
+
+import org.devgateway.toolkit.persistence.dto.ServiceDimension;
+
+import jakarta.ws.rs.core.GenericType;
+import java.util.List;
+
+public class ServiceDimensionClient extends ServiceEntityClient {
+
+ public ServiceDimensionClient(final String baseUrl) {
+ super(baseUrl);
+ }
+
+ @Override
+ public String getEntitiesPath() {
+ return ClientConstants.PATH_DIMENSIONS;
+ }
+
+ @Override
+ protected GenericType getGenericType() {
+ return new GenericType() {};
+ }
+
+ @Override
+ protected GenericType> getGenericListType() {
+ return new GenericType>() {};
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceEntityClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceEntityClient.java
new file mode 100644
index 00000000..7a1a5171
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceEntityClient.java
@@ -0,0 +1,136 @@
+package org.devgateway.toolkit.forms.client;
+
+import jakarta.ws.rs.ProcessingException;
+import org.devgateway.toolkit.persistence.dto.ServiceEntity;
+import org.glassfish.jersey.client.JerseyClient;
+import org.glassfish.jersey.client.JerseyClientBuilder;
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.Response;
+import java.util.List;
+
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static jakarta.ws.rs.core.Response.Status.Family.SUCCESSFUL;
+import static org.devgateway.toolkit.forms.client.ClientConstants.PATH_HEALTH;
+
+public abstract class ServiceEntityClient {
+
+ protected final JerseyClient client;
+
+ protected final String baseUrl;
+
+ public ServiceEntityClient(final String baseUrl) {
+ this.baseUrl = baseUrl;
+ this.client = JerseyClientBuilder.createClient();
+ this.client.register(MultiPartFeature.class);
+ }
+
+ public abstract String getEntitiesPath();
+
+ public List findAll() {
+ if (isUp()) {
+ Response response = client.target(baseUrl)
+ .path(getEntitiesPath())
+ .request(APPLICATION_JSON).get();
+
+ if (response.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return response.readEntity(getGenericListType());
+ }
+ return null;
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public T findOne(final Long id) {
+ if (isUp()) {
+ Response response = client.target(baseUrl)
+ .path(getEntitiesPath())
+ .path(id.toString())
+ .request(APPLICATION_JSON).get();
+
+ if (response.getStatusInfo().getFamily() == SUCCESSFUL) {
+ return response.readEntity(getGenericType());
+ }
+
+ return null;
+ }
+
+ throw new RuntimeException(("Service is not up"));
+ }
+
+ public void save(final T entity) {
+ if (isUp()) {
+ Response measuresResponse = client.target(baseUrl)
+ .path(getEntitiesPath())
+ .request(APPLICATION_JSON)
+ .post(Entity.entity(entity, APPLICATION_JSON));
+
+ if (measuresResponse.getStatusInfo().getFamily() != SUCCESSFUL) {
+ throw new RuntimeException("Error in updating the entity");
+ }
+ } else {
+ throw new RuntimeException(("Service is not up"));
+ }
+ }
+
+ public void update(final T entity) {
+ if (isUp()) {
+ Response measuresResponse = client.target(baseUrl)
+ .path(getEntitiesPath())
+ .path(entity.getId().toString())
+ .request(APPLICATION_JSON)
+ .put(Entity.entity(entity, APPLICATION_JSON));
+
+ if (measuresResponse.getStatusInfo().getFamily() != SUCCESSFUL) {
+ throw new RuntimeException("Error in updating the entity");
+ }
+ } else {
+ throw new RuntimeException(("Service is not up"));
+ }
+ }
+
+ public void delete(final T entity) {
+ if (isUp()) {
+ Response response = client.target(baseUrl)
+ .path(getEntitiesPath())
+ .path(entity.getId().toString())
+ .request(APPLICATION_JSON)
+ .delete();
+
+ if (response.getStatusInfo().getFamily() != SUCCESSFUL) {
+ throw new RuntimeException("Error in deleting the entity");
+ }
+ } else {
+ throw new RuntimeException(("Service is not up"));
+ }
+ }
+
+ public boolean isUp() {
+ try {
+ Response healthResponse = client.target(baseUrl).path(PATH_HEALTH)
+ .request(APPLICATION_JSON).get();
+
+ if (healthResponse.getStatusInfo().getFamily() == SUCCESSFUL) {
+ try {
+ // First try the direct mapping to ServiceHealthStatus
+ ServiceHealthStatus status = healthResponse.readEntity(ServiceHealthStatus.class);
+ return status.isUp();
+ } catch (ProcessingException e) {
+ return false;
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ System.err.println("Health check failed: " + e.getMessage());
+ return false;
+ }
+ }
+
+ protected abstract GenericType getGenericType();
+
+ protected abstract GenericType> getGenericListType();
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceFilterClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceFilterClient.java
new file mode 100644
index 00000000..6479a138
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceFilterClient.java
@@ -0,0 +1,29 @@
+package org.devgateway.toolkit.forms.client;
+
+import org.devgateway.toolkit.persistence.dto.ServiceFilter;
+
+import jakarta.ws.rs.core.GenericType;
+import java.util.List;
+
+public class ServiceFilterClient extends ServiceEntityClient {
+
+ public ServiceFilterClient(final String baseUrl) {
+ super(baseUrl);
+ }
+
+ @Override
+ public String getEntitiesPath() {
+ return ClientConstants.PATH_FILTERS;
+ }
+
+ @Override
+ protected GenericType getGenericType() {
+ return new GenericType() {};
+ }
+
+ @Override
+ protected GenericType> getGenericListType() {
+ return new GenericType>() {};
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceHealthStatus.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceHealthStatus.java
new file mode 100644
index 00000000..f891db05
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceHealthStatus.java
@@ -0,0 +1,21 @@
+package org.devgateway.toolkit.forms.client;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ServiceHealthStatus {
+
+ private String status;
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(final String status) {
+ this.status = status;
+ }
+
+ public boolean isUp() {
+ return "UP".equalsIgnoreCase(status);
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceMeasureClient.java b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceMeasureClient.java
new file mode 100644
index 00000000..bc325d27
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/client/ServiceMeasureClient.java
@@ -0,0 +1,29 @@
+package org.devgateway.toolkit.forms.client;
+
+import org.devgateway.toolkit.persistence.dto.ServiceMeasure;
+
+import jakarta.ws.rs.core.GenericType;
+import java.util.List;
+
+public class ServiceMeasureClient extends ServiceEntityClient {
+
+ public ServiceMeasureClient(final String baseUrl) {
+ super(baseUrl);
+ }
+
+ @Override
+ public String getEntitiesPath() {
+ return ClientConstants.PATH_MEASURES;
+ }
+
+ @Override
+ protected GenericType getGenericType() {
+ return new GenericType() {};
+ }
+
+ @Override
+ protected GenericType> getGenericListType() {
+ return new GenericType>() {};
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullEditPageClassException.java b/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullEditPageClassException.java
index 4252babd..ee95d617 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullEditPageClassException.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullEditPageClassException.java
@@ -1,16 +1,16 @@
-/*******************************************************************************
+/**
* Copyright (c) 2015 Development Gateway, Inc and others.
- *
+ *
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the MIT License (MIT)
* which accompanies this distribution, and is available at
* https://opensource.org/licenses/MIT
- *
+ *
* Contributors:
* Development Gateway - initial API and implementation
- *******************************************************************************/
+ */
/**
- *
+ *
*/
package org.devgateway.toolkit.forms.exceptions;
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullListPageClassException.java b/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullListPageClassException.java
index 496ee949..80281d27 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullListPageClassException.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullListPageClassException.java
@@ -1,16 +1,16 @@
-/*******************************************************************************
+/**
* Copyright (c) 2015 Development Gateway, Inc and others.
- *
+ *
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the MIT License (MIT)
* which accompanies this distribution, and is available at
* https://opensource.org/licenses/MIT
- *
+ *
* Contributors:
* Development Gateway - initial API and implementation
- *******************************************************************************/
+ */
/**
- *
+ *
*/
package org.devgateway.toolkit.forms.exceptions;
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullServiceEntityServiceException.java b/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullServiceEntityServiceException.java
new file mode 100644
index 00000000..05656e00
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/exceptions/NullServiceEntityServiceException.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015 Development Gateway, Inc and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the MIT License (MIT)
+ * which accompanies this distribution, and is available at
+ * https://opensource.org/licenses/MIT
+ *
+ * Contributors:
+ * Development Gateway - initial API and implementation
+ */
+/**
+ *
+ */
+package org.devgateway.toolkit.forms.exceptions;
+
+/**
+ * @author vchihai
+ */
+public class NullServiceEntityServiceException extends RuntimeException {
+ private static final long serialVersionUID = 7516874812755335131L;
+
+ public NullServiceEntityServiceException() {
+ super("serviceEntityService is null! Please set the serviceEntityService in your constructor");
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/models/SubComponentWrapModel.java b/forms/src/main/java/org/devgateway/toolkit/forms/models/SubComponentWrapModel.java
index 194fe406..9de6bf91 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/models/SubComponentWrapModel.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/models/SubComponentWrapModel.java
@@ -1,16 +1,16 @@
-/*******************************************************************************
+/**
* Copyright (c) 2015 Development Gateway, Inc and others.
- *
+ *
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the MIT License (MIT)
* which accompanies this distribution, and is available at
* https://opensource.org/licenses/MIT
- *
+ *
* Contributors:
* Development Gateway - initial API and implementation
- *******************************************************************************/
+ */
/**
- *
+ *
*/
package org.devgateway.toolkit.forms.models;
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java
index 220de14d..774b518f 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java
@@ -1,16 +1,16 @@
-/*******************************************************************************
+/**
* Copyright (c) 2015 Development Gateway, Inc and others.
- *
+ *
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the MIT License (MIT)
* which accompanies this distribution, and is available at
* https://opensource.org/licenses/MIT
- *
+ *
* Contributors:
* Development Gateway - initial API and implementation
- *******************************************************************************/
+ */
/**
- *
+ *
*/
package org.devgateway.toolkit.forms.security;
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/DatasetClientService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/DatasetClientService.java
new file mode 100644
index 00000000..0bbfcd2c
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/DatasetClientService.java
@@ -0,0 +1,130 @@
+package org.devgateway.toolkit.forms.service;
+
+import org.apache.commons.io.FileUtils;
+import org.devgateway.toolkit.forms.client.DataSetClientException;
+import org.devgateway.toolkit.forms.client.DatasetClient;
+import org.devgateway.toolkit.persistence.dao.data.CSVDataset;
+import org.devgateway.toolkit.persistence.dao.data.Dataset;
+import org.devgateway.toolkit.persistence.dao.data.TetsimDataset;
+import org.devgateway.toolkit.persistence.dto.ServiceMeasure;
+import org.devgateway.toolkit.persistence.dto.ServiceMetadata;
+import org.devgateway.toolkit.persistence.service.data.CSVDatasetService;
+import org.devgateway.toolkit.persistence.service.data.TetsimDatasetService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.devgateway.toolkit.forms.client.ClientConstants.CODE_PREFIX;
+import static org.devgateway.toolkit.forms.client.ClientConstants.JobStatus.COMPLETED;
+import static org.devgateway.toolkit.forms.client.ClientConstants.JobStatus.ERROR;
+import static org.devgateway.toolkit.persistence.dao.DBConstants.Status.DRAFT;
+import static org.devgateway.toolkit.persistence.dao.DBConstants.Status.ERROR_IN_PUBLISHING;
+import static org.devgateway.toolkit.persistence.dao.DBConstants.Status.ERROR_IN_UNPUBLISHING;
+import static org.devgateway.toolkit.persistence.dao.DBConstants.Status.PUBLISHED;
+import static org.devgateway.toolkit.persistence.dao.DBConstants.Status.PUBLISHING;
+
+@Service
+public class DatasetClientService {
+
+ private static final Logger logger = LoggerFactory.getLogger(DatasetClientService.class);
+
+ @Autowired
+ private TetsimDatasetService tetsimDatasetService;
+
+ @Autowired
+ private CSVDatasetService csvDatasetService;
+
+ @Autowired
+ private EurekaClientService eurekaClientService;
+
+ @Scheduled(cron = "0 * * * * *")
+ public void triggerCheckDatasetsJob() {
+ logger.debug("Fired triggerCheckDatasetsJob");
+ List datasets = new ArrayList<>();
+ datasets.addAll(tetsimDatasetService.findAllInProgress());
+ datasets.addAll(csvDatasetService.findAllInProgress());
+
+ checkDatasetJobs(datasets);
+ }
+
+ private void checkDatasetJobs(List datasets) {
+ datasets.forEach(d -> {
+ ServiceMetadata serviceMetadata = eurekaClientService.findByName(d.getDestinationService());
+ DatasetClient client = new DatasetClient(serviceMetadata.getUrl());
+ String status = client.getDatasetJobStatus(CODE_PREFIX + d.getId()).getStatus();
+ String initialStatus = d.getStatus();
+ if (COMPLETED.equals(status)) {
+ String completedStatus = getCompletedStatus(initialStatus);
+ d.setStatus(completedStatus);
+ logger.info(String.format("The dataset with id %s changed the status from %s to %s",
+ d.getId(), initialStatus, completedStatus));
+ } else if (ERROR.equals(status)) {
+ String errorStatus = getErrorStatus(initialStatus);
+ d.setStatus(errorStatus);
+ logger.info(String.format("The dataset with id %s changed the status from %s to %s",
+ d.getId(), initialStatus, errorStatus));
+ }
+
+ if (!initialStatus.equals(d.getStatus())) {
+ if (d instanceof TetsimDataset) {
+ tetsimDatasetService.save((TetsimDataset) d);
+ } else if (d instanceof CSVDataset) {
+ csvDatasetService.save((CSVDataset) d);
+ } else {
+ throw new RuntimeException("Invalid dataset class");
+ }
+ }
+ });
+ }
+
+ private String getCompletedStatus(final String status) {
+ return PUBLISHING.equals(status) ? PUBLISHED : DRAFT;
+ }
+
+ private String getErrorStatus(final String status) {
+ return PUBLISHING.equals(status) ? ERROR_IN_PUBLISHING : ERROR_IN_UNPUBLISHING;
+ }
+
+ public void publishDataset(Dataset dataset, String fileName, byte[] content) throws DataSetClientException {
+ String serviceURL = getDestinationService(dataset).getUrl();
+ DatasetClient client = new DatasetClient(serviceURL);
+
+ String name = "Dataset " + dataset.getYear();
+ String code = CODE_PREFIX + dataset.getId();
+
+ File tempUploadFile;
+ try {
+ tempUploadFile = File.createTempFile(fileName, null);
+ tempUploadFile.deleteOnExit();
+ FileUtils.writeByteArrayToFile(tempUploadFile, content);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ client.publishDataset(name, code, tempUploadFile);
+ }
+
+ public void unpublishDataset(Dataset dataset) throws DataSetClientException {
+ String code = CODE_PREFIX + dataset.getId();
+ String serviceURL = getDestinationService(dataset).getUrl();
+
+ new DatasetClient(serviceURL).unpublishDataset(code);
+ }
+
+ private ServiceMetadata getDestinationService(Dataset dataset) {
+ String destinationService = dataset.getDestinationService();
+ return eurekaClientService.findByName(destinationService);
+ }
+
+ public byte[] getTemplateDownload(final String serviceName) {
+ ServiceMetadata service = eurekaClientService.findByName(serviceName);
+ return new DatasetClient(service.getUrl()).getTemplateDownload();
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/DerbyDatabaseBackupService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/DerbyDatabaseBackupService.java
index 68a0026c..cd3065e6 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/service/DerbyDatabaseBackupService.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/DerbyDatabaseBackupService.java
@@ -32,17 +32,15 @@
/**
* @author mpostelnicu Provides built-in backup services. Defaults to the
- * database location derby.system.home. Currently works only for Derby.
- * Runs 9PM daily (good backup time for both EST and CET)
+ * database location derby.system.home. Currently works only for Derby.
+ * Runs 9PM daily (good backup time for both EST and CET)
*/
@Service
public class DerbyDatabaseBackupService {
- private static final Logger logger = LoggerFactory.getLogger(DerbyDatabaseBackupService.class);
-
public static final String DATABASE_PRODUCT_NAME_APACHE_DERBY = "Apache Derby";
public static final String ARCHIVE_SUFFIX = ".zip";
-
+ private static final Logger logger = LoggerFactory.getLogger(DerbyDatabaseBackupService.class);
@Autowired
private DataSource datasource;
@@ -55,7 +53,6 @@ public class DerbyDatabaseBackupService {
* use a cron format and invoke it every day at 21:00 server time. That
* should be a good time for backup for both EST and CET
*/
- @Scheduled(cron = "0 0 21 * * ?")
public void backupDatabase() {
String databaseProductName;
@@ -89,9 +86,8 @@ public void backupDatabase() {
* last leaf of backup's location parent directory + {@link #databaseName}
* If the backupPath does not have a parent, it uses the host name from
* {@link InetAddress#getLocalHost()}
- *
- * @param backupPath
- * the parent directory for the backup
+ *
+ * @param backupPath the parent directory for the backup
* @return the backup url to be used by the backup procedure
* @throws UnknownHostException
*/
@@ -121,7 +117,7 @@ private String createBackupURL(final String backupPath) {
* Use backup.home system variable, if exists, as homedir for backups If
* backup.home does not exist try using derby.system.home If that is also
* null, use user.dir
- *
+ *
* @return the backupURL
*/
private String createBackupURL() {
@@ -138,11 +134,11 @@ private String createBackupURL() {
/**
* Backup the On-Line Derby database. This temporarily locks the db in
* readonly mode
- *
+ *
* Invokes SYSCS_BACKUP_DATABASE and dumps the database to the temporary
* directory Use {@link ZipUtil#pack(File, File)} to zip the directory
* Deletes the temporary directory
- *
+ *
* @see #createBackupURL(String)
*/
private void backupDerbyDatabase() {
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/EurekaClientService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/EurekaClientService.java
new file mode 100644
index 00000000..e00b43fe
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/EurekaClientService.java
@@ -0,0 +1,54 @@
+package org.devgateway.toolkit.forms.service;
+
+import com.netflix.appinfo.InstanceInfo;
+import org.devgateway.toolkit.persistence.dto.ServiceMetadata;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.devgateway.toolkit.forms.WebConstants.SERVICE_DATA_TYPE;
+
+@Service
+public class EurekaClientService {
+
+ @Autowired
+ private DiscoveryClient discoveryClient;
+
+ public List findAll() {
+ List services = new ArrayList<>();
+ discoveryClient.getServices().forEach(s -> {
+ discoveryClient.getInstances(s).stream().forEach(instance -> {
+ InstanceInfo instanceInfo = ((EurekaServiceInstance) instance).getInstanceInfo();
+ ServiceMetadata service = new ServiceMetadata();
+ service.setName(instanceInfo.getAppName());
+ service.setUrl(instanceInfo.getHomePageUrl());
+ service.setId(instanceInfo.getId());
+ service.setType(instance.getMetadata().getOrDefault("type", null));
+ service.setLabel(instance.getMetadata().getOrDefault("label", instanceInfo.getAppName()));
+ service.setStatus(instanceInfo.getStatus().toString());
+ service.setTetsim(Boolean.valueOf(instanceInfo.getMetadata().getOrDefault("tetsim", "false")));
+ services.add(service);
+ });
+ });
+
+ return services;
+ }
+
+ public List findAllWithData() {
+ return findAll().stream()
+ .filter(s -> SERVICE_DATA_TYPE.equalsIgnoreCase(s.getType()))
+ .collect(Collectors.toList());
+ }
+
+ public ServiceMetadata findByName(String name) {
+ return findAll().stream()
+ .filter(s -> s.getName().equals(name))
+ .findFirst().get();
+ }
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java
index 06baf338..c9e24aae 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java
@@ -11,8 +11,13 @@
*******************************************************************************/
package org.devgateway.toolkit.forms.service;
+import org.apache.commons.lang3.StringUtils;
import org.devgateway.toolkit.persistence.dao.Person;
+import org.devgateway.toolkit.persistence.service.AdminSettingsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
@@ -28,10 +33,20 @@
@Component
public class SendEmailService {
+ private static final Logger logger = LoggerFactory.getLogger(SendEmailService.class);
+
+ private final static String DEFAULT_EMAIL_FROM_ADDRESS = "tcdisupport@developmentgateway.org";
+
+ @Value("${spring.mail.sender}")
+ private String from;
+
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
private JavaMailSender javaMailSender;
+ @Autowired
+ private AdminSettingsService adminSettingsService;
+
private SimpleMailMessage templateMessage;
/**
@@ -43,19 +58,22 @@ public class SendEmailService {
* @param newPassword
*/
public void sendEmailResetPassword(final Person person, final String newPassword) {
+ String country = adminSettingsService.get().getCountryName();
final SimpleMailMessage msg = new SimpleMailMessage();
+ String fromEmail = StringUtils.isNotBlank(from) ? from : DEFAULT_EMAIL_FROM_ADDRESS;
msg.setTo(person.getEmail());
- msg.setFrom("support@developmentgateway.org");
- msg.setSubject("Recover your password");
+ msg.setFrom(fromEmail);
+ msg.setSubject("TCDI " + country + " - Recover your password");
msg.setText("Dear " + person.getFirstName() + " " + person.getLastName() + ",\n\n"
- + "These are your new login credentials for DGToolkit.\n\n" + "Username: " + person.getUsername() + "\n"
+ + "These are your new login credentials for TCDI Admin " + country+ ".\n\n"
+ + "Username: " + person.getUsername() + "\n"
+ "Password: " + newPassword + "\n\n"
+ "At login, you will be prompted to change your password to one of your choice.\n\n" + "Thank you,\n"
- + "DG Team");
+ + "TCDI Team.");
try {
javaMailSender.send(msg);
} catch (MailException e) {
- e.printStackTrace();
+ logger.error(e.getMessage(), e);
}
}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/SessionFinderService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/SessionFinderService.java
index 909253e0..c9f0ee71 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/service/SessionFinderService.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/SessionFinderService.java
@@ -16,13 +16,13 @@
import org.hibernate.Session;
import org.springframework.stereotype.Component;
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
/**
* Spring Service allowing access to hibernate session. This is needed by
* {@link DozerModel}
- *
+ *
* @author mpostelnicu
* @see DozerModel
*/
@@ -34,7 +34,7 @@ public class SessionFinderService implements SessionFinder {
/*
* (non-Javadoc)
- *
+ *
* @see
* nl.dries.wicket.hibernate.dozer.SessionFinder#getHibernateSession(java
* .lang.Class)
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/BaseServiceEntityService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/BaseServiceEntityService.java
new file mode 100644
index 00000000..60ff63ad
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/BaseServiceEntityService.java
@@ -0,0 +1,28 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.persistence.dto.ServiceEntity;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+@Service
+public interface BaseServiceEntityService {
+
+ List findAll(String serviceName);
+
+ List findAll(String serviceName, Predicate spec);
+
+ T findOne(String serviceName, Long id);
+
+ void save(String serviceName, T entity);
+
+ void update(String serviceName, T entity);
+
+ void delete(String serviceName, T entity);
+
+ long count(String serviceName);
+
+ T newInstance();
+
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/BaseServiceEntityServiceImpl.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/BaseServiceEntityServiceImpl.java
new file mode 100644
index 00000000..7e82acf5
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/BaseServiceEntityServiceImpl.java
@@ -0,0 +1,55 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.forms.client.ServiceEntityClient;
+import org.devgateway.toolkit.forms.service.EurekaClientService;
+import org.devgateway.toolkit.persistence.dto.ServiceEntity;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public abstract class BaseServiceEntityServiceImpl implements BaseServiceEntityService {
+
+ @Override
+ public List findAll(final String serviceName) {
+ return serviceEntityClient(serviceName).findAll();
+ }
+
+ @Override
+ public List findAll(final String serviceName, final Predicate spec) {
+ return findAll(serviceName).stream()
+ .filter(spec)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public T findOne(final String serviceName, final Long id) {
+ return serviceEntityClient(serviceName).findOne(id);
+ }
+
+ @Override
+ public void save(final String serviceName, final T entity) {
+ serviceEntityClient(serviceName).save(entity);
+ }
+
+ public void update(final String serviceName, final T entity) {
+ serviceEntityClient(serviceName).update(entity);
+ }
+
+ public void delete(final String serviceName, final T entity) {
+ serviceEntityClient(serviceName).delete(entity);
+ }
+
+ @Override
+ public long count(String serviceName) {
+ return 0;
+ }
+
+ @Override
+ public T newInstance() {
+ return null;
+ }
+
+ protected abstract ServiceEntityClient serviceEntityClient(final String serviceName);
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceCategoryService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceCategoryService.java
new file mode 100644
index 00000000..0035bcdb
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceCategoryService.java
@@ -0,0 +1,6 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.persistence.dto.ServiceCategory;
+
+public interface ServiceCategoryService extends BaseServiceEntityService {
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceCategoryServiceImpl.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceCategoryServiceImpl.java
new file mode 100644
index 00000000..17999c89
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceCategoryServiceImpl.java
@@ -0,0 +1,25 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.forms.client.ServiceCategoryClient;
+import org.devgateway.toolkit.forms.client.ServiceEntityClient;
+import org.devgateway.toolkit.forms.service.EurekaClientService;
+import org.devgateway.toolkit.persistence.dto.ServiceCategory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ServiceCategoryServiceImpl extends BaseServiceEntityServiceImpl implements ServiceCategoryService {
+
+ @Autowired
+ private EurekaClientService eurekaClientService;
+
+ @Override
+ public ServiceCategory newInstance() {
+ return new ServiceCategory();
+ }
+
+ @Override
+ protected ServiceEntityClient serviceEntityClient(final String serviceName) {
+ return new ServiceCategoryClient(eurekaClientService.findByName(serviceName).getUrl());
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDatasetService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDatasetService.java
new file mode 100644
index 00000000..e543ab7f
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDatasetService.java
@@ -0,0 +1,6 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.persistence.dto.ServiceDataset;
+
+public interface ServiceDatasetService extends BaseServiceEntityService {
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDatasetServiceImpl.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDatasetServiceImpl.java
new file mode 100644
index 00000000..5625fb6d
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDatasetServiceImpl.java
@@ -0,0 +1,25 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.forms.client.ServiceDatasetClient;
+import org.devgateway.toolkit.forms.client.ServiceEntityClient;
+import org.devgateway.toolkit.forms.service.EurekaClientService;
+import org.devgateway.toolkit.persistence.dto.ServiceDataset;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ServiceDatasetServiceImpl extends BaseServiceEntityServiceImpl implements ServiceDatasetService {
+
+ @Autowired
+ private EurekaClientService eurekaClientService;
+
+ @Override
+ public ServiceDataset newInstance() {
+ return new ServiceDataset();
+ }
+
+ @Override
+ protected ServiceEntityClient serviceEntityClient(final String serviceName) {
+ return new ServiceDatasetClient(eurekaClientService.findByName(serviceName).getUrl());
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDimensionService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDimensionService.java
new file mode 100644
index 00000000..e27c371f
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDimensionService.java
@@ -0,0 +1,6 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.persistence.dto.ServiceDimension;
+
+public interface ServiceDimensionService extends BaseServiceEntityService {
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDimensionServiceImpl.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDimensionServiceImpl.java
new file mode 100644
index 00000000..eef58eab
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceDimensionServiceImpl.java
@@ -0,0 +1,25 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.forms.client.ServiceDimensionClient;
+import org.devgateway.toolkit.forms.client.ServiceEntityClient;
+import org.devgateway.toolkit.forms.service.EurekaClientService;
+import org.devgateway.toolkit.persistence.dto.ServiceDimension;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ServiceDimensionServiceImpl extends BaseServiceEntityServiceImpl implements ServiceDimensionService {
+
+ @Autowired
+ private EurekaClientService eurekaClientService;
+
+ @Override
+ public ServiceDimension newInstance() {
+ return new ServiceDimension();
+ }
+
+ @Override
+ protected ServiceEntityClient serviceEntityClient(final String serviceName) {
+ return new ServiceDimensionClient(eurekaClientService.findByName(serviceName).getUrl());
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceFilterService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceFilterService.java
new file mode 100644
index 00000000..24152e9f
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceFilterService.java
@@ -0,0 +1,6 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.persistence.dto.ServiceFilter;
+
+public interface ServiceFilterService extends BaseServiceEntityService {
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceFilterServiceImpl.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceFilterServiceImpl.java
new file mode 100644
index 00000000..372afac5
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceFilterServiceImpl.java
@@ -0,0 +1,25 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.forms.client.ServiceEntityClient;
+import org.devgateway.toolkit.forms.client.ServiceFilterClient;
+import org.devgateway.toolkit.forms.service.EurekaClientService;
+import org.devgateway.toolkit.persistence.dto.ServiceFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ServiceFilterServiceImpl extends BaseServiceEntityServiceImpl implements ServiceFilterService {
+
+ @Autowired
+ private EurekaClientService eurekaClientService;
+
+ @Override
+ public ServiceFilter newInstance() {
+ return new ServiceFilter();
+ }
+
+ @Override
+ protected ServiceEntityClient serviceEntityClient(final String serviceName) {
+ return new ServiceFilterClient(eurekaClientService.findByName(serviceName).getUrl());
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceMeasureService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceMeasureService.java
new file mode 100644
index 00000000..67efddfb
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceMeasureService.java
@@ -0,0 +1,6 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.persistence.dto.ServiceMeasure;
+
+public interface ServiceMeasureService extends BaseServiceEntityService {
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceMeasureServiceImpl.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceMeasureServiceImpl.java
new file mode 100644
index 00000000..1ab90f22
--- /dev/null
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/admin/ServiceMeasureServiceImpl.java
@@ -0,0 +1,25 @@
+package org.devgateway.toolkit.forms.service.admin;
+
+import org.devgateway.toolkit.forms.client.ServiceEntityClient;
+import org.devgateway.toolkit.forms.client.ServiceMeasureClient;
+import org.devgateway.toolkit.forms.service.EurekaClientService;
+import org.devgateway.toolkit.persistence.dto.ServiceMeasure;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ServiceMeasureServiceImpl extends BaseServiceEntityServiceImpl implements ServiceMeasureService {
+
+ @Autowired
+ private EurekaClientService eurekaClientService;
+
+ @Override
+ public ServiceMeasure newInstance() {
+ return new ServiceMeasure();
+ }
+
+ @Override
+ protected ServiceEntityClient serviceEntityClient(final String serviceName) {
+ return new ServiceMeasureClient(eurekaClientService.findByName(serviceName).getUrl());
+ }
+}
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java
index 0d668aa8..7b55513d 100644
--- a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java
+++ b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java
@@ -17,8 +17,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import javax.cache.Cache;
-import javax.cache.CacheManager;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
import java.util.Collection;
/**
@@ -68,7 +69,7 @@ public void addPentahoReportToCache(final String outputType, final String report
final byte[] buffer) {
// get the reports cache "reportsCache", declared in ehcache.xml
- final Cache cache = cm.getCache("reportsCache", String.class, byte[].class);
+ final Cache cache = cm.getCache("reportsCache");
cache.put(createCacheKey(outputType, reportName, parameters), buffer);
}
@@ -84,12 +85,11 @@ public void addPentahoReportToCache(final String outputType, final String report
public byte[] getPentahoReportFromCache(final String outputType, final String reportName, final String parameters) {
// get the reports cache "reportsCache", declared in ehcache.xml
- final Cache cache = cm.getCache("reportsCache", String.class, byte[].class);
-
- final String key = createCacheKey(outputType, reportName, parameters);
+ final Cache cache = cm.getCache("reportsCache");
- if (cache.containsKey(key)) {
- return cache.get(key);
+ if (cache != null) {
+ String key = createCacheKey(outputType, reportName, parameters);
+ return cache.get(key, byte[].class);
}
return null;
@@ -101,10 +101,10 @@ public byte[] getPentahoReportFromCache(final String outputType, final String re
public void clearPentahoReportsCache() {
// get the reports cache "reportsCache", declared in ehcache.xml
- final Cache