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