Commit 9ad57666 authored by LAVENIER's avatar LAVENIER
Browse files

[enh] Server: Allow LDAP connexion

[enh] Liquibase: add Person.username and userExtranet, to keep LDAP login
[fix] Fix duplicate batches when load tree (because of a LEFT JOIN on sortingMeasurement)
[enh] Clarify graphql services (split administration/account, configuration/software)
[enh] Server: add unit test on GraphQL
[fix] Remove unused hibernate-search library
parent 26dc7f01
......@@ -116,6 +116,7 @@
<spring.version>5.3.6</spring.version>
<spring-web.version>5.3.6</spring-web.version>
<spring-data.version>2.5.0</spring-data.version>
<spring-security.version>5.4.6</spring-security.version>
<querydsl.version>4.2.2</querydsl.version>
<aspectj.version>1.9.6</aspectj.version>
<javassist.version>3.27.0-GA</javassist.version>
......@@ -141,8 +142,10 @@
<graphql-java.version>16.2</graphql-java.version>
<graphql-java-tools.version>11.0.1</graphql-java-tools.version>
<graphql-java-servlet.version>11.1.0</graphql-java-servlet.version>
<graphql-spring-boot.version>7.1.0</graphql-spring-boot.version>
<spqr.version>0.11.2</spqr.version>
<kotlin.version>1.3.70</kotlin.version>
<unboundid-ldapsdk.version>4.0.14</unboundid-ldapsdk.version>
<!-- database version management -->
<liquibase.version>3.6.3</liquibase.version>
......@@ -461,6 +464,14 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</exclusion>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
......@@ -486,7 +497,6 @@
<version>${jnr-ffi.version}</version>
</dependency>
<!-- graphqQL-->
<!-- GraphQL -->
<dependency>
<groupId>com.graphql-java</groupId>
......@@ -647,16 +657,16 @@
<artifactId>hibernate-tools</artifactId>
<version>${hibernate-tools.version}</version>
</dependency>
<dependency>
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>${hibernate-search.version}</version>
</dependency>
<dependency>
</dependency>-->
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-elasticsearch</artifactId>
<version>${hibernate-search.version}</version>
</dependency>
</dependency>-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
......@@ -769,6 +779,11 @@
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>${spring-security.version}</version>
</dependency>
<!-- Compile -->
<dependency>
......@@ -1011,6 +1026,18 @@
<version>${dbunit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-test</artifactId>
<version>${graphql-spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>${unboundid-ldapsdk.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
......
......@@ -3,7 +3,7 @@ sumaris.name=SUMARiS
sumaris.persistence.liquibase.changelog.path=classpath:net/sumaris/core/db/changelog/db-changelog-master.xml
sumaris.test.data.common=data-hsqldb-01-common.xml
sumaris.test.data.additional=data-hsqldb-02-program.xml,data-hsqldb-03-data.xml,data-hsqldb-04-pendings.xml,data-hsqldb-05-extracts.xml,data-hsqldb-06-configs.xml,data-hsqldb-07-backgrounds.xml
sumaris.cache.enabled=false
spring.cache.enabled=false
# Spring: Common properties
# see https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
......
......@@ -38,6 +38,10 @@
<artifactId>spqr</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- hibernate -->
<dependency>
......@@ -48,10 +52,10 @@
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
</dependency>
<dependency>
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-elasticsearch</artifactId>
</dependency>
</dependency>-->
<!-- hibernate -->
<dependency>
......
......@@ -824,6 +824,52 @@ public class SumarisConfiguration extends PropertyPlaceholderConfigurer {
return applicationConfig.getOption(SumarisConfigurationOption.VESSEL_DEFAULT_PROGRAM_LABEL.getKey());
}
/**
* <p>find the ActiveMQ broker URL.</p>
*
* @return a {@link String}
*/
public String getActiveMQBrokerURL() {
return applicationConfig.getOption(SumarisConfigurationOption.ACTIVEMQ_BROKER_URL.getKey());
}
/**
* <p>find the ActiveMQ broker username (or null if no auth).</p>
*
* @return a {@link String}
*/
public String getActiveMQBrokerUserName() {
return applicationConfig.getOption(SumarisConfigurationOption.ACTIVEMQ_BROKER_USERNAME.getKey());
}
/**
* <p>find the ActiveMQ broker username (or null if no auth).</p>
*
* @return a {@link Integer}
*/
public String getActiveMQBrokerPassword() {
return applicationConfig.getOption(SumarisConfigurationOption.ACTIVEMQ_BROKER_PASSWORD.getKey());
}
/**
* <p>find the ActiveMQ broker username (or null if no auth).</p>
*
* @return a {@link Integer}
*/
public int getActiveMQPrefetchLimit() {
return applicationConfig.getOptionAsInt(SumarisConfigurationOption.ACTIVEMQ_PREFETCH_LIMIT.getKey());
}
/**
* <p>Is ActiveMQ enabled ?</p>
*
* @return a {@link Boolean}
*/
public boolean enableActiveMQ() {
return applicationConfig.getOptionAsBoolean(SumarisConfigurationOption.ACTIVEMQ_ENABLED.getKey());
}
/* -- protected methods -- */
/**
......
......@@ -359,6 +359,8 @@ public enum SumarisConfigurationOption implements ConfigOptionDef {
String.class,
false),
/* -- Liquibase options-- */
LIQUIBASE_RUN_AUTO(
"spring.liquibase.enabled",
n("sumaris.config.option.liquibase.should.run.description"),
......@@ -394,6 +396,40 @@ public enum SumarisConfigurationOption implements ConfigOptionDef {
boolean.class,
false),
/* -- Active MQ options-- */
ACTIVEMQ_ENABLED(
"spring.activemq.pool.enabled",
n("sumaris.config.option.spring.activemq.pool.enabled.description"),
"false",
Boolean.class),
ACTIVEMQ_BROKER_URL(
"spring.activemq.broker-url",
n("sumaris.config.option.spring.activemq.broker-url.description"),
"vm://embedded?broker.persistent=true",
String.class),
ACTIVEMQ_BROKER_USERNAME(
"spring.activemq.broker-username",
n("sumaris.config.option.spring.activemq.broker-username.description"),
"",
String.class),
ACTIVEMQ_BROKER_PASSWORD(
"spring.activemq.broker-password",
n("sumaris.config.option.spring.activemq.broker-username.description"),
"",
String.class),
ACTIVEMQ_PREFETCH_LIMIT(
"spring.activemq.prefetch.limit",
n("sumaris.config.option.spring.activemq.prefetch.limit.description"),
"10",
Integer.class),
/* -- Functional features options-- */
ENABLE_ANALYTIC_REFERENCES(
"sumaris.analyticReferences.enable",
n("sumaris.config.option.analyticReferences.enable.description"),
......
......@@ -25,7 +25,6 @@ package net.sumaris.core.dao.technical.cache;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.springframework.context.ApplicationEventPublisher;
@Slf4j
public class CacheEventLogger implements CacheEventListener<Object, Object> {
......@@ -36,6 +35,7 @@ public class CacheEventLogger implements CacheEventListener<Object, Object> {
@Override
public void onEvent(CacheEvent cacheEvent) {
log.info("custom Caching event {} {} {} {} ", cacheEvent.getType(),cacheEvent.getKey(),cacheEvent.getOldValue(),cacheEvent.getNewValue());
// TODO: send clear cache event to the ActiveMQ event queue
//log.info("custom Caching event {} {} {} {} ", cacheEvent.getType(),cacheEvent.getKey(),cacheEvent.getOldValue(),cacheEvent.getNewValue());
}
}
\ No newline at end of file
......@@ -86,19 +86,19 @@ public class TreeNodeEntities {
protected static <T extends ITreeNodeEntityBean<? extends Serializable, T>, R extends ITreeNodeEntityBean<? extends Serializable, R>>
void mapAndAppendToStream(final T node, final Stream.Builder<R> result, final BiFunction<T, R, R> mapFunction, R parentNode) {
if (node == null) return;
void mapAndAppendToStream(final T sourceNode, final Stream.Builder<R> result, final BiFunction<T, R, R> mapFunction, R parentNode) {
if (sourceNode == null) return;
// Visit current
R mappedNode = mapFunction.apply(node, parentNode);
R targetNode = mapFunction.apply(sourceNode, parentNode);
// Add visited node
result.add(mappedNode);
result.add(targetNode);
// Process children
if (CollectionUtils.isNotEmpty(node.getChildren())) {
if (CollectionUtils.isNotEmpty(sourceNode.getChildren())) {
// Recursive call
node.getChildren().forEach(child -> mapAndAppendToStream(child, result, mapFunction, mappedNode));
sourceNode.getChildren().forEach(child -> mapAndAppendToStream(child, result, mapFunction, targetNode));
}
}
}
......@@ -57,6 +57,15 @@ public class Beans {
// helper class does not instantiate
}
public static <C> C newInstance(Class<C> objectClass) {
Preconditions.checkNotNull(objectClass, "Cant create an instance of 'null'");
try {
return objectClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new SumarisTechnicalException(e);
}
}
/**
* <p>getList.</p>
*
......
......@@ -79,6 +79,10 @@ sumaris.config.option.server.port.description=
sumaris.config.option.site.doc.url.description=
sumaris.config.option.site.partners.department.ids.description=
sumaris.config.option.site.url.description=
sumaris.config.option.spring.activemq.broker-url.description=
sumaris.config.option.spring.activemq.broker-username.description=
sumaris.config.option.spring.activemq.pool.enabled.description=
sumaris.config.option.spring.activemq.prefetch.limit.description=
sumaris.config.option.spring.datasource.driver-class-name.description=
sumaris.config.option.spring.datasource.password.description=
sumaris.config.option.spring.datasource.url.description=
......
......@@ -79,6 +79,10 @@ sumaris.config.option.server.port.description=
sumaris.config.option.site.doc.url.description=
sumaris.config.option.site.partners.department.ids.description=
sumaris.config.option.site.url.description=
sumaris.config.option.spring.activemq.broker-url.description=
sumaris.config.option.spring.activemq.broker-username.description=
sumaris.config.option.spring.activemq.pool.enabled.description=
sumaris.config.option.spring.activemq.prefetch.limit.description=
sumaris.config.option.spring.datasource.driver-class-name.description=
sumaris.config.option.spring.datasource.password.description=
sumaris.config.option.spring.datasource.url.description=
......
......@@ -72,14 +72,14 @@
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
</dependency>
<dependency>
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-elasticsearch</artifactId>
</dependency>
</dependency>-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
......
......@@ -48,9 +48,15 @@ spring.jpa.hibernate.ddl-auto=none
spring.liquibase.enabled=false
spring.liquibase.compact.enabled=false
spring.jms.enabled=false
#spring.activemq.pool.enabled=false
#spring.activemq.broker-url=tcp://localhost:61616
# Logging Levels
logging.level.ROOT=info
logging.level.net.sumaris=info
logging.level.net.sumaris.core.action.data=debug
#logging.level.net.sumaris.core.dao.technical.liquibase=debug
logging.level.org.springframework=warn
logging.level.org.nuiton=warn
......
......@@ -22,6 +22,7 @@ package net.sumaris.core;
* #L%
*/
import com.fasterxml.jackson.databind.ObjectMapper;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.action.ActionUtils;
......@@ -31,15 +32,18 @@ import net.sumaris.core.service.technical.ConfigurationService;
import net.sumaris.core.util.ApplicationUtils;
import net.sumaris.core.util.I18nUtil;
import net.sumaris.core.util.StringUtils;
import org.hibernate.cache.jcache.ConfigSettings;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
......@@ -54,23 +58,24 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManager;
/**
* <p>
* Application class.
* </p>
*
*
*/
@SpringBootApplication(
exclude = {
LiquibaseAutoConfiguration.class,
FreeMarkerAutoConfiguration.class,
JmsAutoConfiguration.class,
JndiConnectionFactoryAutoConfiguration.class,
ActiveMQAutoConfiguration.class
},
scanBasePackages = {
"net.sumaris.core"
}
exclude = {
LiquibaseAutoConfiguration.class,
FreeMarkerAutoConfiguration.class,
JndiConnectionFactoryAutoConfiguration.class,
//JmsAutoConfiguration.class,
//ActiveMQAutoConfiguration.class
},
scanBasePackages = {
"net.sumaris.core"
}
)
@EnableAsync
@Component("core-application")
......@@ -141,7 +146,7 @@ public class Application {
*/
public static void main(String[] args) {
run(args, null);
}
}
@Bean
public SumarisConfiguration configuration() {
......@@ -210,8 +215,11 @@ public class Application {
} catch (NoSuchBeanDefinitionException e) {
// continue
}
}
@Bean
@ConditionalOnMissingBean({ObjectMapper.class})
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
......@@ -51,9 +51,10 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass({javax.cache.Cache.class, org.ehcache.Cache.class})
@ConditionalOnProperty(
prefix = "sumaris.cache",
name = {"enabled"},
matchIfMissing = true)
name = "spring.cache.enabled",
havingValue = "true",
matchIfMissing = true
)
@EnableCaching
@Slf4j
public class CacheConfiguration extends CachingConfigurerSupport {
......
......@@ -20,24 +20,25 @@
* #L%
*/
package net.sumaris.server.config;
package net.sumaris.core.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.util.StringUtils;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQPrefetchPolicy;
import org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.apache.activemq.broker.BrokerService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
......@@ -48,44 +49,56 @@ import javax.jms.ConnectionFactory;
@Configuration(proxyBeanMethods = false)
@Slf4j
@EnableJms
@ConditionalOnProperty(
name = "spring.jms.enabled",
havingValue = "true"
)
public class JmsConfiguration {
public static final String CONTAINER_FACTORY_NAME = "jmsListenerContainerFactory";
@Value("${spring.activemq.broker-url:vm://localhost}")
private String brokerUrl;
@Bean
@ConditionalOnMissingBean({JmsTemplate.class})
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
public JmsTemplate jmsTemplate(CachingConnectionFactory cachingConnectionFactory, MessageConverter messageConverter) {
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
jmsTemplate.setMessageConverter(messageConverter);
return jmsTemplate;
}
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(
ConnectionFactory connectionFactory,
TaskExecutor taskExecutor) {
CachingConnectionFactory cachingConnectionFactory,
MessageConverter messageConverter,
TaskExecutor taskExecutor
) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cachingConnectionFactory);
factory.setMessageConverter(messageConverter);
factory.setTaskExecutor(taskExecutor);
factory.setErrorHandler(t -> log.error("An error has occurred in the JMS transaction: " + t.getMessage(), t));
factory.setConnectionFactory(connectionFactory);
return factory;
}
@Bean
public MessageConverter jacksonJmsMessageConverter(ObjectMapper objectMapper) {
public MessageConverter messageConverter(ObjectMapper jacksonObjectMapper) {
// Serialize message content to json using TextMessage
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(objectMapper);
converter.setObjectMapper(jacksonObjectMapper);
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
converter.setEncodingPropertyName("_encoding");
return converter;
}
@Bean
@ConditionalOnProperty(
prefix = "spring.activemq",
name = {"broker-url"}
)
@ConditionalOnClass({KahaDBPersistenceAdapter.class})
public ConnectionFactory connectionFactory(SumarisServerConfiguration config) {
public CachingConnectionFactory cachingConnectionFactory(ConnectionFactory connectionFactory) {
return new CachingConnectionFactory(connectionFactory);
}
@Bean
public ConnectionFactory connectionFactory(SumarisConfiguration config) {
String url = config.getActiveMQBrokerURL();
int prefetchLimit = config.getActiveMQPrefetchLimit();
......@@ -111,7 +124,30 @@ public class JmsConfiguration {
log.info(String.format("Starting JMS broker... {type: 'ActiveMQ', url: '%s'}...", url));
}
connectionFactory.setTrustAllPackages(true);
return connectionFactory;
}
@Bean
@ConditionalOnProperty(
prefix = "spring.activemq.",
name = "broker-url",
havingValue = "vm://localhost",
matchIfMissing = true
)
public BrokerService brokerService() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.addConnector("vm://localhost");
broker.setPersistent(true);
return broker;
}
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper() {
return new ObjectMapper();
}
}
......@@ -26,10 +26,7 @@ import net.sumaris.core.dao.technical.jpa.SumarisJpaRepository;
import net.sumaris.core.model.administration.user.Person;
import net.sumaris.core.vo.administration.user.PersonVO;
import java.util.Optional;
public interface PersonRepository extends SumarisJpaRepository<Person, Integer, PersonVO>, PersonSpecifications {
boolean existsByEmailMD5(String emailMD5);
}