001/*
002 *  Copyright 2023 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.cms.content.consistency;
017
018import java.io.IOException;
019import java.time.ZonedDateTime;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.cocoon.ProcessingException;
028import org.apache.cocoon.environment.ObjectModelHelper;
029import org.apache.cocoon.environment.Request;
030import org.apache.cocoon.generation.AbstractGenerator;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.lang3.StringUtils;
034import org.apache.commons.lang3.Strings;
035import org.xml.sax.SAXException;
036
037import org.ametys.cms.repository.Content;
038import org.ametys.core.util.DateUtils;
039import org.ametys.core.util.ServerCommHelper;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.runtime.i18n.I18nizableText;
042
043/**
044 * Retrieve all content consistency result stored and sax them
045 */
046public class ContentConsistencyResultGenerator extends AbstractGenerator implements Serviceable
047{
048    /** the name of the attribute use to recover the list of result */
049    public static final String RESULTS_REQUEST_ATTRIBUTE_NAME = "contentConsistencyResult";
050    
051    /** The ametys object resolver */
052    protected AmetysObjectResolver _resolver;
053    private ServerCommHelper _serverCommHelper;
054    private ContentConsistencySearchModel _searchModel;
055
056    public void service(ServiceManager manager) throws ServiceException
057    {
058        _searchModel = (ContentConsistencySearchModel) manager.lookup(ContentConsistencySearchModel.ROLE);
059        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
060        _serverCommHelper = (ServerCommHelper) manager.lookup(ServerCommHelper.ROLE);
061    }
062    
063    public void generate() throws IOException, SAXException, ProcessingException
064    {
065        Request request = ObjectModelHelper.getRequest(objectModel);
066        Map<String, Object> jsParameters = _serverCommHelper.getJsParameters();
067        
068        
069        contentHandler.startDocument();
070        XMLUtils.startElement(contentHandler, "export");
071        
072        XMLUtils.startElement(contentHandler, "columns");
073        
074        Map<String, Object> model = _searchModel.getModel();
075        @SuppressWarnings("unchecked")
076        List<Map<String, Object>> modelColumns = (List<Map<String, Object>>) model.get("columns");
077        @SuppressWarnings("unchecked")
078        List<String> columns = jsParameters != null ? (List<String>) jsParameters.get("columns") : List.of();
079        for (Map<String, Object> column : modelColumns)
080        {
081            if (columns.isEmpty() || columns.contains(column.get("path")))
082            {
083                AttributesImpl attrs = new AttributesImpl();
084                attrs.addCDATAAttribute("id", (String) column.get("path"));
085                attrs.addCDATAAttribute("type", (String) column.get("type"));
086                XMLUtils.startElement(contentHandler, "column", attrs);
087                Object label = column.get("label");
088                if (label instanceof I18nizableText i18n)
089                {
090                    i18n.toSAX(contentHandler);
091                }
092                else
093                {
094                    XMLUtils.data(contentHandler, label.toString());
095                    
096                }
097                XMLUtils.endElement(contentHandler, "column");
098            }
099        }
100        XMLUtils.endElement(contentHandler, "columns");
101        
102        XMLUtils.startElement(contentHandler, "contents");
103        
104        @SuppressWarnings("unchecked")
105        List<Map<String, Object>> results = (List<Map<String, Object>>) request.getAttribute(RESULTS_REQUEST_ATTRIBUTE_NAME);
106        if (results != null)
107        {
108            for (Map<String, Object> result : results)
109            {
110                _saxContentConsistency(result, columns);
111            }
112        }
113        
114        XMLUtils.endElement(contentHandler, "contents");
115        XMLUtils.endElement(contentHandler, "export");
116        contentHandler.endDocument();
117    }
118
119    /**
120     * Generate information on content consistency. 
121     * @param result the content consistency checker result
122     * @param columns the names of the columns to SAX
123     * @throws SAXException if an errors occurs generating the data.
124     */
125    protected void _saxContentConsistency(Map<String, Object> result, List<String> columns) throws SAXException
126    {
127        XMLUtils.startElement(contentHandler, "content");
128        // if there is no columns, we use all the keys
129        for (String column : !columns.isEmpty() ? columns : result.keySet())
130        {
131            AttributesImpl attrs = new AttributesImpl();
132            attrs.addCDATAAttribute("fullModelPath", column);
133            Object value = result.get(column);
134            if (value instanceof String str)
135            {
136                XMLUtils.createElement(contentHandler, column, attrs, str);
137            }
138            else if (value instanceof Long count)
139            {
140                XMLUtils.createElement(contentHandler, column, attrs, count.toString());
141            }
142            else if (value instanceof ZonedDateTime date)
143            {
144                XMLUtils.createElement(contentHandler, column, attrs, DateUtils.zonedDateTimeToString(date));
145            }
146            else if (Strings.CS.equals(column, "contributor"))
147            {
148                @SuppressWarnings("unchecked")
149                Map<String, Object> mapValue = (Map<String, Object>) value;
150                String fullName = (String) mapValue.get("fullname");
151                if (StringUtils.isNotBlank(fullName))
152                {
153                    XMLUtils.createElement(contentHandler, column, attrs, fullName);
154                }
155                else
156                {
157                    String login = (String) mapValue.get("login");
158                    String population = (String) mapValue.get("population");
159                    if (StringUtils.isNoneBlank(login, population))
160                    {
161                        XMLUtils.createElement(contentHandler, column, attrs, login + "#" + population);
162                    }
163                    else
164                    {
165                        XMLUtils.createElement(contentHandler, column, attrs);
166                    }
167                }
168            }
169            else if (Strings.CS.equals(column, "workflowStep"))
170            {
171                @SuppressWarnings("unchecked")
172                Map<String, Object> mapValue = (Map<String, Object>) value;
173                XMLUtils.startElement(contentHandler, column, attrs);
174                I18nizableText label = (I18nizableText) mapValue.get("name");
175                if (label != null)
176                {
177                    label.toSAX(contentHandler);
178                }
179                XMLUtils.endElement(contentHandler, column);
180            }
181            else if (Strings.CS.equals(column, "contentTypes"))
182            {
183                @SuppressWarnings("unchecked")
184                List<Map<String, Object>> jsonValues = (List<Map<String, Object>>) value;
185                List<I18nizableText> labels = jsonValues.stream()
186                    .map(map -> (I18nizableText) map.get("label"))
187                    .filter(Objects::nonNull)
188                    .toList();
189                for (I18nizableText label : labels)
190                {
191                    XMLUtils.startElement(contentHandler, column, attrs);
192                    label.toSAX(contentHandler);
193                    XMLUtils.endElement(contentHandler, column);
194                }
195            }
196        }
197        XMLUtils.endElement(contentHandler, "content");
198    }
199    
200    /**
201     * Sax additional data on the content 
202     * @param content the content.
203     * @param atts the attributes the will be saxed
204     */
205    protected void _saxAdditionalContentAttributes (Content content, AttributesImpl atts)
206    {
207        // Nothing to do
208    }
209}