001/* 002 * Copyright 2018 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.validator; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Collectors; 025import java.util.stream.Stream; 026 027import org.apache.avalon.framework.configuration.Configurable; 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.configuration.ConfigurationException; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033 034import org.ametys.cms.contenttype.validation.AbstractContentValidator; 035import org.ametys.cms.data.ContentValue; 036import org.ametys.cms.repository.Content; 037import org.ametys.odf.ODFHelper; 038import org.ametys.odf.ProgramItem; 039import org.ametys.odf.course.Course; 040import org.ametys.odf.courselist.CourseList; 041import org.ametys.odf.program.ProgramPart; 042import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 043import org.ametys.plugins.repository.data.holder.values.SynchronizableValue; 044import org.ametys.plugins.repository.data.holder.values.SynchronizableValue.Mode; 045import org.ametys.runtime.i18n.I18nizableText; 046import org.ametys.runtime.i18n.I18nizableTextParameter; 047import org.ametys.runtime.model.ElementDefinition; 048import org.ametys.runtime.model.View; 049import org.ametys.runtime.parameter.Errors; 050 051/** 052 * Global validator for {@link ProgramItem} content. 053 * Check that structure to not create an infinite loop 054 */ 055public class ProgramItemHierarchyValidator extends AbstractContentValidator implements Serviceable, Configurable 056{ 057 /** The ODF helper */ 058 protected ODFHelper _odfHelper; 059 060 private Set<String> _childMetadataNames; 061 062 @Override 063 public void service(ServiceManager smanager) throws ServiceException 064 { 065 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 066 } 067 068 @Override 069 public void validate(Content content, Errors errors) 070 { 071 // Nothing 072 } 073 074 public void configure(Configuration configuration) throws ConfigurationException 075 { 076 _childMetadataNames = new HashSet<>(); 077 078 Configuration[] children = configuration.getChildren("childMetadata"); 079 for (Configuration childConf : children) 080 { 081 _childMetadataNames.add(childConf.getAttribute("name")); 082 } 083 } 084 085 @Override 086 public void validate(Content content, Map<String, Object> values, View view, Errors errors) 087 { 088 Set<String> names = getChildMetadataNames(); 089 for (String name : names) 090 { 091 if (view.hasModelViewItem(name) && content instanceof ProgramItem) 092 { 093 List<String> childContentIds = _odfHelper.getChildProgramItems((ProgramItem) content).stream() 094 .map(ProgramItem::getId) 095 .collect(Collectors.toList()); 096 097 ElementDefinition childDefinition = (ElementDefinition) content.getDefinition(name); 098 099 Object value = values.get(name); 100 Object[] valueToValidate = (Object[]) DataHolderHelper.getValueToValidate(value); 101 Mode mode = _getMode(value); 102 103 // Check duplicate child and infinite loop is not necessary when value is removed 104 if (Mode.REMOVE != mode) 105 { 106 ContentValue[] contentValues = Stream.of(valueToValidate) 107 .map(v -> childDefinition.getType().castValue(v)) 108 .toArray(ContentValue[]::new); 109 for (ContentValue contentValue : contentValues) 110 { 111 Content childContent = contentValue.getContent(); 112 113 // Check duplicate child only on APPEND mode 114 if (Mode.APPEND == mode) 115 { 116 if (childContentIds.contains(childContent.getId())) 117 { 118 _addError(errors, childDefinition, content, childContent, "plugin.odf", "PLUGINS_ODF_CONTENT_VALIDATOR_DUPLICATE_CHILD_ERROR"); 119 } 120 } 121 122 if (childContent instanceof ProgramItem) 123 { 124 if (!checkAncestors((ProgramItem) content, (ProgramItem) childContent)) 125 { 126 _addError(errors, childDefinition, content, childContent, "plugin.odf", "PLUGINS_ODF_CONTENT_VALIDATOR_HIERARCHY_ERROR"); 127 } 128 } 129 } 130 } 131 } 132 } 133 } 134 135 private Mode _getMode(Object value) 136 { 137 return value instanceof SynchronizableValue ? ((SynchronizableValue) value).getMode() : Mode.REPLACE; 138 } 139 140 /** 141 * Get the names of child metadata to be checked 142 * @return the child metadata's names 143 */ 144 protected Set<String> getChildMetadataNames() 145 { 146 return _childMetadataNames; 147 } 148 149 /** 150 * Check if the hierarchy of a program item will be still valid if adding the given program item as child. 151 * Return false if the {@link ProgramItem} to add is in the hierarchy of the given {@link ProgramItem} 152 * @param programItem The content to start search 153 * @param childProgramItem The child program item to search in ancestors 154 * @return true if child program item is already part of the hierarchy (ascendant search) 155 */ 156 protected boolean checkAncestors(ProgramItem programItem, ProgramItem childProgramItem) 157 { 158 if (programItem.equals(childProgramItem)) 159 { 160 return false; 161 } 162 163 List<? extends ProgramItem> parents = new ArrayList<>(); 164 if (programItem instanceof Course) 165 { 166 parents = ((Course) programItem).getParentCourseLists(); 167 } 168 else if (programItem instanceof CourseList) 169 { 170 parents = ((CourseList) programItem).getParentCourses(); 171 } 172 else if (programItem instanceof ProgramPart) 173 { 174 parents = ((ProgramPart) programItem).getProgramPartParents(); 175 } 176 177 for (ProgramItem parent : parents) 178 { 179 if (parent.equals(childProgramItem)) 180 { 181 return false; 182 } 183 else if (!checkAncestors(parent, childProgramItem)) 184 { 185 return false; 186 } 187 } 188 189 return true; 190 } 191 192 /** 193 * Add an error 194 * @param errors The list of errors 195 * @param childDefinition The child metadata definition 196 * @param content The content being edited 197 * @param childContent The content to add as child content 198 * @param catalog the i18n catalog 199 * @param i18nKey the i18n key for the error message 200 */ 201 protected void _addError(Errors errors, ElementDefinition childDefinition, Content content, Content childContent, String catalog, String i18nKey) 202 { 203 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 204 i18nParams.put("title", new I18nizableText(content.getTitle())); 205 i18nParams.put("childTitle", new I18nizableText(childContent.getTitle())); 206 i18nParams.put("fieldLabel", childDefinition.getLabel()); 207 errors.addError(new I18nizableText(catalog, i18nKey, i18nParams)); 208 } 209}