Commit 058b382d authored by LAVENIER's avatar LAVENIER
Browse files

Merge branch 'release/1.8.5'

parents aca0afa8 91e3bfcc
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<groupId>net.sumaris</groupId> <groupId>net.sumaris</groupId>
<artifactId>sumaris-pod</artifactId> <artifactId>sumaris-pod</artifactId>
<version>1.8.4</version> <version>1.8.5</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>SUMARiS</name> <name>SUMARiS</name>
<description>SUMARiS :: Maven parent</description> <description>SUMARiS :: Maven parent</description>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>net.sumaris</groupId> <groupId>net.sumaris</groupId>
<artifactId>sumaris-pod</artifactId> <artifactId>sumaris-pod</artifactId>
<version>1.8.4</version> <version>1.8.5</version>
</parent> </parent>
<artifactId>sumaris-core-extraction</artifactId> <artifactId>sumaris-core-extraction</artifactId>
......
...@@ -76,6 +76,10 @@ public class ExtractionConfiguration { ...@@ -76,6 +76,10 @@ public class ExtractionConfiguration {
} }
} }
public String getJdbcURL() {
return delegate.getJdbcURL();
}
public boolean enableExtractionProduct() { public boolean enableExtractionProduct() {
return getApplicationConfig().getOptionAsBoolean(ExtractionConfigurationOption.EXTRACTION_PRODUCT_ENABLE.getKey()); return getApplicationConfig().getOptionAsBoolean(ExtractionConfigurationOption.EXTRACTION_PRODUCT_ENABLE.getKey());
} }
...@@ -92,4 +96,16 @@ public class ExtractionConfiguration { ...@@ -92,4 +96,16 @@ public class ExtractionConfiguration {
return delegate.getApplicationConfig(); return delegate.getApplicationConfig();
} }
/**
* Extraction query timeout, in millisecond
* @return
*/
public int getExtractionQueryTimeout() {
return getApplicationConfig().getOptionAsInt(ExtractionConfigurationOption.EXTRACTION_QUERY_TIMEOUT.getKey());
}
public String getCsvSeparator() {
return delegate.getCsvSeparator();
}
} }
...@@ -72,6 +72,13 @@ public enum ExtractionConfigurationOption implements ConfigOptionDef { ...@@ -72,6 +72,13 @@ public enum ExtractionConfigurationOption implements ConfigOptionDef {
n("sumaris.config.option.extraction.product.enable.description"), n("sumaris.config.option.extraction.product.enable.description"),
Boolean.FALSE.toString(), Boolean.FALSE.toString(),
Boolean.class, Boolean.class,
false),
EXTRACTION_QUERY_TIMEOUT(
"sumaris.extraction.query.timeout",
n("sumaris.config.option.extraction.query.timeout.description"),
String.valueOf(5 * 60 * 1000), // 5min
Integer.class,
false) false)
; ;
......
...@@ -26,13 +26,13 @@ import com.google.common.base.Preconditions; ...@@ -26,13 +26,13 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.config.SumarisConfiguration;
import net.sumaris.core.dao.technical.Daos; import net.sumaris.core.dao.technical.Daos;
import net.sumaris.core.dao.technical.DatabaseType; import net.sumaris.core.dao.technical.DatabaseType;
import net.sumaris.core.dao.technical.hibernate.HibernateDaoSupport; import net.sumaris.core.dao.technical.hibernate.HibernateDaoSupport;
import net.sumaris.core.dao.technical.schema.SumarisDatabaseMetadata; import net.sumaris.core.dao.technical.schema.SumarisDatabaseMetadata;
import net.sumaris.core.dao.technical.schema.SumarisTableMetadata; import net.sumaris.core.dao.technical.schema.SumarisTableMetadata;
import net.sumaris.core.exception.SumarisTechnicalException; import net.sumaris.core.exception.SumarisTechnicalException;
import net.sumaris.core.extraction.config.ExtractionConfiguration;
import net.sumaris.core.extraction.dao.technical.schema.SumarisTableMetadatas; import net.sumaris.core.extraction.dao.technical.schema.SumarisTableMetadatas;
import net.sumaris.core.extraction.vo.ExtractionContextVO; import net.sumaris.core.extraction.vo.ExtractionContextVO;
import net.sumaris.core.extraction.vo.ExtractionFilterVO; import net.sumaris.core.extraction.vo.ExtractionFilterVO;
...@@ -41,9 +41,12 @@ import net.sumaris.core.service.referential.ReferentialService; ...@@ -41,9 +41,12 @@ import net.sumaris.core.service.referential.ReferentialService;
import net.sumaris.core.util.StringUtils; import net.sumaris.core.util.StringUtils;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.query.internal.NativeQueryImpl;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.orm.hibernate5.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.persistence.Query; import javax.persistence.Query;
...@@ -62,7 +65,7 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport { ...@@ -62,7 +65,7 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport {
protected static final String XSL_ORACLE_FILENAME = "xmlQuery/queryOracle.xsl"; protected static final String XSL_ORACLE_FILENAME = "xmlQuery/queryOracle.xsl";
@Autowired @Autowired
protected SumarisConfiguration configuration; protected ExtractionConfiguration configuration;
@Autowired @Autowired
protected ReferentialService referentialService; protected ReferentialService referentialService;
...@@ -73,15 +76,17 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport { ...@@ -73,15 +76,17 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport {
@Autowired @Autowired
protected SumarisDatabaseMetadata databaseMetadata; protected SumarisDatabaseMetadata databaseMetadata;
protected DatabaseType databaseType = null; protected DatabaseType databaseType = null;
protected String dropTableQuery; protected String dropTableQuery;
protected int hibernateQueryTimeout;
@PostConstruct @PostConstruct
public void init() { public void init() {
this.databaseType = Daos.getDatabaseType(configuration.getJdbcURL()); this.databaseType = Daos.getDatabaseType(configuration.getJdbcURL());
this.dropTableQuery = getDialect().getDropTableString("%s"); this.dropTableQuery = getDialect().getDropTableString("%s");
this.hibernateQueryTimeout = Math.max(1, Math.round(configuration.getExtractionQueryTimeout() / 1000));
} }
protected Dialect getDialect() { protected Dialect getDialect() {
...@@ -103,19 +108,19 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport { ...@@ -103,19 +108,19 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected <R> List<R> query(String query, Class<R> jdbcClass) { protected <R> List<R> query(String query, Class<R> jdbcClass) {
Query nativeQuery = getEntityManager().createNativeQuery(query); Query nativeQuery = createNativeQuery(query);
Stream<R> resultStream = (Stream<R>) nativeQuery.getResultStream().map(jdbcClass::cast); Stream<R> resultStream = (Stream<R>) nativeQuery.getResultStream().map(jdbcClass::cast);
return resultStream.collect(Collectors.toList()); return resultStream.collect(Collectors.toList());
} }
protected <R> List<R> query(String query, Function<Object[], R> rowMapper) { protected <R> List<R> query(String query, Function<Object[], R> rowMapper) {
Query nativeQuery = getEntityManager().createNativeQuery(query); Query nativeQuery = createNativeQuery(query);
Stream<Object[]> resultStream = (Stream<Object[]>) nativeQuery.getResultStream(); Stream<Object[]> resultStream = (Stream<Object[]>) nativeQuery.getResultStream();
return resultStream.map(rowMapper).collect(Collectors.toList()); return resultStream.map(rowMapper).collect(Collectors.toList());
} }
protected <R> List<R> query(String query, Function<Object[], R> rowMapper, int offset, int size) { protected <R> List<R> query(String query, Function<Object[], R> rowMapper, int offset, int size) {
Query nativeQuery = getEntityManager().createNativeQuery(query) Query nativeQuery = createNativeQuery(query)
.setFirstResult(offset) .setFirstResult(offset)
.setMaxResults(size); .setMaxResults(size);
Stream<Object[]> resultStream = (Stream<Object[]>) nativeQuery.getResultStream(); Stream<Object[]> resultStream = (Stream<Object[]>) nativeQuery.getResultStream();
...@@ -125,7 +130,7 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport { ...@@ -125,7 +130,7 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport {
protected int queryUpdate(String query) { protected int queryUpdate(String query) {
if (log.isDebugEnabled()) log.debug("execute update: " + query); if (log.isDebugEnabled()) log.debug("execute update: " + query);
Query nativeQuery = getEntityManager().createNativeQuery(query); Query nativeQuery = createNativeQuery(query);
return nativeQuery.executeUpdate(); return nativeQuery.executeUpdate();
} }
...@@ -154,7 +159,7 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport { ...@@ -154,7 +159,7 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport {
protected long queryCount(String query) { protected long queryCount(String query) {
if (log.isDebugEnabled()) log.debug("aggregate: " + query); if (log.isDebugEnabled()) log.debug("aggregate: " + query);
Query nativeQuery = getEntityManager().createNativeQuery(query); Query nativeQuery = createNativeQuery(query);
Object result = nativeQuery.getSingleResult(); Object result = nativeQuery.getSingleResult();
if (result == null) if (result == null)
throw new DataRetrievalFailureException(String.format("query count result is null.\nquery: %s", query)); throw new DataRetrievalFailureException(String.format("query count result is null.\nquery: %s", query));
...@@ -237,4 +242,19 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport { ...@@ -237,4 +242,19 @@ public abstract class ExtractionBaseDaoImpl extends HibernateDaoSupport {
queryUpdate(sql); queryUpdate(sql);
}); });
} }
/**
* Create a native query, with the timeout for extraction (should b longer than the default timeout)
* @param sql
* @return
*/
protected Query createNativeQuery(String sql) {
Query query = getEntityManager().createNativeQuery(sql);
// Set the query timeout (in seconds)
query.unwrap(org.hibernate.query.Query.class)
.setTimeout(this.hibernateQueryTimeout);
return query;
}
} }
...@@ -62,7 +62,7 @@ public interface AggregationService { ...@@ -62,7 +62,7 @@ public interface AggregationService {
* @param productId * @param productId
*/ */
@Transactional @Transactional
void updateProduct(int productId); AggregationTypeVO updateProduct(int productId);
/** /**
* Do an aggregate * Do an aggregate
...@@ -113,11 +113,11 @@ public interface AggregationService { ...@@ -113,11 +113,11 @@ public interface AggregationService {
@Nullable String sort, @Nullable String sort,
@Nullable SortDirection direction); @Nullable SortDirection direction);
@Transactional @Transactional(timeout = 10000000)
AggregationTypeVO save(AggregationTypeVO type, @Nullable ExtractionFilterVO filter); AggregationTypeVO save(AggregationTypeVO type, @Nullable ExtractionFilterVO filter);
@Async @Async
@Transactional(timeout = -1, propagation = Propagation.REQUIRES_NEW) @Transactional(timeout = 10000000, propagation = Propagation.REQUIRES_NEW)
CompletableFuture<AggregationTypeVO> asyncSave(AggregationTypeVO type, @Nullable ExtractionFilterVO filter); CompletableFuture<AggregationTypeVO> asyncSave(AggregationTypeVO type, @Nullable ExtractionFilterVO filter);
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW) @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
......
...@@ -31,6 +31,7 @@ import com.google.common.collect.Maps; ...@@ -31,6 +31,7 @@ import com.google.common.collect.Maps;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.dao.technical.SortDirection; import net.sumaris.core.dao.technical.SortDirection;
import net.sumaris.core.exception.DataNotFoundException;
import net.sumaris.core.exception.SumarisTechnicalException; import net.sumaris.core.exception.SumarisTechnicalException;
import net.sumaris.core.extraction.config.ExtractionCacheConfiguration; import net.sumaris.core.extraction.config.ExtractionCacheConfiguration;
import net.sumaris.core.extraction.dao.AggregationDao; import net.sumaris.core.extraction.dao.AggregationDao;
...@@ -324,21 +325,14 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -324,21 +325,14 @@ public class AggregationServiceImpl implements AggregationService {
} }
@Override @Override
public void updateProduct(int productId) { public AggregationTypeVO updateProduct(int productId) {
ExtractionProductVO target = productService.findById(productId, ExtractionProductFetchOptions.FOR_UPDATE).orElse(null); ExtractionProductVO target = productService.findById(productId, ExtractionProductFetchOptions.FOR_UPDATE)
Collection<String> existingTablesToDrop = Lists.newArrayList(target.getTableNames()); .orElseThrow(() -> new DataNotFoundException(String.format("Unknown product {id: %s}", productId)));
Collection<String> tablesToDrop = Lists.newArrayList(target.getTableNames());
// Read filter // Read filter
ExtractionFilterVO filter = null; ExtractionFilterVO filter = readFilter(target.getFilter());
if (StringUtils.isNotBlank(target.getFilter())) {
try {
filter = objectMapper.readValue(target.getFilter(), ExtractionFilterVO.class);
}
catch(Exception e) {
throw new SumarisTechnicalException("Unparseable filter string: " + e.getMessage(), e);
}
}
String rawFormat = target.getRawFormatLabel(); String rawFormat = target.getRawFormatLabel();
if (rawFormat.startsWith(AggSpecification.FORMAT_PREFIX)) { if (rawFormat.startsWith(AggSpecification.FORMAT_PREFIX)) {
...@@ -360,7 +354,10 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -360,7 +354,10 @@ public class AggregationServiceImpl implements AggregationService {
productService.save(target); productService.save(target);
// Drop old tables // Drop old tables
dropTables(existingTablesToDrop); dropTables(tablesToDrop);
// Transform to type
return toAggregationType(target);
} }
@Override @Override
...@@ -374,7 +371,7 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -374,7 +371,7 @@ public class AggregationServiceImpl implements AggregationService {
Preconditions.checkNotNull(source); Preconditions.checkNotNull(source);
Preconditions.checkNotNull(source.getLabel()); Preconditions.checkNotNull(source.getLabel());
Preconditions.checkNotNull(source.getName()); Preconditions.checkNotNull(source.getName());
Collection<String> existingTablesToDrop = Lists.newArrayList(); Collection<String> tablesToDrop = Lists.newArrayList();
// Load the product // Load the product
ExtractionProductVO target = null; ExtractionProductVO target = null;
...@@ -394,7 +391,8 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -394,7 +391,8 @@ public class AggregationServiceImpl implements AggregationService {
String previousLabel = target.getLabel(); String previousLabel = target.getLabel();
Preconditions.checkArgument(previousLabel.equalsIgnoreCase(source.getLabel()), "Cannot change a product label"); Preconditions.checkArgument(previousLabel.equalsIgnoreCase(source.getLabel()), "Cannot change a product label");
filter = filter != null ? filter : readFilterString(target.getFilter()); // If not given in arguments, parse the product's filter string
filter = filter != null ? filter : readFilter(target.getFilter());
} }
...@@ -413,9 +411,11 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -413,9 +411,11 @@ public class AggregationServiceImpl implements AggregationService {
// Should clean existing table // Should clean existing table
if (!isNew) { if (!isNew) {
existingTablesToDrop.addAll(target.getTableNames()); tablesToDrop.addAll(Beans.getList(target.getTableNames()));
} }
// Execute the aggregation
{
// Prepare a executable type (with label=format) // Prepare a executable type (with label=format)
AggregationTypeVO executableType = new AggregationTypeVO(); AggregationTypeVO executableType = new AggregationTypeVO();
executableType.setLabel(source.getRawFormatLabel()); executableType.setLabel(source.getRawFormatLabel());
...@@ -424,8 +424,9 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -424,8 +424,9 @@ public class AggregationServiceImpl implements AggregationService {
// Execute the aggregation // Execute the aggregation
AggregationContextVO context = aggregate(executableType, filter, null); AggregationContextVO context = aggregate(executableType, filter, null);
// Update product tables, using the aggregation result // Update product, using the aggregation result
toProductVO(context, target); toProductVO(context, target);
}
// Copy some properties from the given type // Copy some properties from the given type
target.setName(source.getName()); target.setName(source.getName());
...@@ -464,7 +465,7 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -464,7 +465,7 @@ public class AggregationServiceImpl implements AggregationService {
target = productService.save(target); target = productService.save(target);
// Drop old tables // Drop old tables
dropTables(existingTablesToDrop); dropTables(tablesToDrop);
// Transform back to type // Transform back to type
return toAggregationType(target); return toAggregationType(target);
...@@ -688,8 +689,9 @@ public class AggregationServiceImpl implements AggregationService { ...@@ -688,8 +689,9 @@ public class AggregationServiceImpl implements AggregationService {
} }
} }
protected ExtractionFilterVO readFilterString(String json) { protected ExtractionFilterVO readFilter(String json) {
if (StringUtils.isBlank(json)) return null; if (StringUtils.isBlank(json)) return null;
try { try {
return objectMapper.readValue(json, ExtractionFilterVO.class); return objectMapper.readValue(json, ExtractionFilterVO.class);
} }
......
...@@ -22,15 +22,17 @@ package net.sumaris.core.extraction.service; ...@@ -22,15 +22,17 @@ package net.sumaris.core.extraction.service;
* #L% * #L%
*/ */
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.dao.schema.DatabaseSchemaDao; import net.sumaris.core.dao.schema.DatabaseSchemaDao;
import net.sumaris.core.dao.technical.SortDirection; import net.sumaris.core.dao.technical.SortDirection;
import net.sumaris.core.dao.technical.extraction.ExtractionProductRepository;
import net.sumaris.core.dao.technical.model.IEntity; import net.sumaris.core.dao.technical.model.IEntity;
import net.sumaris.core.dao.technical.schema.SumarisDatabaseMetadata; import net.sumaris.core.dao.technical.schema.SumarisDatabaseMetadata;
import net.sumaris.core.dao.technical.schema.SumarisTableMetadata; import net.sumaris.core.dao.technical.schema.SumarisTableMetadata;
...@@ -62,6 +64,7 @@ import net.sumaris.core.model.referential.location.Location; ...@@ -62,6 +64,7 @@ import net.sumaris.core.model.referential.location.Location;
import net.sumaris.core.model.referential.location.LocationLevelEnum; import net.sumaris.core.model.referential.location.LocationLevelEnum;
import net.sumaris.core.model.technical.extraction.ExtractionCategoryEnum; import net.sumaris.core.model.technical.extraction.ExtractionCategoryEnum;
import net.sumaris.core.model.technical.extraction.IExtractionFormat; import net.sumaris.core.model.technical.extraction.IExtractionFormat;
import net.sumaris.core.model.technical.history.ProcessingFrequencyEnum;
import net.sumaris.core.service.referential.LocationService; import net.sumaris.core.service.referential.LocationService;
import net.sumaris.core.service.referential.ReferentialService; import net.sumaris.core.service.referential.ReferentialService;
import net.sumaris.core.util.*; import net.sumaris.core.util.*;
...@@ -103,6 +106,9 @@ public class ExtractionServiceImpl implements ExtractionService { ...@@ -103,6 +106,9 @@ public class ExtractionServiceImpl implements ExtractionService {
@Autowired @Autowired
protected ExtractionConfiguration configuration; protected ExtractionConfiguration configuration;
@Autowired
private ObjectMapper objectMapper;
@Autowired @Autowired
protected DataSource dataSource; protected DataSource dataSource;
...@@ -113,7 +119,7 @@ public class ExtractionServiceImpl implements ExtractionService { ...@@ -113,7 +119,7 @@ public class ExtractionServiceImpl implements ExtractionService {
protected ExtractionStrategyDao extractionStrategyDao; protected ExtractionStrategyDao extractionStrategyDao;
@Autowired @Autowired
protected ExtractionProductRepository extractionProductRepository; protected ExtractionProductService productService;
@Autowired @Autowired
protected ExtractionTableDao extractionTableDao; protected ExtractionTableDao extractionTableDao;
...@@ -178,18 +184,23 @@ public class ExtractionServiceImpl implements ExtractionService { ...@@ -178,18 +184,23 @@ public class ExtractionServiceImpl implements ExtractionService {
@Override @Override
public List<ExtractionTypeVO> getLiveExtractionTypes() { public List<ExtractionTypeVO> getLiveExtractionTypes() {
MutableInt id = new MutableInt(-1); MutableInt id = new MutableInt(-1);
return Arrays.stream(LiveFormatEnum.values()) return Arrays.stream(LiveFormatEnum.values())
// Sort by label
.sorted(Comparator.comparing(LiveFormatEnum::getLabel))
.map(format -> { .map(format -> {
ExtractionTypeVO type = new ExtractionTypeVO(); ExtractionTypeVO type = new ExtractionTypeVO();
type.setId(id.getValue());
// Generate a negative an unique id
type.setId(-1 * format.hashCode());
type.setLabel(format.getLabel().toLowerCase()); type.setLabel(format.getLabel().toLowerCase());
type.setCategory(ExtractionCategoryEnum.LIVE); type.setCategory(ExtractionCategoryEnum.LIVE);
type.setSheetNames(format.getSheetNames()); type.setSheetNames(format.getSheetNames());
type.setStatusId(StatusEnum.TEMPORARY.getId()); // = not public by default type.setStatusId(StatusEnum.TEMPORARY.getId()); // = not public by default
type.setVersion(format.getVersion()); type.setVersion(format.getVersion());
type.setLiveFormat(format); type.setLiveFormat(format);
id.decrement();
return type; return type;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
...@@ -234,7 +245,7 @@ public class ExtractionServiceImpl implements ExtractionService { ...@@ -234,7 +245,7 @@ public class ExtractionServiceImpl implements ExtractionService {
switch (type.getCategory()) { switch (type.getCategory()) {