001/*
002 *  Copyright 2021 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.transformation.htmledition;
018
019import java.io.InputStream;
020import java.util.Map;
021import java.util.Optional;
022
023import javax.jcr.Node;
024import javax.jcr.RepositoryException;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.components.ContextHelper;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.xml.AttributesImpl;
031import org.apache.commons.lang3.StringUtils;
032import org.xml.sax.Attributes;
033import org.xml.sax.SAXException;
034
035import org.ametys.cms.URIPrefixHandler;
036import org.ametys.cms.data.Resource;
037import org.ametys.cms.data.RichText;
038import org.ametys.cms.repository.Content;
039import org.ametys.cms.transformation.ImageResolverHelper;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
042import org.ametys.runtime.model.type.DataContext;
043
044/**
045 * Handler to enhance the rich text attachments.
046 * Add the content id and the data path to the local attachment's attribute
047 */
048public class DocbookLocalMediaObjectEditionHandler extends AbstractHTMLEditionHandler
049{
050    private static final String __ATTACHMENT_IMAGE_TAG_NAME = "imagedata";
051    private static final String __ATTACHMENT_VIDEO_TAG_NAME = "videodata";
052    private static final String __ATTACHMENT_AUDIO_TAG_NAME = "audiodata";
053    private static final String __ATTACHMENT_TYPE_ATTRIBUTE_NAME = "type";
054    private static final String __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE = "local";
055
056    /** Ametys object resolver */
057    protected AmetysObjectResolver _resolver;
058    
059    /** the context of the data being edited */
060    protected DataContext _dataContext;
061    
062    private URIPrefixHandler _prefixHandler;
063    
064    @Override
065    public void service(ServiceManager manager) throws ServiceException
066    {
067        super.service(manager);
068        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
069        _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE);
070    }
071    
072    @Override
073    public void startDocument() throws SAXException
074    {
075        Map objectModel = ContextHelper.getObjectModel(_context);
076        Map parentContextParameters = (Map) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
077        _dataContext = (DataContext) parentContextParameters.get("dataContext");
078        
079        super.startDocument();
080    }
081
082    @Override
083    public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException
084    {
085        // A new attachment starts being saxed
086        boolean isAttachment = _isAttachment(loc);
087        String type = attrs.getValue(__ATTACHMENT_TYPE_ATTRIBUTE_NAME);
088        if (isAttachment && __ATTACHMENT_TYPE_ATTRIBUTE_LOCAL_VALUE.equals(type))
089        {
090            Attributes newAttrs = _processAttachment(attrs);
091            super.startElement(uri, loc, raw, newAttrs);
092        }
093        else
094        {
095            super.startElement(uri, loc, raw, attrs);
096        }
097    }
098
099    private boolean _isAttachment(String loc)
100    {
101        return __ATTACHMENT_IMAGE_TAG_NAME.equals(loc) || __ATTACHMENT_VIDEO_TAG_NAME.equals(loc) || __ATTACHMENT_AUDIO_TAG_NAME.equals(loc);
102    }
103
104    private Attributes _processAttachment(Attributes attrs) throws SAXException
105    {
106        String contentId = _dataContext.getObjectId()
107                .orElseThrow(() -> new SAXException("The object id is required in the data context in order to set the fileref"));
108        Content content = _resolver.resolveById(contentId);
109
110        String dataPath = _dataContext.getDataPath();
111        RichText richText = content.getValue(dataPath);
112        
113        String filename = attrs.getValue("fileref");
114        Resource attachment = richText.getAttachment(filename);
115
116        AttributesImpl newAttrs = new AttributesImpl(attrs);
117        
118        _getAttachmentUUID(attachment)
119                  .map(this::getRichTextByUUIDURL)
120                  .ifPresentOrElse(url -> newAttrs.addCDATAAttribute("resourceByUUID", url),
121                      () -> newAttrs.addCDATAAttribute("base64", _resolveAttachmentAsBase64(attrs, attachment)));
122
123        return newAttrs;
124    }
125    
126    /**
127     * Retrieves the URL to retrieve a rich text attachment by its UUID 
128     * @param uuid the UUID of the rich text's attachment
129     * @return the URL to retrieve a rich text attachment by its UUID
130     */
131    protected String getRichTextByUUIDURL(String uuid)
132    {
133        StringBuilder resultPath = new StringBuilder();
134        
135        resultPath.append(_prefixHandler.computeUriPrefix(false, false))
136            .append("/plugins/cms/richText-file-by-uuid/")
137            .append(uuid);
138        
139        return resultPath.toString();
140    }
141    
142    private Optional<String> _getAttachmentUUID(Resource attachment)
143    {
144        return attachment.getRepositoryData()
145                .filter(JCRRepositoryData.class::isInstance)
146                .map(JCRRepositoryData.class::cast)
147                .map(JCRRepositoryData::getNode)
148                .map(this::_getNodeIdentifier);
149    }
150    
151    private String _getNodeIdentifier(Node node)
152    {
153        try
154        {
155            return node.getIdentifier();
156        }
157        catch (RepositoryException e)
158        {
159            // Return null to use the fallback method (base64) if we are not able to get the node identifier 
160            return null;
161        }
162    }
163    
164    private String _resolveAttachmentAsBase64(Attributes attrs, Resource attachment)
165    {
166        try (InputStream dataIs = attachment.getInputStream())
167        {
168            String heightAsString = StringUtils.substringBefore(attrs.getValue("depth"), "px");
169            int height = heightAsString != null ? Integer.valueOf(heightAsString) : -1;
170            
171            String widthAsString = StringUtils.substringBefore(attrs.getValue("width"), "px");
172            int width = widthAsString != null ? Integer.valueOf(widthAsString) : -1;
173            
174            return ImageResolverHelper.resolveImageAsBase64(dataIs, attachment.getMimeType(), height, width, 0, 0);
175        }
176        catch (Exception e)
177        {
178            throw new IllegalStateException(e);
179        }
180    }
181}