001/*
002 *  Copyright 2019 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.odf.ose.enumeration;
017
018import java.util.Arrays;
019import java.util.Map;
020import java.util.Optional;
021import java.util.stream.Stream;
022
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.avalon.framework.service.Serviceable;
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.cms.contenttype.validation.AbstractContentValidator;
029import org.ametys.cms.data.ContentValue;
030import org.ametys.cms.repository.Content;
031import org.ametys.cms.repository.ContentQueryHelper;
032import org.ametys.cms.repository.ContentTypeExpression;
033import org.ametys.plugins.repository.AmetysObjectResolver;
034import org.ametys.plugins.repository.query.expression.AndExpression;
035import org.ametys.plugins.repository.query.expression.Expression;
036import org.ametys.plugins.repository.query.expression.Expression.Operator;
037import org.ametys.plugins.repository.query.expression.StringExpression;
038import org.ametys.runtime.i18n.I18nizableText;
039import org.ametys.runtime.i18n.I18nizableTextParameter;
040import org.ametys.runtime.model.View;
041import org.ametys.runtime.parameter.Errors;
042
043/**
044 * Global validator for a Degree Category (entry of reference table),
045 * which checks that all its degrees are only binded to this category.
046 */
047public class DegreesInCategoryConsistencyValidator extends AbstractContentValidator implements Serviceable
048{
049    private static final String __DEGREES_CATEGORY_CONTENT_TYPE = "odf-enumeration.DegreeCategory";
050    private static final String __DEGREES_ATTRIBUTE_NAME = "degrees";
051    
052    /** The Ametys object resolver */
053    protected AmetysObjectResolver _resolver;
054    
055    @Override
056    public void service(ServiceManager manager) throws ServiceException
057    {
058        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
059    }
060    
061    @Override
062    public void validate(Content content, Errors errors)
063    {
064        // Nothing to do
065    }
066    
067    public void validate(Content degreeCategory, Map<String, Object> values, View view, Errors errors)
068    {
069        Optional.ofNullable(values.get(__DEGREES_ATTRIBUTE_NAME))
070                       .map(ContentValue[].class::cast)
071                       .map(Arrays::stream)
072                       .orElseGet(Stream::empty)
073                       .map(degree -> degree.getContentIfExists())
074                       .flatMap(Optional::stream)
075                       .filter(degree -> _isInAnotherCategory(degree, degreeCategory))
076                       .map(degree -> _getErrorLabel(degree, degreeCategory))
077                       .forEach(errors::addError);    
078    }
079    
080    /**
081     * Check if the current degree is in another category than the current category.
082     * @param degree The current degree
083     * @param degreeCategory The current category
084     * @return <code>true</code> if it's in another degree category
085     */
086    protected boolean _isInAnotherCategory(Content degree, Content degreeCategory)
087    {
088        return _resolver.query(_getXPathQuery(degree, degreeCategory)).iterator().hasNext();
089    }
090    
091    /**
092     * Returns the XPath query to execute to obtain the list of categories except the current category in which the degree appears.
093     * @param degree The degree
094     * @param currentDegreeCategory The current degree category
095     * @return An XPath query
096     */
097    protected String _getXPathQuery(Content degree, Content currentDegreeCategory)
098    {
099        Expression contentTypeExpr = new ContentTypeExpression(Operator.EQ, __DEGREES_CATEGORY_CONTENT_TYPE);
100        Expression categoryExpr = new StringExpression(__DEGREES_ATTRIBUTE_NAME, Operator.EQ, degree.getId());
101        String categoryUUID = StringUtils.substringAfter(currentDegreeCategory.getId(), "://");
102        Expression notCurrentExpr = () -> "jcr:uuid != '" + categoryUUID + "'";
103        Expression finalExpression = new AndExpression(contentTypeExpr, categoryExpr, notCurrentExpr);
104        return ContentQueryHelper.getContentXPathQuery(finalExpression);
105    }
106    
107    /**
108     * Get the error label corresponding to the given degree and category.
109     * @param degree The degree
110     * @param degreeCategory The degree category
111     * @return The error label as i18n
112     */
113    protected I18nizableText _getErrorLabel(Content degree, Content degreeCategory)
114    {
115        Map<String, I18nizableTextParameter> params = Map.of(
116                "degree.title", new I18nizableText(degree.getTitle()),
117                "degree.code", new I18nizableText(degree.getDataHolder().getValue("code")),
118                "degreeCategory.title", new I18nizableText(degreeCategory.getTitle()),
119                "degreeCategory.code", new I18nizableText(degreeCategory.getDataHolder().getValue("code"))
120        );
121        return new I18nizableText("plugin.odf-ose", "PLUGINS_ODF_OSE_TABLE_REF_DEGREE_CATEGORY_VALIDATOR_UNCONSISTENCY_ERROR", params);
122    }
123}
124