-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3843 from mercedes-benz/feature-3610-validate-tem…
…plate-settings-on-job-creation Validate templates on job creation + use sealed objects in domain communication
- Loading branch information
Showing
31 changed files
with
1,625 additions
and
47 deletions.
There are no files selected for viewing
112 changes: 112 additions & 0 deletions
112
...mmons-core/src/main/java/com/mercedesbenz/sechub/commons/core/CachingPatternProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package com.mercedesbenz.sechub.commons.core; | ||
|
||
import static java.util.Objects.*; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.Map; | ||
import java.util.SortedSet; | ||
import java.util.TreeMap; | ||
import java.util.TreeSet; | ||
import java.util.regex.Pattern; | ||
import java.util.regex.PatternSyntaxException; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* This provider gives a cached way to provide patterns. Because {@link Pattern} | ||
* is very expensive to create. | ||
* | ||
*/ | ||
public class CachingPatternProvider { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(CachingPatternProvider.class); | ||
|
||
private Map<String, PatternCacheEntry> map = new TreeMap<>(); | ||
private SortedSet<PatternCacheEntry> sortedEntries = new TreeSet<>(); | ||
|
||
private int maximumSize; | ||
private PatternCompiler compiler; | ||
|
||
private static final Pattern ACCEPT_EVERYTHING_PATTERN = Pattern.compile(".*"); | ||
|
||
public CachingPatternProvider(int maximumCacheSize, PatternCompiler compiler) { | ||
if (maximumCacheSize < 2) { | ||
throw new IllegalArgumentException("Maximum cache size must be greater than 1!"); | ||
} | ||
this.maximumSize = maximumCacheSize; | ||
this.compiler = requireNonNull(compiler); | ||
} | ||
|
||
/** | ||
* Resolve compiled pattern for given pattern string. | ||
* | ||
* @param regularExpression regular expression as string | ||
* @return compiled pattern or <code>null</code> if it was not possible to | ||
* create a compiled pattern (because of syntax problems) | ||
*/ | ||
public Pattern get(String regularExpression) { | ||
requireNonNull(regularExpression, "regular expression text may not be null!"); | ||
|
||
PatternCacheEntry entry = map.get(regularExpression); | ||
if (entry == null) { | ||
entry = createNewEntryAndShrinkCacheIfNecesary(regularExpression); | ||
|
||
} | ||
return entry.compiledPattern; | ||
} | ||
|
||
private PatternCacheEntry createNewEntryAndShrinkCacheIfNecesary(String regularExpression) { | ||
PatternCacheEntry entry = new PatternCacheEntry(); | ||
entry.pattern = regularExpression; | ||
try { | ||
Pattern compiledPattern = compiler.compile(regularExpression); | ||
entry.compiledPattern = compiledPattern; | ||
|
||
} catch (PatternSyntaxException e) { | ||
/* | ||
* this is a configuration problem at administrator side! We log the problem as | ||
* an error and use an alternative which accepts everything. | ||
*/ | ||
entry.compiledPattern = ACCEPT_EVERYTHING_PATTERN; | ||
LOG.error("The pattern is not valid: '{}'. Use instead compiled pattern: '{}'", regularExpression, entry.compiledPattern, e); | ||
} | ||
|
||
map.put(regularExpression, entry); | ||
sortedEntries.add(entry); // we add it here for cleanup | ||
|
||
/* check max size */ | ||
while (sortedEntries.size() > maximumSize) { | ||
PatternCacheEntry removeMe = sortedEntries.iterator().next(); | ||
|
||
/* remove from both collections: */ | ||
sortedEntries.remove(removeMe); | ||
map.remove(removeMe.pattern); | ||
} | ||
return entry; | ||
} | ||
|
||
private class PatternCacheEntry implements Comparable<PatternCacheEntry> { | ||
private Pattern compiledPattern; | ||
private String pattern; | ||
private LocalDateTime created; | ||
|
||
private PatternCacheEntry() { | ||
this.created = LocalDateTime.now(); | ||
} | ||
|
||
@Override | ||
public int compareTo(PatternCacheEntry o) { | ||
if (o == null) { | ||
return 1; | ||
} | ||
return created.compareTo(o.created); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "PatternCacheEntry [pattern=" + pattern + ",created=" + created + ", compiledPattern=" + compiledPattern + "]"; | ||
} | ||
|
||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/PatternCompiler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.mercedesbenz.sechub.commons.core; | ||
|
||
import static java.util.Objects.*; | ||
|
||
import java.util.regex.Pattern; | ||
import java.util.regex.PatternSyntaxException; | ||
|
||
public class PatternCompiler { | ||
|
||
/** | ||
* Compiles regular expression to a pattern | ||
* | ||
* @param regExp regular expression | ||
* @return pattern, never {@link NullPointerException} | ||
* @throws PatternSyntaxException | ||
*/ | ||
public Pattern compile(String regExp) { | ||
return Pattern.compile(requireNonNull(regExp, "Regular expression may not be null!")); | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
...s-core/src/test/java/com/mercedesbenz/sechub/commons/core/CachingPatternProviderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package com.mercedesbenz.sechub.commons.core; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
import static org.mockito.Mockito.*; | ||
|
||
import java.util.regex.Pattern; | ||
import java.util.regex.PatternSyntaxException; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
class CachingPatternProviderTest { | ||
|
||
@Test | ||
void cache_maximum_2_wrong_regular_expression_results_in_alternative_accepting_everything() { | ||
|
||
/* prepare */ | ||
PatternCompiler compiler = mock(); | ||
when(compiler.compile(any())).thenThrow(new PatternSyntaxException("desc", "regex", 0)); | ||
CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); | ||
|
||
/* execute */ | ||
Pattern result = providerToTest.get("i-am-simulated-wrong"); | ||
|
||
/* test */ | ||
assertThat(result.toString()).isEqualTo(".*"); | ||
} | ||
|
||
@Test | ||
void cache_maximum_2_correct_regular_expression_results_in_correct_compiled_pattern() { | ||
|
||
/* prepare */ | ||
PatternCompiler compiler = mock(); | ||
Pattern pattern = mock(); | ||
when(compiler.compile(eq("i-am-correct"))).thenReturn(pattern); | ||
CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); | ||
|
||
/* execute */ | ||
Pattern result = providerToTest.get("i-am-correct"); | ||
|
||
/* test */ | ||
verify(compiler).compile("i-am-correct"); | ||
assertThat(result).isSameAs(pattern); | ||
} | ||
|
||
@Test | ||
void cache_maximum_2_correct_regular_expression_fetched_3_times_created_only_one_time() { | ||
|
||
/* prepare */ | ||
PatternCompiler compiler = mock(); | ||
Pattern pattern = mock(); | ||
when(compiler.compile(eq("i-am-correct"))).thenReturn(pattern); | ||
CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); | ||
|
||
/* execute */ | ||
Pattern result = providerToTest.get("i-am-correct"); | ||
result = providerToTest.get("i-am-correct"); | ||
result = providerToTest.get("i-am-correct"); | ||
|
||
/* test */ | ||
verify(compiler, times(1)).compile("i-am-correct"); | ||
assertThat(result).isSameAs(pattern); | ||
} | ||
|
||
@Test | ||
void cache_maximum_2_handling_3_regular_expressions_does_remove_older_entries_automatically_when_cache_size_limit_reached() { | ||
|
||
/* prepare */ | ||
PatternCompiler compiler = mock(); | ||
Pattern pattern1 = mock(); | ||
Pattern pattern2 = mock(); | ||
Pattern pattern3 = mock(); | ||
|
||
when(compiler.compile(eq("p1"))).thenReturn(pattern1); | ||
when(compiler.compile(eq("p2"))).thenReturn(pattern2); | ||
when(compiler.compile(eq("p3"))).thenReturn(pattern3); | ||
CachingPatternProvider providerToTest = new CachingPatternProvider(2, compiler); | ||
|
||
/* execute 1 - empty cache */ | ||
Pattern result1 = providerToTest.get("p1"); // p1 added (one left) | ||
Pattern result2 = providerToTest.get("p2"); // p2 added (zero left) | ||
|
||
/* test 1 - empty cache, both patterns are compiled */ | ||
var expectedCompileP1amount = 1; | ||
var expectedCompileP2amount = 1; | ||
|
||
verify(compiler, times(expectedCompileP1amount)).compile("p1"); | ||
verify(compiler, times(expectedCompileP2amount)).compile("p2"); | ||
|
||
/* execute 2 - use cache only */ | ||
for (int i = 0; i < 10; i++) { | ||
/* execute 0 */ | ||
result1 = providerToTest.get("p1"); // must be from cache | ||
result2 = providerToTest.get("p2"); // must be from cache | ||
} | ||
|
||
/* test 2 - test fetching existing ones does not compile again */ | ||
verify(compiler, times(expectedCompileP1amount)).compile("p1"); | ||
verify(compiler, times(expectedCompileP2amount)).compile("p2"); | ||
|
||
/* execute 3 - recreate removed pattern */ | ||
result1 = providerToTest.get("p1"); // p1 added (one left) | ||
result2 = providerToTest.get("p2"); // p2 added (zero left) | ||
Pattern result3 = providerToTest.get("p3"); // p3 added, must remove p1 fetch here because oldest | ||
Pattern result1x = providerToTest.get("p1"); // fetch p1 again... will recreated | ||
|
||
/* test 3 - data is still correct and re-creation works */ | ||
expectedCompileP1amount++;// p1 must be recreated (p1,p2,p3->p1 removed, p1 fetch--> recreate) | ||
var expectedCompileP3amount = 1; | ||
|
||
verify(compiler, times(expectedCompileP2amount)).compile("p2"); | ||
verify(compiler, times(expectedCompileP3amount)).compile("p3"); | ||
verify(compiler, times(expectedCompileP1amount)).compile("p1"); | ||
|
||
assertThat(result1).isSameAs(pattern1); | ||
assertThat(result1x).isSameAs(pattern1); | ||
assertThat(result2).isSameAs(pattern2); | ||
assertThat(result3).isSameAs(pattern3); | ||
|
||
/* execute 4 - use cached values */ | ||
result1 = providerToTest.get("p1"); | ||
result3 = providerToTest.get("p3"); | ||
|
||
/* test 4 */ | ||
verify(compiler, times(expectedCompileP1amount)).compile("p1"); // p1 must not be created again | ||
verify(compiler, times(expectedCompileP3amount)).compile("p3"); // p3 must not be created again | ||
|
||
assertThat(result1).isSameAs(pattern1); | ||
assertThat(result3).isSameAs(pattern3); | ||
|
||
/* execute 5 - use next entry, not in cache, and max reached */ | ||
result2 = providerToTest.get("p2"); | ||
|
||
/* test 5 */ | ||
expectedCompileP2amount++; // p2 must be recreated | ||
verify(compiler, times(expectedCompileP2amount)).compile("p2"); | ||
assertThat(result2).isSameAs(pattern2); | ||
|
||
/* execute 6 - next entry not */ | ||
result2 = providerToTest.get("p2"); | ||
|
||
/* test 6 */ | ||
verify(compiler, times(expectedCompileP2amount)).compile("p2"); // same expectedCompileP2amount as before - means comes from cache | ||
assertThat(result2).isSameAs(pattern2); | ||
|
||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
...-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/PatternCompilerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.mercedesbenz.sechub.commons.core; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
|
||
import java.util.regex.Pattern; | ||
import java.util.regex.PatternSyntaxException; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.ValueSource; | ||
|
||
class PatternCompilerTest { | ||
|
||
private PatternCompiler compilerToTest; | ||
|
||
@BeforeEach | ||
void beforeEach() { | ||
compilerToTest = new PatternCompiler(); | ||
} | ||
|
||
@Test | ||
void wrong_regular_expresion_throws_pattern_syntax_exception() { | ||
assertThatThrownBy(() -> compilerToTest.compile("i-am-wrong(")).isInstanceOf(PatternSyntaxException.class); | ||
} | ||
|
||
@Test | ||
void null_regular_expresion_throws_npe() { | ||
assertThatThrownBy(() -> compilerToTest.compile(null)).isInstanceOf(NullPointerException.class).hasMessageContaining("may not be null"); | ||
} | ||
|
||
@ParameterizedTest | ||
@ValueSource(strings = { "abc", ".*" }) | ||
void correct_regular_expresion_results_in_pattern(String regExp) { | ||
/* execute */ | ||
Pattern result = compilerToTest.compile(regExp); | ||
|
||
/* test */ | ||
assertThat(result).isNotNull(); | ||
assertThat(result.toString()).isEqualTo(regExp); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.