001/*
002 *  Copyright 2020 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.net.MalformedURLException;
020import java.util.Map;
021import java.util.Optional;
022import java.util.stream.Stream;
023
024import org.apache.avalon.framework.context.Context;
025import org.apache.avalon.framework.context.ContextException;
026import org.apache.avalon.framework.context.Contextualizable;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.avalon.framework.thread.ThreadSafe;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.commons.lang3.StringUtils;
034import org.apache.excalibur.source.Source;
035import org.apache.excalibur.source.SourceFactory;
036
037import org.ametys.cms.data.Binary;
038import org.ametys.cms.data.BinarySource;
039import org.ametys.cms.repository.Content;
040import org.ametys.cms.transformation.AttributeURIResolver;
041import org.ametys.core.util.FilenameUtils;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.data.ametysobject.ModelAwareDataAwareAmetysObject;
044import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus;
045import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper;
046import org.ametys.plugins.repository.data.holder.values.ValueContext;
047import org.ametys.runtime.plugin.component.AbstractLogEnabled;
048
049/**
050 * {@link SourceFactory} for content attributes.
051 */
052public class AttributeSourceFactory extends AbstractLogEnabled implements SourceFactory, ThreadSafe, Serviceable, Contextualizable
053{
054    /** source scheme */
055    public static final String ATTRIBUTE_SCHEME = "attribute";
056    
057    /** The context */
058    protected Context _context;
059    
060    /** The resolver */
061    protected AmetysObjectResolver _resolver;
062    
063    @Override
064    public void contextualize(Context context) throws ContextException
065    {
066        _context = context;
067    }
068    
069    public void service(ServiceManager manager) throws ServiceException
070    {
071        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
072    }
073    
074    public Source getSource(String location, Map parameters) throws IOException, MalformedURLException
075    {
076        String uri = _getURI(location);
077        
078        // uri are like <contentPath>@<attributePath>;<filename>
079        int i = uri.indexOf('@');
080        int j = uri.indexOf(';', i);
081        String path = uri.substring(0, i);
082        String attribute = uri.substring(i + 1, j);
083        String filename = uri.substring(j + 1);
084        
085        ModelAwareDataAwareAmetysObject ametysObject = _getModelAwareDataAwareAmetysObject();
086        
087        try
088        {
089            if (ametysObject == null) 
090            {
091                ametysObject = _resolver.resolveByPath(FilenameUtils.decode(path));
092            }
093            
094            Binary binary = _getBinary(ametysObject, attribute, filename);
095    
096            return new BinarySource(binary, FilenameUtils.decode(filename), location, ATTRIBUTE_SCHEME);
097        }
098        catch (Exception e)
099        {
100            getLogger().error("Unable to resolve binary attribute for uri " + location, e);
101            
102            // returns an unexisting Source
103            return new BinarySource(null, FilenameUtils.decode(filename), location, ATTRIBUTE_SCHEME);
104        }
105    }
106
107    /**
108     * Get binary
109     * @param object the ametys object
110     * @param attribute the attribut
111     * @param filename the filename
112     * @return the binary
113     */
114    protected Binary _getBinary(ModelAwareDataAwareAmetysObject object, String attribute, String filename)
115    {
116        ValueContext context = _getValueContext();
117        if (object.isMultiple(attribute))
118        {
119            // if multiple binary, take the first file with name equals to the filename
120            Binary[] binaries = DataHolderHelper.getValue(object, attribute, context);
121            return Optional.ofNullable(binaries)
122                    .map(Stream::of)
123                    .orElseGet(Stream::empty)
124                    .filter(b -> b.getFilename().equals(filename))
125                    .findFirst()
126                    .orElse(null);
127        }
128        
129        return DataHolderHelper.getValue(object, attribute, context);
130    }
131    
132    private ValueContext _getValueContext()
133    {
134        ValueContext context = ValueContext.newInstance();
135        
136        Request request = ContextHelper.getRequest(_context);
137        String parameter = request.getParameter(AttributeURIResolver.EXTERNALIZABLE_DATA_STATUS_PARAM_NAME);
138        if (StringUtils.isNotBlank(parameter))
139        {
140            context = context.withStatus(ExternalizableDataStatus.valueOf(parameter));
141        }
142        
143        return context;
144    }
145    
146    /**
147     * Get URI
148     * @param location the location
149     * @return the uri
150     */
151    protected String _getURI(String location)
152    {
153        return location.substring((ATTRIBUTE_SCHEME + "://").length());
154    }
155    
156    /**
157     * Get model aware data aware ametys object
158     * @return model aware data aware ametys object
159     */
160    protected ModelAwareDataAwareAmetysObject _getModelAwareDataAwareAmetysObject()
161    {
162        Request request = ContextHelper.getRequest(_context);
163        return (Content) request.getAttribute(Content.class.getName());
164    }
165    
166    public void release(Source source)
167    {
168        // nothing to do
169    }
170}