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}