001/*
002 *  Copyright 2010 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 */
016
017package org.ametys.cms.content;
018
019import java.io.IOException;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.cocoon.ProcessingException;
027import org.apache.cocoon.environment.ObjectModelHelper;
028import org.apache.cocoon.generation.ServiceableGenerator;
029import org.apache.cocoon.xml.AttributesImpl;
030import org.apache.cocoon.xml.XMLUtils;
031import org.apache.commons.lang3.StringUtils;
032import org.xml.sax.SAXException;
033
034import org.ametys.cms.content.references.OutgoingReferences;
035import org.ametys.cms.contenttype.ContentConstants;
036import org.ametys.cms.contenttype.ContentType;
037import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
038import org.ametys.cms.contenttype.MetadataDefinition;
039import org.ametys.cms.contenttype.MetadataDefinitionHolder;
040import org.ametys.cms.contenttype.RepeaterDefinition;
041import org.ametys.cms.repository.Content;
042import org.ametys.cms.transformation.ConsistencyChecker;
043import org.ametys.cms.transformation.ConsistencyChecker.CHECK;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.runtime.i18n.I18nizableText;
047
048/**
049 * Generates the consistency report for the given content
050 */
051public class ConsistencyGenerator extends ServiceableGenerator
052{
053    /** Repository content */
054    protected AmetysObjectResolver _resolver;
055    /** The consistency checker */
056    protected ConsistencyChecker _consistencyChecker;
057    /** The content types extension point */
058    protected ContentTypeExtensionPoint _cTypeEP;
059    
060    @Override
061    public void service(ServiceManager smanager) throws ServiceException
062    {
063        super.service(smanager);
064        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
065        _consistencyChecker = (ConsistencyChecker) smanager.lookup(ConsistencyChecker.ROLE);
066        _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
067    }
068
069    @Override
070    public void generate() throws IOException, SAXException, ProcessingException
071    {
072        contentHandler.startDocument();
073        XMLUtils.startElement(contentHandler, "contents");
074        
075        @SuppressWarnings("unchecked")
076        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
077        @SuppressWarnings("unchecked")
078        List<String> contentsId = (List<String>) jsParameters.get("contentsId");
079        for (String contentId : contentsId)
080        {
081            try
082            {
083                Content content = _resolver.resolveById(contentId);
084                
085                AttributesImpl attrs = new AttributesImpl();
086                attrs.addCDATAAttribute("id", content.getId());
087                attrs.addCDATAAttribute("title", content.getTitle());
088                attrs.addCDATAAttribute("name", content.getName());
089                attrs.addCDATAAttribute("path", content.getPath());
090                attrs.addCDATAAttribute("type", StringUtils.join(content.getTypes(), ','));
091                attrs.addCDATAAttribute("lang", content.getLanguage());
092
093                XMLUtils.startElement(contentHandler, "content", attrs);
094                
095                Map<String, MetadataDefinition> metadataDefToSax = new HashMap<>();
096                Map<String, OutgoingReferences> referencesByPath = content.getOutgoingReferences();
097                
098                for (String valuePath : referencesByPath.keySet())
099                {
100                    // The metadata path is the value path with repeater indices stripped.
101                    String metaPath = valuePath.replaceAll("\\[[0-9]+\\]", "");
102                    
103                    Map<String, MetadataDefinition> metadataDefByPath = getMetadataDefinitionListByPath(valuePath, content.getTypes(), content.getMixinTypes());
104                    
105                    // Add these metadata definitions to the set of metadata definition to SAX.
106                    metadataDefToSax.putAll(metadataDefByPath);
107                    
108                    // SAX'ing consistency info
109                    OutgoingReferences references = referencesByPath.get(valuePath);
110                    for (String referenceType : references.keySet())
111                    {
112                        for (String referenceValue : references.get(referenceType))
113                        {
114                            attrs.clear();
115                            attrs.addCDATAAttribute("type", referenceType);
116                            attrs.addCDATAAttribute("element", referenceValue);
117                            attrs.addCDATAAttribute("path", metaPath);
118                            I18nizableText label = _consistencyChecker.getLabel(referenceType, referenceValue);
119                            
120                            CHECK check = CHECK.SERVER_ERROR;
121                            try
122                            {
123                                check = _consistencyChecker.checkConsistency(referenceType, referenceValue, false);
124                            }
125                            catch (Exception e)
126                            {
127                                // Ignore, consider it a failure.
128                            }
129                            
130                            switch (check)
131                            {
132                                case SUCCESS:
133                                    XMLUtils.startElement(contentHandler, "success", attrs);
134                                    label.toSAX(contentHandler);
135                                    XMLUtils.endElement(contentHandler, "success");
136                                    break;
137                                case UNKNOWN:
138                                    XMLUtils.startElement(contentHandler, "unknown", attrs);
139                                    label.toSAX(contentHandler);
140                                    XMLUtils.endElement(contentHandler, "unknown");
141                                    break;
142                                case UNAUTHORIZED:
143                                    XMLUtils.startElement(contentHandler, "unauthorized", attrs);
144                                    label.toSAX(contentHandler);
145                                    XMLUtils.endElement(contentHandler, "unauthorized");
146                                    break;
147                                case NOT_FOUND:
148                                    XMLUtils.startElement(contentHandler, "not-found", attrs);
149                                    label.toSAX(contentHandler);
150                                    XMLUtils.endElement(contentHandler, "not-found");
151                                    break;
152                                case SERVER_ERROR:
153                                default:
154                                    XMLUtils.startElement(contentHandler, "server-error", attrs);
155                                    label.toSAX(contentHandler);
156                                    XMLUtils.endElement(contentHandler, "server-error");
157                                    break;
158                            }
159                        }
160                    }
161                }
162                
163                // SAX metadata definition for used for this content
164                for (String path : metadataDefToSax.keySet())
165                {
166                    MetadataDefinition metadataDefinition = metadataDefToSax.get(path);
167                    
168                    attrs.clear();
169                    attrs.addCDATAAttribute("path", path);
170                    attrs.addCDATAAttribute("is-repeater", Boolean.toString(metadataDefinition instanceof RepeaterDefinition));
171                    XMLUtils.startElement(contentHandler, "metadata-definition", attrs);
172                    metadataDefinition.getLabel().toSAX(contentHandler);
173                    XMLUtils.endElement(contentHandler, "metadata-definition");
174                }
175                
176                XMLUtils.endElement(contentHandler, "content");
177            }
178            catch (UnknownAmetysObjectException e)
179            {
180                getLogger().warn("Can not check consistency of non existing content '" + contentId + "'");
181            }
182        }
183        
184        XMLUtils.endElement(contentHandler, "contents");
185        contentHandler.endDocument();
186    }
187    
188    /**
189     * Retrieve the list of metadata definition for the given path
190     * @param metadataValuePath the metadata value path. It is slash-separated and repeaters are present as indices, eg "mycomposite/myrepeater[3]/mymetadata".
191     * @param cTypes The id of content types
192     * @param mixins The id of mixins
193     * @return The list of metadata definition (or null)
194     */
195    protected Map<String, MetadataDefinition> getMetadataDefinitionListByPath(String metadataValuePath, String[] cTypes, String[] mixins)
196    {
197        Map<String, MetadataDefinition> metadataDefByPath = new HashMap<>();
198        
199        for (String id : cTypes)
200        {
201            ContentType cType = _cTypeEP.getExtension(id);
202            
203            _getMetadataDefinitionListByPath(metadataValuePath, cType, metadataDefByPath, "");
204            if (!metadataDefByPath.isEmpty())
205            {
206                return metadataDefByPath;
207            }
208        }
209
210        for (String id : mixins)
211        {
212            ContentType cType = _cTypeEP.getExtension(id);
213            
214            _getMetadataDefinitionListByPath(metadataValuePath, cType, metadataDefByPath, "");
215            if (!metadataDefByPath.isEmpty())
216            {
217                return metadataDefByPath;
218            }
219        }
220
221        return metadataDefByPath;
222    }
223    
224    /**
225     * Retrieve the list of metadata definition for the given path
226     * @param metadataValuePath the metadata value path. It is slash-separated and repeaters are present as indices, eg "mycomposite/myrepeater[3]/mymetadata".
227     * @param metadataDefHolder The metadata def holder (such as the content type)
228     * @param metadataDefByPath The map of metadata definition by path, to be filled.
229     * @param prefix The metadata path prefix.
230    */
231    protected void _getMetadataDefinitionListByPath(String metadataValuePath, MetadataDefinitionHolder metadataDefHolder, Map<String, MetadataDefinition> metadataDefByPath, String prefix)
232    {
233        String metadataName = StringUtils.substringBefore(metadataValuePath, ContentConstants.METADATA_PATH_SEPARATOR);
234        String subMetadataPath = StringUtils.substringAfter(metadataValuePath, ContentConstants.METADATA_PATH_SEPARATOR);
235        // Ignore the repeater entry if any.
236        metadataName = StringUtils.substringBefore(metadataName, "[");
237        
238        MetadataDefinition metadataDefinition = metadataDefHolder.getMetadataDefinition(metadataName);
239        
240        if (metadataDefinition != null)
241        {
242            String metaPath = prefix + metadataName;
243            metadataDefByPath.put(metaPath, metadataDefinition);
244            
245            // If there is some path remaining, recurse.
246            if (StringUtils.isNotEmpty(subMetadataPath))
247            {
248                _getMetadataDefinitionListByPath(subMetadataPath, metadataDefinition, metadataDefByPath, metaPath + ContentConstants.METADATA_PATH_SEPARATOR);
249            }
250        }
251    }
252}