All Downloads are FREE. Search and download functionalities are using the official Maven repository.

li.strolch.report.policy.GenericReport Maven / Gradle / Ivy

package li.strolch.report.policy;

import static java.util.Comparator.comparingInt;
import static li.strolch.report.ReportConstants.*;
import static li.strolch.utils.helper.StringHelper.EMPTY;

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import li.strolch.agent.api.ComponentContainer;
import li.strolch.model.*;
import li.strolch.model.parameter.*;
import li.strolch.model.policy.PolicyDef;
import li.strolch.model.visitor.ElementDateVisitor;
import li.strolch.model.visitor.ElementStateVisitor;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.policy.PolicyHandler;
import li.strolch.report.ReportElement;
import li.strolch.runtime.StrolchConstants;
import li.strolch.utils.ObjectHelper;
import li.strolch.utils.collections.DateRange;
import li.strolch.utils.collections.MapOfSets;
import li.strolch.utils.iso8601.ISO8601FormatFactory;

/**
 * @author Robert von Burg <[email protected]>
 */
public class GenericReport extends ReportPolicy {

	private Resource reportRes;

	private boolean hideObjectTypeFromFilterCriteria;
	private String objectType;

	private ParameterBag columnsBag;
	private List orderingParams;
	private boolean descending;
	private boolean allowMissingColumns;
	private List columnIds;
	private StringParameter dateRangeSelP;

	private DateRange dateRange;
	private Map filtersByPolicy;
	private MapOfSets filtersById;

	public GenericReport(ComponentContainer container, StrolchTransaction tx) {
		super(container, tx);
	}

	public void initialize(String reportId) {

		// get the reportRes
		this.reportRes = tx().getResourceBy(TYPE_REPORT, reportId, true);

		BooleanParameter hideObjectTypeFromFilterCriteriaP = this.reportRes
				.getParameter(BAG_PARAMETERS, PARAM_HIDE_OBJECT_TYPE_FROM_FILTER_CRITERIA);
		this.hideObjectTypeFromFilterCriteria =
				hideObjectTypeFromFilterCriteriaP != null && hideObjectTypeFromFilterCriteriaP.getValue();
		if (this.hideObjectTypeFromFilterCriteria) {
			this.objectType = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_OBJECT_TYPE).getValue();
		}

		this.columnsBag = this.reportRes.getParameterBag(BAG_COLUMNS, true);

		this.columnIds = this.columnsBag.getParameters().stream() //
				.sorted(comparingInt(Parameter::getIndex)) //
				.map(StrolchElement::getId) //
				.collect(Collectors.toList());

