Commit 5b1ed5d5 authored by Matija Obreza's avatar Matija Obreza

Merge branch '457-filtercode-v2' into 'master'

Resolve "FilterCode v2"

Closes #457

See merge request genesys-pgr/genesys-server!424
parents be158fe1 0cb1ab49
......@@ -647,6 +647,11 @@
<artifactId>amphibian-client-resttemplate</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.hashids</groupId>
<artifactId>hashids</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
<build>
......
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -18,6 +18,8 @@ package org.genesys.catalog.model;
import org.genesys.blocks.model.EntityId;
import org.hibernate.annotations.Type;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
......@@ -53,6 +55,15 @@ public class ShortFilter implements Serializable, EntityId {
@Type(type = "org.hibernate.type.TextType")
private String json;
/**
* The new code to use for an old filter.
*
* Filters with {@link #redirect} == null are using the current shortCode syntax.
*/
@JsonIgnore
@Column(length = 50, nullable = true, name = "redir")
private String redirect;
/**
* Default constructor.
*/
......@@ -125,4 +136,12 @@ public class ShortFilter implements Serializable, EntityId {
public int hashCode() {
return Objects.hash(id, code, json);
}
public void setRedirect(String redirectCode) {
this.redirect = redirectCode;
}
public String getRedirect() {
return redirect;
}
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,8 +15,11 @@
*/
package org.genesys.catalog.persistence;
import java.util.List;
import org.genesys.catalog.model.ShortFilter;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.stereotype.Repository;
......@@ -35,10 +38,19 @@ public interface ShortFilterRepository extends JpaRepository<ShortFilter, Long>,
ShortFilter findByCode(String shortName);
/**
* Gets the ShortFilter by jsonFilters.
* Gets the ShortFilters by jsonFilters.
*
* @param json the json
* @return the ShortFilter
*/
ShortFilter findByJson(String json);
List<ShortFilter> findByJson(String json);
/**
* Gets the current ShortFilter for specified JSON.
*
* @param json the json
* @return the current ShortFilter for JSON
*/
@Query("select sf from ShortFilter sf where json=?1 and redirect is null")
ShortFilter getCurrentForJson(String json);
}
......@@ -60,14 +60,6 @@ public interface ShortFilterService {
*/
ShortFilter loadByJSON(String json);
/**
* Load ShortFilter by json filters or short name.
*
* @param searchString json filters or short name of ShortFilter
* @return found ShortFilter
*/
ShortFilter load(String searchString);
/**
* Returns the Java filter object by short name.
*
......@@ -94,4 +86,14 @@ public interface ShortFilterService {
public T filter;
public String filterCode;
}
/**
* Upgrade a {@link ShortFilter} to the current version of the short code.
*
* Maintains the original JSON + registers a new entry!
*
* @param shortFilter the filter
* @return filterCode by current standards
*/
ShortFilter upgradeFilterCode(ShortFilter shortFilter);
}
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -18,18 +18,22 @@ package org.genesys.catalog.service.impl;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.genesys.blocks.model.filters.BasicModelFilter;
import org.genesys.catalog.model.ShortFilter;
import org.genesys.catalog.persistence.ShortFilterRepository;
import org.genesys.catalog.service.ShortFilterService;
import org.hashids.Hashids;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
......@@ -47,15 +51,24 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
@Service
@Transactional(readOnly = true)
public class ShortFilterServiceImpl implements ShortFilterService {
public class ShortFilterServiceImpl implements ShortFilterService, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(ShortFilterServiceImpl.class);
public static final String CODE_PREFIX_V1 = "v1";
public static final String CODE_PREFIX_V2 = "v2";
public static final String CODE_PREFIX_CURRENT = CODE_PREFIX_V2;
@Autowired
private ShortFilterRepository shortFilterRepository;
private final ObjectMapper mapper;
private final ObjectMapper strictMapper;
@Value("${hashids.salt}")
private String hashidsSalt;
private Hashids hashids;
public ShortFilterServiceImpl() {
mapper = new ObjectMapper();
strictMapper = new ObjectMapper();
......@@ -73,6 +86,12 @@ public class ShortFilterServiceImpl implements ShortFilterService {
strictMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
}
@Override
public void afterPropertiesSet() throws Exception {
// At least 4 chars for HashId
hashids = new Hashids(this.hashidsSalt, 4);
}
@Override
public String normalizeFilter(final BasicModelFilter<?, ?> filter) throws IOException {
......@@ -106,7 +125,7 @@ public class ShortFilterServiceImpl implements ShortFilterService {
try {
final String normalizedFilter = normalizeFilter(filter);
ShortFilter shortFilter = shortFilterRepository.findByJson(normalizedFilter);
ShortFilter shortFilter = shortFilterRepository.getCurrentForJson(normalizedFilter);
if (shortFilter != null) {
// return existing shortFilter
......@@ -114,7 +133,7 @@ public class ShortFilterServiceImpl implements ShortFilterService {
}
// store new filter
final String shortName = generateShortNameV1();
final String shortName = generateFilterCode();
shortFilter = shortFilterRepository.save(new ShortFilter(shortName, normalizedFilter));
return shortFilter.getCode();
......@@ -123,15 +142,39 @@ public class ShortFilterServiceImpl implements ShortFilterService {
return null;
}
}
private String generateFilterCode() {
return generateFilterCodeV2();
}
/**
* Generate short name V1.
*
* @return the shortName
* @deprecated Use {@link #generateFilterCodeV2()}
*/
@Deprecated
private String generateShortNameV1() {
for (int i = 0; i < 10; i++) {
final String code = "v1" + UUID.randomUUID().toString().replace("-", "");
final String code = CODE_PREFIX_V1.concat(UUID.randomUUID().toString().replace("-", ""));
// return shortName if unique
if (shortFilterRepository.findByCode(code) == null) {
return code;
}
}
throw new RuntimeException("Failed to generate a unique filters short name in several attempts");
}
/**
* Generate short name V2.
*
* @return the shortName
*/
private String generateFilterCodeV2() {
for (int i = 0; i < 10; i++) {
String hashId = hashids.encode(System.currentTimeMillis());
final String code = CODE_PREFIX_V2.concat(hashId);
// return shortName if unique
if (shortFilterRepository.findByCode(code) == null) {
return code;
......@@ -147,21 +190,29 @@ public class ShortFilterServiceImpl implements ShortFilterService {
@Override
public ShortFilter loadByJSON(final String jsonFilters) {
return shortFilterRepository.findByJson(jsonFilters);
return shortFilterRepository.getCurrentForJson(jsonFilters);
}
@Override
public ShortFilter load(final String searchString) {
ShortFilter shortFilter = shortFilterRepository.findByCode(searchString);
public <T extends BasicModelFilter<?, ?>> T filterByCode(String code, Class<T> clazz) throws IOException {
ShortFilter shortFilter = shortFilterRepository.findByCode(code == null ? "" : code);
if (shortFilter == null) {
shortFilter = shortFilterRepository.findByJson(searchString);
throw new NullPointerException("No filter with " + code);
}
return shortFilter;
}
@Override
public <T extends BasicModelFilter<?, ?>> T filterByCode(String code, Class<T> clazz) throws IOException {
ShortFilter shortFilter = shortFilterRepository.findByCode(code == null ? "" : code);
// handle redirects
Set<String> codes = new HashSet<>();
codes.add(shortFilter.getCode());
while (shortFilter.getRedirect() != null) {
if (codes.contains(shortFilter.getRedirect())) {
LOG.warn("Cyclic shortFilter redirect for {}", shortFilter.getRedirect());
return strictMapper.readValue(shortFilter.getJson(), clazz);
}
shortFilter = shortFilterRepository.findByCode(shortFilter.getRedirect());
codes.add(shortFilter.getCode());
}
return strictMapper.readValue(shortFilter.getJson(), clazz);
}
......@@ -231,4 +282,46 @@ public class ShortFilterServiceImpl implements ShortFilterService {
return sb.length() > 0 ? sb : null;
}
}
/* (non-Javadoc)
* @see org.genesys.catalog.service.ShortFilterService#upgradeFilterCode(org.genesys.catalog.model.ShortFilter)
*/
@Override
@Transactional
public ShortFilter upgradeFilterCode(ShortFilter shortFilter) {
assert(shortFilter.getJson() != null);
assert(shortFilter.getCode() != null);
// If filter code is current, ignore.
if (shortFilter.getCode().startsWith(CODE_PREFIX_CURRENT)) {
return shortFilter;
}
// If there's a filter with CODE_PREFIX_CURRENT for the same JSON, ignore
List<ShortFilter> existingShortFilters = shortFilterRepository.findByJson(shortFilter.getJson());
ShortFilter filterWithCurrentCode = null;
int needUpgrade = 0;
for (ShortFilter existing: existingShortFilters) {
if (existing.getCode().startsWith(CODE_PREFIX_CURRENT) && existing.getRedirect() == null) {
filterWithCurrentCode = existing;
} else {
needUpgrade++;
}
}
if (needUpgrade == 0) {
return filterWithCurrentCode;
}
ShortFilter newFilter = filterWithCurrentCode != null ? filterWithCurrentCode : shortFilterRepository.save(new ShortFilter(generateFilterCode(), shortFilter.getJson()));
for (ShortFilter existing: existingShortFilters) {
if (! newFilter.getCode().equalsIgnoreCase(existing.getCode())) {
existing.setRedirect(newFilter.getCode());
shortFilterRepository.save(existing);
}
}
return newFilter;
}
}
......@@ -15,7 +15,13 @@
*/
package org.genesys2.server.component.listener;
import java.util.List;
import org.genesys.blocks.util.CurrentApplicationContext;
import org.genesys.catalog.model.ShortFilter;
import org.genesys.catalog.persistence.ShortFilterRepository;
import org.genesys.catalog.service.ShortFilterService;
import org.genesys.catalog.service.impl.ShortFilterServiceImpl;
import org.genesys2.server.component.security.AsAdminInvoker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -50,6 +56,25 @@ public class ApplicationUpgrades implements InitializingBean {
LOG.info("Executing {}", this.getClass().getName());
// aclEnsureClassOIDs();
// aclMakePartnersPublic();
upgradeShortFiltrerCodeFromV1ToV2();
}
@Autowired
private ShortFilterRepository shortFilterRepository;
@Autowired
private ShortFilterService shortFilterService;
private void upgradeShortFiltrerCodeFromV1ToV2() throws Exception {
asAdminInvoker.invoke(() -> {
List<ShortFilter> allShortFilters = shortFilterRepository.findAll();
allShortFilters.forEach((filter) -> {
if (filter.getCode().startsWith(ShortFilterServiceImpl.CODE_PREFIX_V1)) {
shortFilterService.upgradeFilterCode(filter);
}
});
return true;
});
}
// @Autowired
......
#-------------------------------------------------------------------------------
# Copyright 2014 Global Crop Diversity Trust
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
......@@ -212,7 +212,7 @@ repository.ftp.port=-1
repository.ftp.passivePorts=2300-2350
repository.ftp.externalAddress=
# GLIS
# GLIS
itpgrfa.glis.basepath=https://glistest.planttreaty.org
itpgrfa.glis.username=
itpgrfa.glis.password=
......@@ -227,3 +227,6 @@ genesys.catalog.url=http://localhost:3000
# Amphibian
amphibian.url=
# Hashids
hashids.salt=This is Genesys!
......@@ -5835,3 +5835,15 @@ databaseChangeLog:
columnNames: imageId
constraintName: UK_p30ny8gip3fmcl83a0ot11a1
tableName: descriptor
- changeSet:
id: 1561170853000-1
author: mobreza
comment: Add column `redirect` (redir) to ShortFilter
changes:
- addColumn:
columns:
- column:
name: redir
type: VARCHAR(50)
tableName: short_filter
/*
* Copyright 2018 Global Crop Diversity Trust
* Copyright 2019 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -20,6 +20,8 @@ import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils;
......@@ -29,6 +31,7 @@ import org.genesys.catalog.model.filters.DescriptorFilter;
import org.genesys.catalog.model.filters.DescriptorListFilter;
import org.genesys.catalog.service.ShortFilterService;
import org.genesys.catalog.service.ShortFilterService.FilterInfo;
import org.genesys.catalog.service.impl.ShortFilterServiceImpl;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.dao.DataIntegrityViolationException;
......@@ -41,8 +44,8 @@ import com.fasterxml.jackson.databind.JsonMappingException;
*/
public class ShortFilterServiceTest extends CatalogServiceTest {
private static final String SHORT_NAME_1 = "v1cf4c01a7efed4f6697ddac7a974c4859";
private static final String SHORT_NAME_2 = "v18a0c1ab8180c48208776fcbc1e016cff";
private static final String V1_SHORT_NAME1 = "v1cf4c01a7efed4f6697ddac7a974c4859";
private static final String V1_SHORT_NAME2 = "v18a0c1ab8180c48208776fcbc1e016cff";
private static final String JSON_FILTERS_1 = "{\"crop\":[\"apple\",\"banana\",\"barley\",\"potato\"],\"title\":{\"contains\":\"test\"}}";
private static final String JSON_FILTERS_2 = "{\"crop\":[\"apple\",\"banana\",\"barley\",\"potato\"]}";
......@@ -54,9 +57,9 @@ public class ShortFilterServiceTest extends CatalogServiceTest {
@Test
public void testSave() {
final ShortFilter shortFilter = shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_1));
final ShortFilter shortFilter = shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME1, JSON_FILTERS_1));
assertThat(shortFilter, not(nullValue()));
assertThat(shortFilter.getCode(), is(SHORT_NAME_1));
assertThat(shortFilter.getCode(), is(V1_SHORT_NAME1));
assertThat(shortFilter.getJson(), is(JSON_FILTERS_1));
}
......@@ -72,6 +75,9 @@ public class ShortFilterServiceTest extends CatalogServiceTest {
filter.crop = Arrays.stream(cropValues).collect(Collectors.toSet());
String shortName = shortFilterService.getCode(filter);
assertThat(shortName, startsWith(ShortFilterServiceImpl.CODE_PREFIX_V2)); // V2 using HashIds
// System.err.println(shortName);
final ShortFilter shortFilter = shortFilterService.loadByCode(shortName);
assertThat(shortFilter, not(nullValue()));
assertThat(shortFilterRepository.findAll().size(), is(1));
......@@ -86,38 +92,27 @@ public class ShortFilterServiceTest extends CatalogServiceTest {
@Test
public void testLoadByShortName() {
final ShortFilter shortFilter1 = shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_1));
final ShortFilter shortFilter1 = shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME1, JSON_FILTERS_1));
assertThat(shortFilter1, not(nullValue()));
final ShortFilter shortFilter2 = shortFilterRepository.save(new ShortFilter(SHORT_NAME_2, JSON_FILTERS_2));
final ShortFilter shortFilter2 = shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME2, JSON_FILTERS_2));
assertThat(shortFilter2, not(nullValue()));
assertThat(shortFilterRepository.findAll().size(), is(2));
final ShortFilter foundShortFilter = shortFilterService.loadByCode(SHORT_NAME_1);
final ShortFilter foundShortFilter = shortFilterService.loadByCode(V1_SHORT_NAME1);
assertTrue(foundShortFilter.equals(shortFilter1));
}
@Test
public void testLoadByJsonFilters() {
final ShortFilter shortFilter = shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_1));
final ShortFilter shortFilter = shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME1, JSON_FILTERS_1));
assertThat(shortFilter, not(nullValue()));
final ShortFilter foundShortFilter = shortFilterService.loadByJSON(JSON_FILTERS_1);
assertTrue(foundShortFilter.equals(shortFilter));
}
@Test
public void testLoad() {
final ShortFilter shortFilter = shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_2));
assertThat(shortFilter, not(nullValue()));
final ShortFilter foundShortFilter1 = shortFilterService.load(JSON_FILTERS_2);
final ShortFilter foundShortFilter2 = shortFilterService.load(SHORT_NAME_1);
assertTrue(foundShortFilter1.equals(shortFilter) && foundShortFilter1.equals(foundShortFilter2));
}
/**
* It should not be able to insert a conflicting shortName.
* {@link ShortFilter#shortName} is unique
......@@ -125,11 +120,11 @@ public class ShortFilterServiceTest extends CatalogServiceTest {
@Test(expected = DataIntegrityViolationException.class)
@Ignore
public void testSaveWithNotUniqueShortName() {
final ShortFilter shortFilter1 = shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_1));
final ShortFilter shortFilter1 = shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME1, JSON_FILTERS_1));
assertThat(shortFilter1, not(nullValue()));
// create a new ShortFilter with the same shortName
shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_2));
shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME1, JSON_FILTERS_2));
}
/**
......@@ -138,11 +133,11 @@ public class ShortFilterServiceTest extends CatalogServiceTest {
*/
@Test
public void testSaveWithNotUniqueJsonFilters() {
final ShortFilter shortFilter1 = shortFilterRepository.save(new ShortFilter(SHORT_NAME_1, JSON_FILTERS_1));
final ShortFilter shortFilter1 = shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME1, JSON_FILTERS_1));
assertThat(shortFilter1, not(nullValue()));
// create a new ShortFilter with the same jsonFilters
shortFilterRepository.save(new ShortFilter(SHORT_NAME_2, JSON_FILTERS_1));
shortFilterRepository.save(new ShortFilter(V1_SHORT_NAME2, JSON_FILTERS_1));
}
@Test
......@@ -222,4 +217,22 @@ public class ShortFilterServiceTest extends CatalogServiceTest {
filterCode = shortFilterService.getCode(listFilter);
assertThat(filterInfo.filterCode, is(filterCode));
}
/**
* Test that short filter codes for different inputs don't collide.
*/
@Test
public void testCodeCollision() {
Set<String> shortCodes = new HashSet<>();
final DescriptorFilter descriptorFilter = new DescriptorFilter();
// Do 100 filters
for (long i=0; i<100; i++) {
descriptorFilter.id().add(i);
String filterCode = shortFilterService.getCode(descriptorFilter);
assertThat("Same short filter code must not be created", shortCodes.contains(filterCode), is(false));
shortCodes.add(filterCode);
}
// System.out.println(shortCodes);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment