001/* 002 * Copyright 2025 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.content.code; 017 018import java.util.HashMap; 019import java.util.Map; 020import java.util.stream.Collectors; 021import java.util.stream.Stream; 022 023import org.apache.avalon.framework.configuration.Configuration; 024import org.apache.avalon.framework.configuration.ConfigurationException; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028 029import org.ametys.cms.helper.AmetysIdentifiers; 030import org.ametys.cms.repository.ContentQueryHelper; 031import org.ametys.cms.repository.ContentTypeExpression; 032import org.ametys.core.util.LambdaUtils; 033import org.ametys.odf.ProgramItem; 034import org.ametys.odf.course.CourseFactory; 035import org.ametys.odf.courselist.CourseListFactory; 036import org.ametys.odf.coursepart.CoursePartFactory; 037import org.ametys.odf.orgunit.OrgUnitFactory; 038import org.ametys.odf.program.ContainerFactory; 039import org.ametys.odf.program.ProgramFactory; 040import org.ametys.odf.program.SubProgramFactory; 041import org.ametys.plugins.repository.AmetysObjectResolver; 042import org.ametys.plugins.repository.query.expression.AndExpression; 043import org.ametys.plugins.repository.query.expression.Expression; 044import org.ametys.plugins.repository.query.expression.Expression.Operator; 045import org.ametys.plugins.repository.query.expression.StringExpression; 046 047/** 048 * Implementation of {@link UniqueCodeGenerator} that generates a code composed by a prefix representing the type of content and an incremental number on 6 digits. 049 */ 050public class PrefixedIncrementalCodeGenerator extends AbstractUniqueCodeGenerator implements Serviceable 051{ 052 private static final String __DEFAULT_PREFIX_FOR_PROGRAM = "FOR"; 053 private static final String __DEFAULT_PREFIX_FOR_SUBPROGRAM = "PAR"; 054 private static final String __DEFAULT_PREFIX_FOR_CONTAINER = "CTN"; 055 private static final String __DEFAULT_PREFIX_FOR_COURSELIST = "LST"; 056 private static final String __DEFAULT_PREFIX_FOR_COURSE = "ELP"; 057 private static final String __DEFAULT_PREFIX_FOR_ORGUNIT = "CMP"; 058 private static final String __DEFAULT_PREFIX_FOR_COURSEPART = "HE"; 059 060 private Map<String, String> _prefixByTypes = new HashMap<>(); 061 062 private ServiceManager _smanager; 063 private AmetysObjectResolver _resolver; 064 private AmetysIdentifiers _ametysIdentifiers; 065 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 _smanager = manager; 069 } 070 071 @Override 072 public void configure(Configuration configuration) throws ConfigurationException 073 { 074 super.configure(configuration); 075 076 // Get configured prefixes 077 Map<String, String> configuredPrefixes = Stream.of(configuration.getChildren("prefix")) 078 .collect(Collectors.toMap(LambdaUtils.wrap(c -> c.getAttribute("type")), LambdaUtils.wrap(c -> c.getValue()))); 079 080 081 // Configured prefixes by types with default values 082 _prefixByTypes.put(ProgramFactory.PROGRAM_CONTENT_TYPE, configuredPrefixes.getOrDefault(ProgramFactory.PROGRAM_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_PROGRAM)); 083 _prefixByTypes.put(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE, configuredPrefixes.getOrDefault(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_SUBPROGRAM)); 084 _prefixByTypes.put(ContainerFactory.CONTAINER_CONTENT_TYPE, configuredPrefixes.getOrDefault(ContainerFactory.CONTAINER_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_CONTAINER)); 085 _prefixByTypes.put(CourseListFactory.COURSE_LIST_CONTENT_TYPE, configuredPrefixes.getOrDefault(CourseListFactory.COURSE_LIST_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_COURSELIST)); 086 _prefixByTypes.put(CourseFactory.COURSE_CONTENT_TYPE, configuredPrefixes.getOrDefault(CourseFactory.COURSE_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_COURSE)); 087 _prefixByTypes.put(OrgUnitFactory.ORGUNIT_CONTENT_TYPE, configuredPrefixes.getOrDefault(OrgUnitFactory.ORGUNIT_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_ORGUNIT)); 088 _prefixByTypes.put(CoursePartFactory.COURSE_PART_CONTENT_TYPE, configuredPrefixes.getOrDefault(CoursePartFactory.COURSE_PART_CONTENT_TYPE, __DEFAULT_PREFIX_FOR_COURSEPART)); 089 } 090 091 private AmetysIdentifiers _getAmetysIdentifiers() 092 { 093 // Lazing loading to keep component safe 094 if (_ametysIdentifiers == null) 095 { 096 try 097 { 098 _ametysIdentifiers = (AmetysIdentifiers) _smanager.lookup(AmetysIdentifiers.ROLE); 099 } 100 catch (ServiceException e) 101 { 102 throw new RuntimeException("Unable to lookup after the ametys identifiers", e); 103 } 104 } 105 106 return _ametysIdentifiers; 107 } 108 109 private AmetysObjectResolver _getResolver() 110 { 111 // Lazing loading to keep component safe 112 if (_resolver == null) 113 { 114 try 115 { 116 _resolver = (AmetysObjectResolver) _smanager.lookup(AmetysObjectResolver.ROLE); 117 } 118 catch (ServiceException e) 119 { 120 throw new RuntimeException("Unable to lookup after the ametys object resolver", e); 121 } 122 } 123 124 return _resolver; 125 } 126 127 public String generateUniqueCode(String cTypeId) 128 { 129 String prefix = _prefixByTypes.get(cTypeId); 130 if (prefix == null) 131 { 132 throw new IllegalArgumentException(cTypeId + " is not supported to generate ODF content unique code"); 133 } 134 135 long counter = _getAmetysIdentifiers().readCounter(_pluginName, cTypeId) + 1; 136 String uniqueCode = prefix + counter; 137 while (!_checkUnicity(cTypeId, uniqueCode)) 138 { 139 counter++; 140 uniqueCode = prefix + counter; 141 } 142 _getAmetysIdentifiers().saveCounter(_pluginName, cTypeId, counter); 143 144 return uniqueCode; 145 } 146 147 private boolean _checkUnicity(String cTypeId, String code) 148 { 149 // Check unicity whatever the language and catalog 150 Expression contentTypeExpr = new ContentTypeExpression(Operator.EQ, cTypeId); 151 Expression codeExpr = new StringExpression(ProgramItem.CODE, Operator.EQ, code); 152 153 Expression expr = new AndExpression(contentTypeExpr, codeExpr); 154 155 String xpathQuery = ContentQueryHelper.getContentXPathQuery(expr); 156 return _getResolver().query(xpathQuery).getSize() == 0; 157 } 158}