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 */
016package org.ametys.cms.content;
017
018import java.io.IOException;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.cocoon.ProcessingException;
025import org.apache.cocoon.environment.ObjectModelHelper;
026import org.apache.cocoon.environment.Request;
027import org.apache.cocoon.generation.Generator;
028import org.apache.cocoon.generation.ServiceableGenerator;
029import org.apache.cocoon.xml.AttributesImpl;
030import org.apache.cocoon.xml.XMLUtils;
031import org.apache.commons.lang.StringUtils;
032import org.apache.commons.lang3.ArrayUtils;
033import org.xml.sax.SAXException;
034
035import org.ametys.cms.contenttype.AbstractMetadataSetElement;
036import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
037import org.ametys.cms.contenttype.ContentTypesHelper;
038import org.ametys.cms.contenttype.Fieldset;
039import org.ametys.cms.contenttype.MetadataDefinition;
040import org.ametys.cms.contenttype.MetadataDefinitionReference;
041import org.ametys.cms.contenttype.MetadataManager;
042import org.ametys.cms.contenttype.MetadataSet;
043import org.ametys.cms.contenttype.MetadataType;
044import org.ametys.cms.contenttype.RepeaterDefinition;
045import org.ametys.cms.contenttype.RichTextMetadataDefinition;
046import org.ametys.cms.contenttype.SemanticAnnotation;
047import org.ametys.cms.repository.Content;
048import org.ametys.core.right.RightManager;
049import org.ametys.core.right.RightManager.RightResult;
050import org.ametys.core.user.CurrentUserProvider;
051import org.ametys.runtime.i18n.I18nizableText;
052import org.ametys.runtime.parameter.Enumerator;
053import org.ametys.runtime.parameter.ParameterHelper;
054import org.ametys.runtime.parameter.Validator;
055
056/**
057 * {@link Generator} for rendering the structure of a metadata set
058 */
059public class MetadataSetDefGenerator extends ServiceableGenerator
060{
061    /** Content type extension point. */
062    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
063    /** Metadata manager. */
064    protected MetadataManager _metadataManager;
065    /** Helper for content types */
066    protected ContentTypesHelper _contentTypesHelper;
067    /** Rights manager */
068    protected RightManager _rightManager;
069    /** The current user provider */
070    protected CurrentUserProvider _currentUserProvider;
071    /** The content helper */
072    protected ContentHelper _contentHelper;
073    
074    @Override
075    public void service(ServiceManager serviceManager) throws ServiceException
076    {
077        super.service(serviceManager);
078        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
079        _metadataManager = (MetadataManager) serviceManager.lookup(MetadataManager.ROLE);
080        _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
081        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
082        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
083        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
084    }
085    
086    @Override
087    public void generate() throws IOException, SAXException, ProcessingException
088    {
089        Request request = ObjectModelHelper.getRequest(objectModel);
090        Content content = (Content) request.getAttribute(Content.class.getName());
091        boolean isEditionMetadataSet = parameters.getParameterAsBoolean("isEditionMetadataSet", false);
092        String metadataSetName = parameters.getParameter("metadataSetName", "main");
093        
094        contentHandler.startDocument();
095        
096        AttributesImpl attrs = new AttributesImpl();
097        attrs.addCDATAAttribute("id", content.getId());
098        attrs.addCDATAAttribute("name", content.getName());
099        attrs.addCDATAAttribute("title", _contentHelper.getTitle(content));
100        if (content.getLanguage() != null)
101        {
102            attrs.addCDATAAttribute("lang", content.getLanguage());
103        }
104        
105        XMLUtils.startElement(contentHandler, "content", attrs);
106
107        AttributesImpl attrs2 = new AttributesImpl();
108        attrs2.addCDATAAttribute("metadataSetName", metadataSetName);
109        attrs2.addCDATAAttribute("isEditionMetadataSet", Boolean.toString(isEditionMetadataSet));
110        XMLUtils.startElement(contentHandler, "metadataSet", attrs2);
111        _saxMetadataSet(content, metadataSetName, isEditionMetadataSet);
112        XMLUtils.endElement(contentHandler, "metadataSet");
113
114        XMLUtils.endElement(contentHandler, "content");
115        contentHandler.endDocument();
116    }
117    
118    /**
119     * SAX metadata set structure
120     * @param content the content.
121     * @param metadataSetName The name of the metadata set to sax
122     * @param isEditionMetadataSet True to use the edit metadata set
123     * @throws SAXException if an error occurs while SAXing.
124     * @throws IOException if an error occurs.
125     * @throws ProcessingException if an error occurs.
126     */
127    protected void _saxMetadataSet(Content content, String metadataSetName, boolean isEditionMetadataSet) throws SAXException, ProcessingException, IOException
128    {
129        MetadataSet metadataSet = null;
130        
131        if (isEditionMetadataSet)
132        {
133            metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes());
134        }
135        else
136        {
137            metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes());
138        }
139        
140        if (metadataSet == null)
141        {
142            throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type '%s'",
143                                          metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ',')));
144        }
145        
146        _saxMetadataSetElement(content, ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.EMPTY_STRING_ARRAY, null, metadataSet);
147    }
148
149    /**
150     * SAX metadata set element.
151     * @param content the content. Can be null then set content types and mixins
152     * @param contentTypes Can be null then set content
153     * @param mixinTypes Can be null then set content
154     * @param metadataDefinition the metadata definition.
155     * @param metadataSetElement the metadata set element.
156     * @throws SAXException if an error occurs while SAXing.
157     */
158    protected void _saxMetadataSetElement(Content content, String[] contentTypes, String[] mixinTypes, MetadataDefinition metadataDefinition, AbstractMetadataSetElement metadataSetElement) throws SAXException
159    {
160        for (AbstractMetadataSetElement subMetadataSetElement : metadataSetElement.getElements())
161        {
162            if (subMetadataSetElement instanceof MetadataDefinitionReference)
163            {
164                MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subMetadataSetElement;
165                String metadataName = metadataDefRef.getMetadataName();
166                MetadataDefinition metaDef = null;
167                if (content != null) 
168                {
169                    metaDef = _getMetadataDefinition(content.getTypes(), content.getTypes(), metadataDefinition, metadataName);
170                }
171                else
172                {
173                    metaDef = _getMetadataDefinition(contentTypes, mixinTypes, metadataDefinition, metadataName);
174                }
175                
176                if (metaDef == null)
177                {
178                    throw new IllegalArgumentException("Unable to get the metadata definition for metadata with name '" + metadataName + "' (" + StringUtils.join(content != null ? content.getTypes() : contentTypes, ',') + ").");
179                }
180                
181                if (_contentTypesHelper.canRead(content, metaDef))
182                {
183                    _saxMetadataDefinition(content, metadataDefRef, metaDef);
184                }
185            }
186            else
187            {
188                Fieldset fieldset = (Fieldset) subMetadataSetElement;
189                
190                AttributesImpl attrs = new AttributesImpl();
191                attrs.addCDATAAttribute("role", fieldset.getRole());
192                
193                XMLUtils.startElement(contentHandler, "fieldset", attrs);
194                fieldset.getLabel().toSAX(contentHandler, "label");
195                _saxMetadataSetElement(content, contentTypes, mixinTypes, metadataDefinition, fieldset);
196                XMLUtils.endElement(contentHandler, "fieldset");
197            }
198        }
199    }
200
201    /**
202     * Retrieves a sub metadata definition from a content type or
203     * a parent metadata definition. 
204     * @param contentTypes the content types
205     * @param mixinTypes the mixin types
206     * @param parentMetadataDefinition the parent metadata definition.
207     * @param metadataName the metadata name.
208     * @return the metadata definition found or <code>null</code> otherwise.
209     */
210    protected MetadataDefinition _getMetadataDefinition(String[] contentTypes, String[] mixinTypes, MetadataDefinition parentMetadataDefinition, String metadataName)
211    {
212        MetadataDefinition metadataDefinition = null;
213        
214        if (parentMetadataDefinition == null)
215        {
216            metadataDefinition = _contentTypesHelper.getMetadataDefinition(metadataName, contentTypes, mixinTypes);
217        }
218        else
219        {
220            metadataDefinition = parentMetadataDefinition.getMetadataDefinition(metadataName);
221        }
222        
223        return metadataDefinition;
224    }
225
226    /**
227     * Sax the metadata definition
228     * @param content The content
229     * @param metadataSetElement The metadata set element
230     * @param metaDef The metadata definition
231     * @throws SAXException if an error occurs while SAXing.
232     */
233    protected void _saxMetadataDefinition(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef) throws SAXException
234    {
235        XMLUtils.startElement(contentHandler, metaDef.getName());
236        
237        XMLUtils.startElement(contentHandler, "label");
238        I18nizableText label = metaDef.getLabel();
239        if (label != null)
240        {
241            label.toSAX(contentHandler);
242        }
243        XMLUtils.endElement(contentHandler, "label");
244        
245        XMLUtils.startElement(contentHandler, "description");
246        I18nizableText description = metaDef.getDescription();
247        if (description != null)
248        {
249            description.toSAX(contentHandler);
250        }
251        XMLUtils.endElement(contentHandler, "description");
252        
253        XMLUtils.createElement(contentHandler, "type", metaDef.getType().name());
254        String cTypeId = metaDef.getContentType();
255        if (cTypeId != null)
256        {
257            XMLUtils.createElement(contentHandler, "contentType", cTypeId);
258        }
259        
260        Validator validator = metaDef.getValidator();
261        ParameterHelper.toSAXValidator(contentHandler, validator);
262        
263        if (metaDef.getType() == MetadataType.RICH_TEXT)
264        {
265            XMLUtils.createElement(contentHandler, "editableSource", Boolean.toString(_rightManager.hasRight(_currentUserProvider.getUser(), "CORE_Rights_SourceEdit", content) == RightResult.RIGHT_ALLOW));
266        }
267        
268        String widget = metaDef.getWidget();
269        if (widget != null)
270        {
271            XMLUtils.createElement(contentHandler, "widget", widget);
272        }
273        
274        Map<String, I18nizableText> widgetParameters = metaDef.getWidgetParameters();
275        if (widgetParameters != null && !widgetParameters.isEmpty())
276        {
277            XMLUtils.startElement(contentHandler, "widget-params");
278            for (String paramName : widgetParameters.keySet())
279            {
280                XMLUtils.startElement(contentHandler, paramName);
281                widgetParameters.get(paramName).toSAX(contentHandler);
282                XMLUtils.endElement(contentHandler, paramName);
283            }
284            XMLUtils.endElement(contentHandler, "widget-params");
285        }
286        
287        
288        XMLUtils.createElement(contentHandler, "multiple", String.valueOf(metaDef.isMultiple()));
289
290        Object defaultValue = metaDef.getDefaultValue();
291        
292        if (defaultValue != null)
293        {
294            XMLUtils.createElement(contentHandler, "default-value", String.valueOf(metaDef.getDefaultValue()));
295        }
296
297        Enumerator enumerator = metaDef.getEnumerator();
298        
299        if (enumerator != null)
300        {
301            _saxEnumeration(enumerator);
302        }
303        
304        if (!_contentTypesHelper.canWrite(content, metaDef))
305        {
306            XMLUtils.createElement(contentHandler, "can-not-write", "true");
307        }
308        
309        if (metaDef.getType() == MetadataType.COMPOSITE)
310        {
311            _saxCompositeDefinition(content, metadataSetElement, metaDef);
312        }
313        
314        if (metaDef instanceof RichTextMetadataDefinition)
315        {
316            _saxAnnotableDefinition(content, metadataSetElement, (RichTextMetadataDefinition) metaDef);
317        }
318        
319        XMLUtils.endElement(contentHandler, metaDef.getName());
320    }
321
322    private void _saxEnumeration(Enumerator enumerator) throws SAXException
323    {
324        XMLUtils.startElement(contentHandler, "enumeration");
325
326        try
327        {
328            for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet())
329            {
330                String valueAsString = ParameterHelper.valueToString(entry.getKey());
331                I18nizableText entryLabel = entry.getValue();
332                AttributesImpl attrs = new AttributesImpl();
333                attrs.addCDATAAttribute("value", valueAsString);
334                XMLUtils.startElement(contentHandler, "option", attrs);
335                
336                if (entryLabel != null)
337                {
338                    entryLabel.toSAX(contentHandler);
339                }
340                else
341                {
342                    XMLUtils.data(contentHandler, valueAsString);
343                }
344                
345                XMLUtils.endElement(contentHandler, "option");
346            }
347        }
348        catch (Exception e)
349        {
350            throw new SAXException("Unable to enumerate entries with enumerator: " + enumerator, e);
351        }
352
353        XMLUtils.endElement(contentHandler, "enumeration");
354    }
355    
356    /**
357     * SAX the annotations of a definition
358     * @param content the content.
359     * @param metadataSetElement the metadata set element.
360     * @param metaDef the metadata definition.
361     * @throws SAXException if an error occurs while SAXing.
362     */
363    private void _saxAnnotableDefinition(Content content, AbstractMetadataSetElement metadataSetElement, RichTextMetadataDefinition metaDef) throws SAXException
364    {     
365        List<SemanticAnnotation> annotations = metaDef.getSemanticAnnotations();
366        if (annotations != null && annotations.size() > 0)
367        {
368            XMLUtils.startElement(contentHandler, "annotations");
369            for (SemanticAnnotation annotation : annotations)
370            {
371                AttributesImpl attrs = new AttributesImpl();
372                attrs.addCDATAAttribute("name", annotation.getId());
373                
374                XMLUtils.startElement(contentHandler, "annotation", attrs);
375                
376                I18nizableText label = annotation.getLabel();
377                if (label != null)
378                {
379                    label.toSAX(contentHandler, "label");
380                }
381                
382                I18nizableText description = annotation.getDescription();
383                if (description != null)
384                {
385                    description.toSAX(contentHandler, "description");
386                }         
387                
388                XMLUtils.endElement(contentHandler, "annotation");
389            }
390            XMLUtils.endElement(contentHandler, "annotations");
391        }
392    }
393
394    /**
395     * Sax a composite definition
396     * @param content the content.
397     * @param metadataSetElement the metadata set element.
398     * @param metaDef the metadata definition.
399     * @throws SAXException if an error occurs while SAXing.
400     */
401    private void _saxCompositeDefinition(Content content, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metaDef) throws SAXException
402    {
403        if (metaDef instanceof RepeaterDefinition)
404        {
405            if (!_contentTypesHelper.canRead(content, metaDef))
406            {
407                return;
408            }
409            
410            AttributesImpl repeaterAttrs = new AttributesImpl();
411            RepeaterDefinition repeaterDef = (RepeaterDefinition) metaDef;
412            I18nizableText addLabel = repeaterDef.getAddLabel();
413            I18nizableText delLabel = repeaterDef.getDeleteLabel();
414            String headerLabel = repeaterDef.getHeaderLabel();
415            int maxSize = repeaterDef.getMaxSize();
416            
417            repeaterAttrs.addCDATAAttribute("initial-size", String.valueOf(repeaterDef.getInitialSize()));
418            repeaterAttrs.addCDATAAttribute("min-size", String.valueOf(repeaterDef.getMinSize()));
419            
420            if (maxSize >= 0)
421            {
422                repeaterAttrs.addCDATAAttribute("max-size", String.valueOf(maxSize));
423            }
424
425            XMLUtils.startElement(contentHandler, "repeater", repeaterAttrs);
426            
427            if (addLabel != null)
428            {
429                addLabel.toSAX(contentHandler, "add-label");
430            }
431            
432            if (delLabel != null)
433            {
434                delLabel.toSAX(contentHandler, "del-label");
435            }
436            
437            if (StringUtils.isNotEmpty(headerLabel))
438            {
439                XMLUtils.createElement(contentHandler, "header-label", headerLabel);
440            }
441            
442            if (!_contentTypesHelper.canWrite(content, metaDef))
443            {
444                XMLUtils.createElement(contentHandler, "can-not-write", "true");
445            }
446        }
447
448        XMLUtils.startElement(contentHandler, "composition");
449        _saxMetadataSetElement(content, ArrayUtils.EMPTY_STRING_ARRAY, ArrayUtils.EMPTY_STRING_ARRAY, metaDef, metadataSetElement);
450        XMLUtils.endElement(contentHandler, "composition");
451        
452        if (metaDef instanceof RepeaterDefinition)
453        {
454            XMLUtils.endElement(contentHandler, "repeater");
455        }
456    }
457}