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