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