001/* 002 * Copyright 2024 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.properties.section.content; 017 018import java.util.ArrayList; 019import java.util.Comparator; 020import java.util.HashMap; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Optional; 026import java.util.function.Function; 027import java.util.function.Predicate; 028 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.commons.lang3.LocaleUtils; 033 034import org.ametys.cms.properties.section.AbstractDefaultPropertySection; 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.repository.Content.ReferencingContentsSearch; 037import org.ametys.odf.ODFHelper; 038import org.ametys.odf.ProgramItem; 039import org.ametys.odf.course.Course; 040import org.ametys.odf.coursepart.CoursePart; 041import org.ametys.plugins.repository.AmetysObject; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043 044/** 045 * The referencer items for {@link ProgramItem}. 046 * It displays educational paths, children and other references to the content. 047 */ 048public class ProgramItemReferencerSection extends AbstractDefaultPropertySection implements Serviceable 049{ 050 private static final ListProgramItemComparator __LIST_PROGRAM_ITEM_COMPARATOR = new ListProgramItemComparator(); 051 052 private AmetysObjectResolver _resolver; 053 private ODFHelper _odfHelper; 054 055 public void service(ServiceManager smanager) throws ServiceException 056 { 057 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 058 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 059 } 060 061 public boolean supports(AmetysObject ametysObject) 062 { 063 return ametysObject instanceof ProgramItem; 064 } 065 066 @Override 067 protected Map<String, Object> buildData(AmetysObject ametysObject) 068 { 069 ProgramItem programItem = (ProgramItem) ametysObject; 070 071 Map<String, Object> resultMap = new LinkedHashMap<>(); 072 073 resultMap.put("educationalPaths", _getEducationalPaths(programItem)); 074 resultMap.put("children", _getChildren(programItem)); 075 resultMap.put("otherReferences", _getOtherReferences((Content) ametysObject)); 076 077 return resultMap; 078 } 079 080 private List<List<Map<String, Object>>> _getEducationalPaths(ProgramItem programItem) 081 { 082 // To avoid to return path only with the program item itself 083 if (_odfHelper.hasParentProgramItems(programItem)) 084 { 085 return _odfHelper.getEducationalPaths(programItem) 086 .stream() 087 .map(p -> p.getProgramItems(_resolver)) 088 .sorted(__LIST_PROGRAM_ITEM_COMPARATOR) 089 .map(this::_programItemList2JSON) 090 .toList(); 091 } 092 093 return List.of(); 094 } 095 096 private List<Map<String, Object>> _getChildren(ProgramItem programItem) 097 { 098 List<Map<String, Object>> children = new ArrayList<>(); 099 100 // Add child program items to children 101 children.addAll(_programItemList2JSON(_odfHelper.getChildProgramItems(programItem))); 102 103 // Add course parts to children only if the content is a course 104 if (programItem instanceof Course course) 105 { 106 children.addAll(_coursePartList2JSON(course.getCourseParts())); 107 } 108 109 return children; 110 } 111 112 private Map<String, Object> _getOtherReferences(Content content) 113 { 114 Map<String, Object> resultMap = new LinkedHashMap<>(); 115 116 ReferencingContentsSearch referencingContents = content.searchReferencingContents(100); 117 118 // Build the list of already referenced contents 119 List<Object> alreadyReferencedContents = new ArrayList<>(); 120 alreadyReferencedContents.addAll(_odfHelper.getChildProgramItems((ProgramItem) content)); 121 if (content instanceof Course course) 122 { 123 alreadyReferencedContents.addAll(course.getCourseParts()); 124 } 125 alreadyReferencedContents.addAll(_odfHelper.getParentProgramItems((ProgramItem) content)); 126 127 Locale locale = Optional.of(content) 128 .map(Content::getLanguage) 129 .map(LocaleUtils::toLocale) 130 .orElse(null); 131 132 resultMap.put( 133 "referencingContents", 134 referencingContents.referencingContents() 135 .stream() 136 // Remove already referenced contents (direct children and direct parents) 137 .filter(Predicate.not(alreadyReferencedContents::contains)) 138 .map(c -> _content2JSON(c, locale)) 139 .toList() 140 ); 141 142 if (referencingContents.hasOtherReferences()) 143 { 144 resultMap.put("hasOther", true); 145 } 146 return resultMap; 147 } 148 149 private List<Map<String, Object>> _programItemList2JSON(List<ProgramItem> programItems) 150 { 151 return _odfItemList2JSON(programItems, c -> ((ProgramItem) c).getDisplayCode()); 152 } 153 154 private List<Map<String, Object>> _coursePartList2JSON(List<CoursePart> courseParts) 155 { 156 return _odfItemList2JSON(courseParts, c -> ((CoursePart) c).getDisplayCode()); 157 } 158 159 private List<Map<String, Object>> _odfItemList2JSON(List<?> contents, Function<Content, String> getDisplayCodeFn) 160 { 161 return contents.stream() 162 .map(Content.class::cast) 163 .map(c -> _odfItem2JSON(c, getDisplayCodeFn)) 164 .toList(); 165 } 166 167 private Map<String, Object> _odfItem2JSON(Content content, Function<Content, String> getDisplayCodeFn) 168 { 169 Map<String, Object> json = new HashMap<>(); 170 json.put("id", content.getId()); 171 json.put("title", content.getTitle()); 172 json.put("code", getDisplayCodeFn.apply(content)); 173 return json; 174 } 175 176 private Map<String, Object> _content2JSON(Content content, Locale locale) 177 { 178 return Map.of( 179 "id", content.getId(), 180 "title", content.getTitle(locale) 181 ); 182 } 183 184 /** 185 * Comparator of {@link List} composing of {@link ProgramItem} by title. 186 */ 187 private static final class ListProgramItemComparator implements Comparator<List<ProgramItem>> 188 { 189 public int compare(List<ProgramItem> l1, List<ProgramItem> l2) 190 { 191 int l1Size = l1.size(); 192 int l2Size = l2.size(); 193 194 for (int i = 0; i < l1Size; i++) 195 { 196 if (l2Size <= i) 197 { 198 // l1 is greater than l2 199 return 1; 200 } 201 else 202 { 203 // Compare title 204 int comparing = ((Content) l1.get(i)).getTitle().compareTo(((Content) l2.get(i)).getTitle()); 205 if (comparing != 0) 206 { 207 return comparing; 208 } 209 } 210 } 211 212 // Lists are the same at the end of reading l1 213 // Then compare size of the lists 214 return Integer.compare(l1Size, l2Size); 215 } 216 } 217}