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