001/* 002 * Copyright 2020 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.plugins.odfsync.apogee.scc; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Optional; 022import java.util.Set; 023 024import org.apache.avalon.framework.activity.Initializable; 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.context.Context; 027import org.apache.avalon.framework.context.ContextException; 028import org.apache.avalon.framework.context.Contextualizable; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.cocoon.components.ContextHelper; 033import org.apache.cocoon.environment.Request; 034import org.apache.commons.lang3.StringUtils; 035import org.slf4j.Logger; 036 037import org.ametys.cms.repository.Content; 038import org.ametys.cms.repository.ContentDAO; 039import org.ametys.cms.repository.ModifiableContent; 040import org.ametys.odf.ODFHelper; 041import org.ametys.odf.ProgramItem; 042import org.ametys.odf.catalog.CatalogsManager; 043import org.ametys.odf.course.Course; 044import org.ametys.odf.coursepart.CoursePart; 045import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDAO; 046import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionHelper; 047import org.ametys.runtime.config.Config; 048 049/** 050 * Helper for Apogee Synchronizable Contents Collections. 051 */ 052public class ApogeeSynchronizableContentsCollectionHelper implements Serviceable, Component, Contextualizable, Initializable 053{ 054 /** The Avalon Role */ 055 public static final String ROLE = ApogeeSynchronizableContentsCollectionHelper.class.getName(); 056 057 /** Request attribute name to store handled contents during import or synchronization. */ 058 public static final String HANDLED_CONTENTS = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$handledContents"; 059 060 /** Request attribute name to store catalog during import or synchronization. */ 061 public static final String CATALOG = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$catalog"; 062 063 /** Request attribute name to store language during import or synchronization. */ 064 public static final String LANG = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$lang"; 065 066 /** SCC DAO */ 067 protected SynchronizableContentsCollectionHelper _sccHelper; 068 069 /** The ODF Helper */ 070 protected ODFHelper _odfHelper; 071 072 /** SCC DAO */ 073 protected SynchronizableContentsCollectionDAO _sccDAO; 074 075 /** Content DAO */ 076 protected ContentDAO _contentDAO; 077 078 /** The catalogs manager */ 079 protected CatalogsManager _catalogsManager; 080 081 /** Context */ 082 protected Context _context; 083 084 /** Default language configured for ODF */ 085 protected String _odfLang; 086 087 088 @Override 089 public void service(ServiceManager smanager) throws ServiceException 090 { 091 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 092 _sccHelper = (SynchronizableContentsCollectionHelper) smanager.lookup(SynchronizableContentsCollectionHelper.ROLE); 093 _sccDAO = (SynchronizableContentsCollectionDAO) smanager.lookup(SynchronizableContentsCollectionDAO.ROLE); 094 _contentDAO = (ContentDAO) smanager.lookup(ContentDAO.ROLE); 095 _catalogsManager = (CatalogsManager) smanager.lookup(CatalogsManager.ROLE); 096 } 097 098 public void contextualize(Context context) throws ContextException 099 { 100 _context = context; 101 } 102 103 public void initialize() throws Exception 104 { 105 _odfLang = Config.getInstance().getValue("odf.programs.lang"); 106 } 107 108 /** 109 * Synchronize the content or its children if the content has no Apogee SCC 110 * @param content the content 111 * @param logger the logger 112 */ 113 public void synchronizeContent(ModifiableContent content, Logger logger) 114 { 115 boolean handled = false; 116 117 AbstractApogeeSynchronizableContentsCollection scc = getContentSCC(content, logger).orElse(null); 118 // The content has a SCC, so synchronize it 119 if (scc != null) 120 { 121 try 122 { 123 Map<String, Object> searchParams = new HashMap<>(); 124 String idField = scc.getIdField(); 125 String syncCode = content.getValue(idField); 126 if (StringUtils.isNotBlank(syncCode)) 127 { 128 searchParams.put(idField, syncCode); 129 if (scc.removalSync() && scc.getTotalCount(searchParams, logger) == 0) 130 { 131 _contentDAO.forceDeleteContentsWithLog(List.of(content), null, logger); 132 } 133 else 134 { 135 scc.synchronizeContent(content, logger); 136 } 137 handled = true; 138 } 139 else 140 { 141 logger.warn("The synchronization code of the content '{}' ({}) is empty but it has the SCC '{}'. It has been skipped.", content.getTitle(), content.getId(), scc.getId()); 142 } 143 } 144 catch (Exception e) 145 { 146 logger.error("An error occurred synchronized content '{}' ({}) from SCC '{}'", content.getTitle(), content.getId(), scc.getId(), e); 147 } 148 } 149 150 151 // The content has not been handled because of error or the content is manually created, so search in deeper children 152 if (!handled && addToHandleContents(content.getId()) && content instanceof ProgramItem programItem) 153 { 154 for (ProgramItem syncChild : _odfHelper.getChildProgramItems(programItem)) 155 { 156 synchronizeContent((ModifiableContent) syncChild, logger); 157 } 158 159 if (content instanceof Course course) 160 { 161 for (CoursePart syncChild : course.getCourseParts()) 162 { 163 synchronizeContent(syncChild, logger); 164 } 165 } 166 } 167 } 168 169 /** 170 * Get the Apogee SCC for the content if it exists 171 * @param content The content to search on 172 * @param logger the logger 173 * @return the Apogee SCC 174 */ 175 public Optional<AbstractApogeeSynchronizableContentsCollection> getContentSCC(Content content, Logger logger) 176 { 177 try 178 { 179 return _sccHelper.getSynchronizableCollectionIds(content) 180 .stream() 181 .map(_sccDAO::getSynchronizableContentsCollection) 182 .filter(AbstractApogeeSynchronizableContentsCollection.class::isInstance) 183 .map(AbstractApogeeSynchronizableContentsCollection.class::cast) 184 .findFirst(); 185 } 186 catch (Exception e) 187 { 188 logger.error("An error occurred getting ametys-internal:scc property", e); 189 } 190 191 return Optional.empty(); 192 } 193 194 /** 195 * Add the content ID to the handle contents list. 196 * @param contentId Content ID 197 * @return <code>true</code> if the content ID have been added, <code>false</code> is returned if the content ID already exists in the handle contents list. 198 */ 199 public boolean addToHandleContents(String contentId) 200 { 201 return _addContentToRequestAttribute(contentId, HANDLED_CONTENTS); 202 } 203 204 private boolean _addContentToRequestAttribute(String contentId, String attributeName) 205 { 206 Request request = ContextHelper.getRequest(_context); 207 @SuppressWarnings("unchecked") 208 Set<String> handleContents = (Set<String>) request.getAttribute(attributeName); 209 boolean added = handleContents.add(contentId); 210 request.setAttribute(attributeName, handleContents); 211 return added; 212 } 213 214 /** 215 * Get the synchronization catalog, can be the one defined by the synchronized content 216 * or the default catalog. 217 * @return the synchronization catalog, can be null 218 */ 219 public String getSynchronizationCatalog() 220 { 221 return Optional.of(ContextHelper.getRequest(_context)) 222 .map(r -> (String) r.getAttribute(CATALOG)) 223 .orElseGet(() -> _catalogsManager.getDefaultCatalogName()); 224 } 225 226 /** 227 * Get the synchronization language, can be the one defined by the synchronized content 228 * or the ODF language. 229 * @return the synchronization language 230 */ 231 public String getSynchronizationLang() 232 { 233 return Optional.of(ContextHelper.getRequest(_context)) 234 .map(r -> (String) r.getAttribute(LANG)) 235 .orElse(_odfLang); 236 } 237}