Skip to content

Commit

Permalink
Introduction of reporting groups (#289)
Browse files Browse the repository at this point in the history
* Introduction of reporting groups

* fixing spelling / spellcheck issues
  • Loading branch information
ohecker authored Oct 20, 2024
1 parent f935a91 commit a707257
Show file tree
Hide file tree
Showing 59 changed files with 1,789 additions and 246 deletions.
28 changes: 25 additions & 3 deletions core/src/main/java/com/devonfw/tools/solicitor/SolicitorSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static class ReaderSetup {
private UsagePattern usagePattern;

private String repoType;

private String packageType;

private Map<String, String> configuration;
Expand Down Expand Up @@ -99,6 +99,7 @@ public String getPackageType() {

return this.packageType;
}

/**
* This method gets the field <code>configuration</code>.
*
Expand Down Expand Up @@ -168,7 +169,7 @@ public void setRepoType(String repoType) {

this.repoType = repoType;
}

/**
* This method sets the field <code>packageType</code>.
*
Expand All @@ -180,9 +181,10 @@ public void setPackageType(String packageType) {
}
}


private String engagementName;

private List<String> reportingGroups;

private List<ReaderSetup> readerSetups = new ArrayList<>();

private List<RuleConfig> ruleSetups = new ArrayList<>();
Expand All @@ -209,6 +211,26 @@ public void setEngagementName(String engagementName) {
this.engagementName = engagementName;
}

/**
* This method gets the field <code>reportingGroups</code>.
*
* @return reportingGroups
*/
public List<String> getReportingGroups() {

return this.reportingGroups;
}

/**
* This method sets the field <code>reportingGroups</code>.
*
* @param reportingGroups new value of {@link #getReportingGroups}.
*/
public void setReportingGroups(List<String> reportingGroups) {

this.reportingGroups = reportingGroups;
}

/**
* This method gets the field <code>readerSetups</code>.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public enum LogMessages {
FINISHED_WRITER(16, "Finished writing report with writer '{}' using template '{}' to file '{}'"), //
INIT_SQL(17, "Initializing SQL reporting database with Solicitor model data"), //
INIT_SQL_OLD(18, "Initializing SQL reporting database with OLD Solicitor model data"), //
EXECUTE_SQL(19, "Creating data of result table '{}' by executing SQL statement given in '{}'"), //
EXECUTE_SQL(19,
"Creating data of result table '{}' by executing SQL statement given in '{}' with reportingGroup '{}'"), //
CREATING_DIFF(20, "Calculating DIFF information for result table '{}'"), //
FILE_EXISTS(21, "At least '{}' already exists. Please remove existing files and retry."), //
PROJECT_CREATED(22,
Expand Down Expand Up @@ -111,7 +112,15 @@ public enum LogMessages {
UNKNOWN_PACKAGE_TYPE(75,
"The CSV file contains packageType '{}' which is not supported and will be ignored. Solicitor reports might be incomplete"), //
CONTENT_FILE_TOO_LARGE(76,
"The size of the content file '{}' is '{}' (max. allowed is '{}'). Reading will be skipped.");
"The size of the content file '{}' is '{}' (max. allowed is '{}'). Reading will be skipped."), //
ILLEGAL_CHARACTER_IN_REPORTING_GROUP(77,
"The name of the reporting group '{}' consists of illegal characters. Allowed are alphanumeric characters (US-ASCII), hyphen, underscore and space. The reporting group name must start with an alphanumeric character."), //
REPORTING_GROUP_NOT_MATCHING_FILTER(78,
"The reporting group '{}' does not match the filter expression. Processing of writer for "
+ "template source '{}' will be skipped for this reporting group"), //
REPORTING_GROUP_FILTER_EXPRESSION_SET_TO_NONDEFAULT(79,
"The filter expression for reporting groups to be processed is set to a non default value: '{}'"), //
REPORTING_GROUPS_DETECTED(80, "The following reporting groups are defined in this project: {} ");

private final String message;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/**
* SPDX-License-Identifier: Apache-2.0
*/
package com.devonfw.tools.solicitor.common;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* Component which provides several methods for dealing with reporting groups.
*/
@Component
public class ReportingGroupHandler {

private static final Logger LOG = LoggerFactory.getLogger(ReportingGroupHandler.class);

/**
* Regular expression pattern string to check the validity of reporting group names. The name might only consist of
* alphanumeric characters (US-ASCII), underscore, hyphen and space. It must begin with an alphanumeric character.
* Other characters are forbidden to ensure list matching using {@value #REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER}
* and to prevent SQL injection in the SQLs used in the reporting mechanism.
*/
private static final String VALID_REPORTING_GROUP_REGEX = "[a-zA-Z0-9][a-zA-Z0-9_ -]*";

/**
* Java pattern representation of {@link #VALID_REPORTING_GROUP_REGEX}.
*/
private static Pattern VALID_REPORTING_GROUP_PATTERN = Pattern.compile(VALID_REPORTING_GROUP_REGEX);

/**
* Name of the default reporting group to be used if no specific is given.
*/
public static final String DEFAULT_REPORTING_GROUP_NAME = "default";

/**
* The delimiter / prefix / suffix to be used when storing a collection of reporting groups as a string.
*/
public static final String REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER = "#";

/**
* Stringified list of reporting groups with only the default reporting group. To be used as default when reading
* model data which does not contain information on the reporting group.
*/
public static final String DEFAULT_REPORTING_GROUP_LIST = REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER
+ DEFAULT_REPORTING_GROUP_NAME + REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER;

private static final String DEFAULT_REPORTING_GROUP_FILTER = ".*";

private Pattern reportingGroupActivationFilterPattern = Pattern.compile(DEFAULT_REPORTING_GROUP_FILTER);

/**
* Sets the regex pattern which is used to determine if a specific reporting group is activated. Reporting groups are
* activated if their name matches the given regular expression. Default is <code>.*</code> which matches any name.
*
* @param filterPattern the new value of the filter pattern
*/
@Value("${solicitor.reportinggroups.filterpattern:.*}")
public void setReportingGroupActivationFilterPattern(String filterPattern) {

if (!DEFAULT_REPORTING_GROUP_FILTER.equals(filterPattern)) {
LOG.info(LogMessages.REPORTING_GROUP_FILTER_EXPRESSION_SET_TO_NONDEFAULT.msg(), filterPattern);
}

this.reportingGroupActivationFilterPattern = Pattern.compile(filterPattern);
}

/**
* Validates that the reporting group name consists only of permitted characters: Uppercase or lowercase, digits,
* spaces, hyphens or underscores.
*
* @param reportingGroup the reporting group which should be validated
* @throws SolicitorRuntimeException if the validation fails
*/
public void validateReportingGroup(String reportingGroup) {

if (!VALID_REPORTING_GROUP_PATTERN.matcher(reportingGroup).matches()) {
LOG.error(LogMessages.ILLEGAL_CHARACTER_IN_REPORTING_GROUP.msg(), reportingGroup);
throw new SolicitorRuntimeException("Illegal character in reportingGroup");
}

}

/**
* Splits a stringified list of reporting groups into a list. This includes validation.
*
* @param reportingGroups the stringified reporting group list which should be split
* @return the list of reporting groups as strings
* @throws SolicitorRuntimeException if the given argument is not a valid stringified list of reporting groups
*/
public List<String> splitReportingGroupList(String reportingGroups) {

if (!reportingGroups.startsWith("#")) {
throw new SolicitorRuntimeException("Stringified reporting group list must start with #");
}
if (!reportingGroups.endsWith("#")) {
throw new SolicitorRuntimeException("Stringified reporting group list must end with #");
}
String[] groups = reportingGroups.substring(1, reportingGroups.length() - 1)
.split(REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER);
for (String oneGroup : groups) {
validateReportingGroup(oneGroup);
}
return Arrays.asList(groups);
}

/**
* Validates a stringified list of reporting groups.
*
* @param reportingGroups the stringified reporting group list which should be validated
* @throws SolicitorRuntimeException if the given argument is not a valid stringified list of reporting groups
*/
public void validateReportingGroupList(String reportingGroups) {

// just delegate
splitReportingGroupList(reportingGroups);
}

/**
* Normalizes and validates the list of reporting groups. In case that the list is null then a set containing the
* default reporting groups will be returned. Otherwise the elements will be validated. Possible duplicates are
* removed due to return datatype being a {@link Set}.
*
* @param reportingGroups the list of reporting groups to be normalized
* @return the normalized set of reporting groups
*/
public Set<String> normalizeReportingGroups(List<String> reportingGroups) {

Set<String> normalizedReportingGroups = new TreeSet<>();
if (reportingGroups == null) {
normalizedReportingGroups.add(DEFAULT_REPORTING_GROUP_NAME);
} else {
for (String oneGroup : reportingGroups) {
validateReportingGroup(oneGroup);
normalizedReportingGroups.add(oneGroup);
}
}
return normalizedReportingGroups;
}

/**
* Converts the set of reporting groups to a single string via concatenation. All reporting group values are
* surrounded with a # to enable easy finding / pattern matching.
*
* @param normalizedReportingGroups set of reporting groups
* @return single string with all reporting groups
*/
public String stringifyReportingGroups(Set<String> normalizedReportingGroups) {

StringBuilder sb = new StringBuilder(REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER);
for (String oneGroup : normalizedReportingGroups) {
sb.append(oneGroup).append(REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER);
}
return sb.toString();
}

/**
* Replaces the placeholder (if any) in the given sql string so that the actual reporting group value is used. SQL
* injection is prevented because of previous validation of the possible <code>reportingGroup</code> values. (See
* {@link ReportingGroupHandler#validateReportingGroup(String)} ).
* <p>
* The placeholder in the sql statement needs to be <code>"#reportingGroup#</code>. It will be replaced by the given
* reporting group value surrounded by {@value ReportingGroupHandler#REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER}. <br>
* So <code>a."reportingGroups" LIKE '%#reportingGroup#%' AND</code> <br>
* will become <code>a."reportingGroups" LIKE '%#default#%' AND</code> if the current reporting group value is
* <code>default</code>,
*
* @param sql the sql which possibly contains the placeholder(s) to be replaced.
* @param reportingGroup the value of the current reporting group
* @return the sql statement with replaced placeholder(s)
*/
public String replacePlaceholderInSql(String sql, String reportingGroup) {

return sql.replace("#reportingGroup#",
REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER + reportingGroup + REPORTING_GROUP_STRINGIFIED_LIST_DELIMITER);
}

/**
* Expands the reporting group name placeholders in the writer output filename (if any). Spaces which exist in the
* reporting group name will be replaced by an underscore.
* <p>
* In case that the reporting group equals {@link #DEFAULT_REPORTING_GROUP_NAME} then the placeholders
* <code>${reportingGroup}</code>, <code>${-reportingGroup}</code>, <code>${_reportingGroup}</code> and
* <code>${/reportingGroup}</code> will be replaced by an empty string - thus removing the placeholder. Otherwise the
* placeholders will be replaced with the reporting group name (prepending <code>-</code>, <code>_</code> or
* <code>/</code> if applicable).
*
* @param rawFilename the raw filename with possible placeholder(s) <code>${reportingGroup}</code>.
* @param reportingGroup the current reporting group
* @return the filename with replaced placeholder(s)
*/
public String expandReportingGroupInFileName(String rawFilename, String reportingGroup) {

String targetFilename;
if (DEFAULT_REPORTING_GROUP_NAME.equals(reportingGroup)) {
targetFilename = rawFilename//
.replace("${-reportingGroup}", "")//
.replace("${_reportingGroup}", "")//
.replace("${/reportingGroup}", "")//
.replace("${reportingGroup}", "");
} else {
String reportingGroupUnderscored = reportingGroup.replace(" ", "_");
targetFilename = rawFilename//
.replace("${-reportingGroup}", "-" + reportingGroupUnderscored)//
.replace("${_reportingGroup}", "_" + reportingGroupUnderscored)//
.replace("${/reportingGroup}", "/" + reportingGroupUnderscored)//
.replace("${reportingGroup}", reportingGroupUnderscored);
}
return targetFilename;
}

/**
* Checks if the given reporting group matches the filter expression given by
* {@link #reportingGroupActivationFilterPattern}.
*
* @param reportingGroup the reporting group name to check
* @return <code>true</code> if the given argument matches the regular expression, <code>false</code> otherwise
*/
public boolean matchesReportingGroupFilter(String reportingGroup) {

return this.reportingGroupActivationFilterPattern.matcher(reportingGroup).matches();

}

/**
* Logs the given reporting group names on INFO level. Any reporting groups which do not match the filter from
* {@link #matchesReportingGroupFilter(String)} will be commented as disabled.
*
* @param reportingGroups the list of reporting groups
*/
public void logReportingGroups(Collection<String> reportingGroups) {

List<String> commentedReportingGroups = new ArrayList<>();
for (String oneGroup : reportingGroups) {
if (matchesReportingGroupFilter(oneGroup)) {
commentedReportingGroups.add("'" + oneGroup + "'");
} else {
commentedReportingGroups.add("'" + oneGroup + "'" + " (disabled via filter)");
}
}
LOG.info(LogMessages.REPORTING_GROUPS_DETECTED.msg(), String.join(", ", commentedReportingGroups));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public class ApplicationConfig {
@JsonProperty
private String programmingEcosystem;

@JsonProperty
private List<String> reportingGroups;

@JsonProperty
private List<ReaderConfig> readers = new ArrayList<>();

Expand Down Expand Up @@ -79,6 +82,16 @@ public String getSourceRepo() {
return this.sourceRepo;
}

/**
* This method gets the field <code>reportingGroups</code>.
*
* @return the field reportingGroups
*/
public List<String> getReportingGroups() {

return this.reportingGroups;
}

/**
* This method sets the field <code>name</code>.
*
Expand Down Expand Up @@ -128,4 +141,14 @@ public void setSourceRepo(String sourceRepo) {

this.sourceRepo = sourceRepo;
}

/**
* This method sets the field <code>reportingGroups</code>.
*
* @param reportingGroups the new value of the field reportingGroups
*/
public void setReportingGroups(List<String> reportingGroups) {

this.reportingGroups = reportingGroups;
}
}
Loading

0 comments on commit a707257

Please sign in to comment.