		if (this.reportRes.hasParameter(BAG_PARAMETERS, PARAM_DESCENDING))
			this.descending = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_DESCENDING).getValue();
		if (this.reportRes.hasParameter(BAG_PARAMETERS, PARAM_ALLOW_MISSING_COLUMNS))
			this.allowMissingColumns = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_ALLOW_MISSING_COLUMNS)
					.getValue();

		this.dateRangeSelP = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_DATE_RANGE_SEL);

		// evaluate ordering params
		if (this.reportRes.hasParameterBag(BAG_ORDERING)) {
			ParameterBag orderingBag = this.reportRes.getParameterBag(BAG_ORDERING);
			if (orderingBag.hasParameters()) {
				this.orderingParams = orderingBag.getParameters().stream().map(e -> (StringParameter) e)
						.collect(Collectors.toList());
				this.orderingParams.sort(comparingInt(AbstractParameter::getIndex));
			}
		}

		// evaluate filters
		this.filtersByPolicy = new HashMap<>();
		List filterBags = this.reportRes.getParameterBagsByType(TYPE_FILTER);
		for (ParameterBag filterBag : filterBags) {

			// prepare filter function policy
			StringParameter functionP = filterBag.getParameter(PARAM_POLICY);
			PolicyHandler policyHandler = getContainer().getComponent(PolicyHandler.class);
			PolicyDef policyDef = PolicyDef.valueOf(functionP.getInterpretation(), functionP.getUom());
			ReportFilterPolicy filterFunction = policyHandler.getPolicy(policyDef, tx());
			filterFunction.init(functionP.getValue());

			StringParameter fieldRefP = filterBag.getParameter(PARAM_FIELD_REF);
			this.filtersByPolicy.put(filterFunction, fieldRefP);
		}
	}

	public boolean hasDateRangeSelector() {
		return this.dateRangeSelP != null;
	}

	public GenericReport dateRange(DateRange dateRange) {
		this.dateRange = dateRange;
		return this;
	}

	public List getColumnKeys() {
		return this.columnIds;
	}

	public GenericReport filter(String type, String... ids) {
		if (this.filtersById == null)
			this.filtersById = new MapOfSets<>();
		for (String id : ids) {
			this.filtersById.addElement(type, id);
		}
		return this;
	}

	public GenericReport filter(String type, List ids) {
		if (this.filtersById == null)
			this.filtersById = new MapOfSets<>();
		for (String id : ids) {
			this.filtersById.addElement(type, id);
		}
		return this;
	}

	public GenericReport filter(String type, Set ids) {
		if (this.filtersById == null)
			this.filtersById = new MapOfSets<>();
		for (String id : ids) {
			this.filtersById.addElement(type, id);
		}
		return this;
	}

	public Stream> buildStream() {

		// query the main objects and return a stream
		Stream> stream = queryRows().map(this::evaluateRow);

		if (hasFilter())
			stream = stream.filter(this::filter);

		if (hasOrdering())
			stream = stream.sorted(this::sort);

		return stream;
	}

	public Stream doReport() {

		return buildStream().map(e -> new ReportElement(this.columnIds, columnId -> {
			StringParameter columnDefP = this.columnsBag.getParameter(columnId, true);
			Object value = evaluateColumnValue(columnDefP, e);
			if (value instanceof Date)
				return ISO8601FormatFactory.getInstance().formatDate((Date) value);
			if (value instanceof Parameter)
				return ((Parameter) value).getValueAsString();
			return value.toString();
		}));
	}

	protected boolean filterCriteria(StrolchRootElement element) {
		if (this.hideObjectTypeFromFilterCriteria)
			return !element.getType().equals(this.objectType);
		return true;
	}

	public MapOfSets generateFilterCriteria() {
		return buildStream() //

				.flatMap(e -> e.values().stream().filter(this::filterCriteria)) //

				.collect( //
						Collector.of( //
								(Supplier>) MapOfSets::new, //
								(m, e) -> m.addElement(e.getType(), e), //
								MapOfSets::addAll, //
								m -> m));
	}

	protected boolean hasOrdering() {
		return this.orderingParams != null && !this.orderingParams.isEmpty();
	}

	protected int sort(Map row1, Map row2) {

		for (StringParameter fieldRefP : this.orderingParams) {

			String type = fieldRefP.getUom();

			StrolchRootElement column1 = row1.get(type);
			StrolchRootElement column2 = row2.get(type);
			if (column1 == null && column2 == null)
				continue;
			if (column1 == null)
				return -1;
			if (column2 == null)
				return 1;

			int sortVal;
			if (fieldRefP.getValue().startsWith("$")) {
				Object columnValue1 = evaluateColumnValue(fieldRefP, row1);
				Object columnValue2 = evaluateColumnValue(fieldRefP, row2);

				if (this.descending) {
					sortVal = ObjectHelper.compare(columnValue1, columnValue2, true);
				} else {
					sortVal = ObjectHelper.compare(columnValue2, columnValue1, true);
				}

			} else {
				Optional> param1 = lookupParameter(fieldRefP, column1);
				Optional> param2 = lookupParameter(fieldRefP, column2);

				if (!param1.isPresent() && !param2.isPresent())
					continue;

				if (param1.isPresent() && !param2.isPresent())
					return 1;
				else if (!param1.isPresent())
					return -1;

				if (this.descending)
					sortVal = param1.get().compareTo(param2.get());
				else
					sortVal = param2.get().compareTo(param1.get());
			}

			if (sortVal != 0)
				return sortVal;
		}

		return 0;
	}

	protected boolean hasFilter() {
		return !this.filtersByPolicy.isEmpty() || this.dateRange != null || (this.filtersById != null
				&& !this.filtersById.isEmpty());
	}

	protected boolean filter(Map row) {

		// do filtering by policies
		for (ReportFilterPolicy filterPolicy : this.filtersByPolicy.keySet()) {
			StringParameter fieldRefP = this.filtersByPolicy.get(filterPolicy);

			String type = fieldRefP.getUom();

			StrolchRootElement column = row.get(type);

			// if column is null, then don't include in result
			if (column == null)
				return false;

			if (fieldRefP.getValue().startsWith("$")) {
				Object value = evaluateColumnValue(fieldRefP, row);

				String columnValue;
				if (value instanceof Date)
					columnValue = ISO8601FormatFactory.getInstance().formatDate((Date) value);
				else if (value instanceof Parameter)
					columnValue = ((Parameter) value).getValueAsString();
				else
					columnValue = value.toString();

				if (!filterPolicy.filter(columnValue))
					return false;
			} else {
				Optional> param = lookupParameter(fieldRefP, column);
				if (param.isPresent() && !filterPolicy.filter(param.get()))
					return false;
			}
		}

		// do a date range selection, if required
		if (this.dateRange != null) {
			if (this.dateRangeSelP == null)
				throw new IllegalStateException(
						"DateRange defined, but reportRes does not defined a date range selector!");

			String type = this.dateRangeSelP.getUom();
			StrolchRootElement element = row.get(type);
			if (element == null)
				return false;

			String dateRangeSel = this.dateRangeSelP.getValue();

			Date date;
			if (dateRangeSel.equals(COL_DATE)) {
				date = element.accept(new ElementDateVisitor());
			} else {
				Optional> param = lookupParameter(this.dateRangeSelP, element);
				if (!param.isPresent() || StrolchValueType.parse(param.get().getType()) != StrolchValueType.DATE)
					throw new IllegalStateException(
							"Date Range selector is invalid, as referenced parameter is not a Date but " + (param
									.isPresent() ? param.get().getType() : "null"));

				date = ((DateParameter) param.get()).getValue();
			}

			if (!this.dateRange.contains(date))
				return false;
		}

		// then we do a filter by criteria
		if (this.filtersById != null && !this.filtersById.isEmpty()) {
			for (String type : this.filtersById.keySet()) {
				StrolchRootElement element = row.get(type);
				if (element == null)
					return false;

				if (!this.filtersById.getSet(type).contains(element.getId()))
					return false;
			}
		}

		// otherwise we want to keep this row
		return true;
	}

	protected Object evaluateColumnValue(StringParameter columnDefP, Map row) {

		String columnDef = columnDefP.getValue();
		String refType = columnDefP.getUom();

		// get the referenced object
		StrolchRootElement column = row.get(refType);

		Object columnValue;

		if (column == null)
			columnValue = EMPTY;
		else if (columnDef.equals(COL_ID)) {
			columnValue = column.getId();
		} else if (columnDef.equals(COL_NAME)) {
			columnValue = column.getName();
		} else if (columnDef.equals(COL_TYPE)) {
			columnValue = column.getType();
		} else if (columnDef.equals(COL_STATE)) {
			columnValue = column.accept(new ElementStateVisitor());
		} else if (columnDef.equals(COL_DATE)) {
			columnValue = column.accept(new ElementDateVisitor());
		} else if (columnDef.startsWith(COL_SEARCH)) {
			Parameter parameter = findParameter(columnDefP, column);
			if (parameter == null)
				columnValue = EMPTY;
			else
				columnValue = parameter;
		} else {
			columnValue = lookupParameter(columnDefP, column).orElseGet(StringParameter::new);
		}

		return columnValue;
	}

	protected Parameter findParameter(StringParameter columnDefP, StrolchRootElement column) {

		String columnDef = columnDefP.getValue();

		String[] searchParts = columnDef.split(SEARCH_SEPARATOR);
		if (searchParts.length != 3)
			throw new IllegalStateException(
					"Parameter search reference (" + columnDef + ") is invalid as it does not have 3 parts for "
							+ columnDefP.getLocator());

		String parentParamId = searchParts[1];
		String paramRef = searchParts[2];

		String[] locatorParts = paramRef.split(Locator.PATH_SEPARATOR);
		if (locatorParts.length != 3)
			throw new IllegalStateException(
					"Parameter search reference (" + paramRef + ") is invalid as it does not have 3 parts for "
							+ columnDefP.getLocator());

		String bagKey = locatorParts[1];
		String paramKey = locatorParts[2];

		return findParameter(column, parentParamId, bagKey, paramKey);
	}

	protected Parameter findParameter(StrolchRootElement element, String parentParamId, String bagKey,
			String paramKey) {

		Parameter parameter = element.getParameter(bagKey, paramKey);
		if (parameter != null)
			return parameter;

		StringParameter parentRefP = element.getParameter(BAG_RELATIONS, parentParamId);
		if (parentRefP == null)
			return null;

		Resource parent = tx().getResourceBy(parentRefP);
		if (parent == null)
			return null;

		return findParameter(parent, parentParamId, bagKey, paramKey);
	}

	protected Optional> lookupParameter(StringParameter paramRefP, StrolchRootElement column) {
		String paramRef = paramRefP.getValue();

		String[] locatorParts = paramRef.split(Locator.PATH_SEPARATOR);
		if (locatorParts.length != 3)
			throw new IllegalStateException(
					"Parameter reference (" + paramRef + ") is invalid as it does not have 3 parts for " + paramRefP
							.getLocator());

		String bagKey = locatorParts[1];
		String paramKey = locatorParts[2];

		Parameter param = column.getParameter(bagKey, paramKey);
		if (!allowMissingColumns && param == null)
			throw new IllegalStateException(
					"Parameter reference (" + paramRef + ") for " + paramRefP.getLocator() + " not found on " + column
							.getLocator());

		return Optional.ofNullable(param);
	}

	protected Stream queryRows() {

		StringParameter objectTypeP = this.reportRes.getParameter(BAG_PARAMETERS, PARAM_OBJECT_TYPE);

		// find the type of object for which the reportRes is created
		switch (objectTypeP.getInterpretation()) {

		case StrolchConstants.INTERPRETATION_RESOURCE_REF:

			return tx().streamResources(objectTypeP.getUom());

		case StrolchConstants.INTERPRETATION_ORDER_REF:

			return tx().streamOrders(objectTypeP.getUom());

		case StrolchConstants.INTERPRETATION_ACTIVITY_REF:

			return tx().streamActivities(objectTypeP.getUom());

		default:

			throw new IllegalArgumentException("Unhandled element type " + objectTypeP.getInterpretation());
		}
	}

	protected Map evaluateRow(StrolchRootElement resource) {

		// interpretation -> Resource-Ref, etc.
		// uom -> object type
		// value -> element type where relation is defined for this join

		// create the refs element
		HashMap refs = new HashMap<>();
		// and add the starting point
		refs.put(resource.getType(), resource);

		if (!this.reportRes.hasParameterBag(BAG_JOINS))
			return refs;

		ParameterBag joinBag = this.reportRes.getParameterBag(BAG_JOINS);
		for (String paramId : joinBag.getParameterKeySet()) {
			StringParameter joinP = joinBag.getParameter(paramId);
			addColumnJoin(refs, joinBag, joinP, true);
		}

		return refs;
	}

	protected StrolchRootElement addColumnJoin(Map refs, ParameterBag joinBag,
			StringParameter joinP, boolean optional) {

		String interpretation = joinP.getInterpretation();
		String elementType = interpretation.substring(0, interpretation.indexOf(SUFFIX_REF));
		String joinType = joinP.getUom();
		String dependencyType = joinP.getValue();

		// get dependency
		StrolchRootElement dependency;
		if (refs.containsKey(dependencyType)) {
			dependency = refs.get(dependencyType);
		} else {
			// recursively find the dependency
			StringParameter dependencyP = joinBag.getParameter(dependencyType);
			dependency = addColumnJoin(refs, joinBag, dependencyP, false);
			if (dependency == null)
				return null;
		}

		ParameterBag relationsBag = dependency.getParameterBag(BAG_RELATIONS);
		if (relationsBag == null)
			throw new IllegalStateException(
					"Invalid join definition value: " + joinP.getValue() + " on: " + joinP.getLocator() + " as "
							+ dependency.getLocator() + " has no ParameterBag " + BAG_RELATIONS);

		List> relationParams = relationsBag.getParametersByInterpretationAndUom(interpretation, joinType);

		if (relationParams.isEmpty()) {
			throw new IllegalStateException(
					"Found no relation parameters with UOM " + joinType + " on dependency " + dependency.getLocator());
		}
		if (relationParams.size() > 1) {
			throw new IllegalStateException(
					"Found multiple possible relation parameters for UOM " + joinType + " on dependency " + dependency
							.getLocator());
		}

		Parameter relationParam = relationParams.get(0);
		if (!relationParam.getType().equals(StrolchValueType.STRING.getType())) {
			throw new IllegalStateException(
					"Expect to find relation parameter of type " + StrolchValueType.STRING.getType() + " but found "
							+ relationParam.getType() + " for " + relationParam.getLocator());
		}

		StringParameter relationP = (StringParameter) relationParam;
		if (relationP.getValue().isEmpty() && optional) {
			return null;
		}

		Locator locator = Locator.valueOf(elementType, joinType, relationP.getValue());
		StrolchRootElement joinElem = tx().findElement(locator, true);
		if (joinElem == null)
			return null;

		refs.put(joinType, joinElem);
		return joinElem;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy