DecisionDataFactoryImpl.java
/*
* Copyright 2010-2025 James Pether Sörling
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id$
* $HeadURL$
*/
package com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.impl;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.hack23.cia.model.external.riksdagen.dokumentstatus.impl.DocumentProposalData;
import com.hack23.cia.model.external.riksdagen.dokumentstatus.impl.DocumentStatusContainer;
import com.hack23.cia.service.api.ApplicationManager;
import com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.api.DecisionDataFactory;
import com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.api.ProposalCommitteeeSummary;
/**
* Implementation of DecisionDataFactory for processing parliamentary committee decisions.
* Handles document status data and creates summaries of committee proposals.
*/
@Service
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public final class DecisionDataFactoryImpl implements DecisionDataFactory {
/** Document type constants. */
private static final String PROPOSITION = "Proposition";
/** The Constant MOTION. */
private static final String MOTION = "Motion";
/** The Constant PROP_TYPE. */
private static final String PROP_TYPE = "prop";
/** Chamber text length constraints for valid proposals. */
private static final int CHAMBER_MIN_LENGTH = "avslag".length(); // 6
/** The Constant CHAMBER_MAX_LENGTH. */
private static final int CHAMBER_MAX_LENGTH = "återförvisning till utskottet".length(); // 29
/**
* Pattern for standardizing committee decision text.
* Removes or replaces parliamentary specific tokens:
* - (UTSKOTTET)
* - Parentheses
* - Various committee spelling variants
*/
private static final Pattern COMMITTEE_TEXT_CLEANUP_PATTERN = Pattern.compile(
"(\\(UTSKOTTET\\))|(\\()|(\\))|(UTBSKOTTET)|(UBTSKOTTET)|(UTKOTTET)",
Pattern.CASE_INSENSITIVE
);
/** The application manager. */
@Autowired
private ApplicationManager applicationManager;
/**
* Creates committee summaries for a specific processing location.
*
* @param processedIn the location where proposals were processed
* @return List of committee summaries
* @throws IllegalArgumentException if processedIn is blank
*/
@Override
public List<ProposalCommitteeeSummary> createCommitteeSummary(final String processedIn) {
validateProcessedIn(processedIn);
return applicationManager.getDataContainer(DocumentStatusContainer.class)
.getAll()
.parallelStream()
.filter(doc -> isValidDocument(doc, processedIn))
.map(doc -> createProposalSummary(doc))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* Validates the processedIn parameter.
*
* @param processedIn location to validate
* @throws IllegalArgumentException if validation fails
*/
private void validateProcessedIn(final String processedIn) {
if (StringUtils.isBlank(processedIn)) {
throw new IllegalArgumentException("ProcessedIn parameter cannot be blank");
}
}
/**
* Validates if a document meets the criteria for processing.
*
* @param document the document to validate
* @param processedIn the required processing location
* @return true if document is valid for processing
*/
private boolean isValidDocument(final DocumentStatusContainer document, final String processedIn) {
if (document == null || document.getDocumentProposal() == null ||
document.getDocumentProposal().getProposal() == null) {
return false;
}
final DocumentProposalData proposal = document.getDocumentProposal().getProposal();
return isValidProposal(proposal, processedIn);
}
/**
* Validates proposal data for processing requirements.
*
* @param proposal the proposal to validate
* @param processedIn the required processing location
* @return true if proposal meets requirements
*/
private boolean isValidProposal(final DocumentProposalData proposal, final String processedIn) {
if (proposal == null || StringUtils.isBlank(proposal.getChamber()) ||
StringUtils.isBlank(proposal.getProcessedIn()) ||
StringUtils.isBlank(proposal.getCommittee())) {
return false;
}
final int chamberLength = proposal.getChamber().length();
return chamberLength >= CHAMBER_MIN_LENGTH &&
chamberLength <= CHAMBER_MAX_LENGTH &&
proposal.getProcessedIn().contains(processedIn);
}
/**
* Creates a ProposalCommitteeeSummary from a valid document.
*
* @param document source document
* @return ProposalCommitteeeSummary or null if invalid
*/
private ProposalCommitteeeSummary createProposalSummary(final DocumentStatusContainer document) {
try {
final DocumentProposalData proposal = document.getDocumentProposal().getProposal();
return new ProposalCommitteeeSummary(
extractCommitteeShortName(proposal),
determineDocumentType(document),
standardizeDecisionText(proposal.getChamber()),
document.getDocument().getHangarId(),
proposal.getWording(),
proposal.getWording2(),
proposal.getDecisionType()
);
} catch (final Exception e) {
// Log error if needed
return null;
}
}
/**
* Standardizes the decision text by removing variations and normalizing format.
*
* @param chamber the original chamber text
* @return standardized decision text
*/
private static String standardizeDecisionText(final String chamber) {
if (StringUtils.isBlank(chamber)) {
return "";
}
String standardized = chamber.toUpperCase(Locale.ENGLISH);
standardized = COMMITTEE_TEXT_CLEANUP_PATTERN.matcher(standardized).replaceAll("");
// Normalize committee spelling
if (standardized.contains("UTKOTTET")) {
standardized = standardized.replace("UTKOTTET", "UTSKOTTET");
}
return standardized.trim();
}
/**
* Extracts the standardized committee short name.
*
* @param proposal source proposal
* @return committee short name
*/
private static String extractCommitteeShortName(final DocumentProposalData proposal) {
final String processedIn = proposal.getProcessedIn();
if (StringUtils.isBlank(processedIn)) {
return "";
}
final String shortName = processedIn
.replaceAll("\\d", "")
.replace("/:", "")
.toUpperCase(Locale.ENGLISH);
final int commaIndex = shortName.indexOf(',');
return (commaIndex >= 0) ? shortName.substring(0, commaIndex) : shortName;
}
/**
* Determines the human-readable document type.
*
* @param document source document
* @return document type string
*/
private static String determineDocumentType(final DocumentStatusContainer document) {
if (PROP_TYPE.equalsIgnoreCase(document.getDocument().getDocumentType())) {
return PROPOSITION;
}
final String subType = document.getDocument().getSubType();
return (subType != null && subType.length() > MOTION.length())
? subType
: MOTION;
}
}