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