001/* 002 * Copyright 2019 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.data.type; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import java.util.Map; 023import java.util.Optional; 024import java.util.stream.Collectors; 025import java.util.stream.Stream; 026 027import javax.xml.transform.TransformerException; 028 029import org.apache.avalon.framework.configuration.Configurable; 030import org.apache.avalon.framework.configuration.Configuration; 031import org.apache.avalon.framework.configuration.ConfigurationException; 032import org.apache.avalon.framework.context.ContextException; 033import org.apache.avalon.framework.context.Contextualizable; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.cocoon.Constants; 037import org.apache.cocoon.environment.Context; 038import org.apache.cocoon.xml.AttributesImpl; 039import org.apache.cocoon.xml.XMLUtils; 040import org.apache.commons.lang3.StringUtils; 041import org.apache.commons.lang3.tuple.ImmutableTriple; 042import org.apache.commons.lang3.tuple.Triple; 043import org.w3c.dom.Element; 044import org.xml.sax.ContentHandler; 045import org.xml.sax.SAXException; 046 047import org.ametys.cms.data.Binary; 048import org.ametys.cms.data.ExplorerFile; 049import org.ametys.cms.data.File; 050import org.ametys.core.model.type.AbstractElementType; 051import org.ametys.core.model.type.ModelItemTypeHelper; 052import org.ametys.core.upload.Upload; 053import org.ametys.core.upload.UploadManager; 054import org.ametys.core.user.CurrentUserProvider; 055import org.ametys.plugins.repository.AmetysObjectResolver; 056import org.ametys.plugins.repository.UnknownAmetysObjectException; 057import org.ametys.plugins.repository.data.holder.values.UntouchedValue; 058import org.ametys.runtime.model.ViewItem; 059import org.ametys.runtime.model.compare.DataChangeType; 060import org.ametys.runtime.model.compare.DataChangeTypeDetail; 061import org.ametys.runtime.model.type.DataContext; 062 063/** 064 * Abstract class for file type of elements 065 */ 066public abstract class AbstractFileElementType extends AbstractElementType<File> implements Configurable, Contextualizable 067{ 068 /** Type of he file from explorer */ 069 protected static final String __EXPLORER_FILE_TYPE = "explorer"; 070 071 /** Ametys object resolver */ 072 protected AmetysObjectResolver _resolver; 073 074 /** Type of the binary */ 075 protected String _binaryType; 076 077 /** {@link UploadManager} */ 078 protected UploadManager _uploadManager; 079 080 /** {@link CurrentUserProvider} */ 081 protected CurrentUserProvider _userProvider; 082 083 /** Coocon's context */ 084 protected Context _context; 085 086 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 087 { 088 _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 089 } 090 091 @Override 092 public void service(ServiceManager manager) throws ServiceException 093 { 094 super.service(manager); 095 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 096 _uploadManager = (UploadManager) manager.lookup(UploadManager.ROLE); 097 _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 098 } 099 100 public void configure(Configuration configuration) throws ConfigurationException 101 { 102 Configuration typeNode = configuration.getChild("binary-type", false); 103 if (typeNode != null) 104 { 105 _binaryType = typeNode.getValue(null); 106 } 107 108 if (StringUtils.isBlank(_binaryType)) 109 { 110 throw new ConfigurationException("The configuration of binary type should contain a node 'binary-type' with the type of the binary ('metadata', 'explorer', ...)", configuration); 111 } 112 } 113 114 @Override 115 public File convertValue(Object value) 116 { 117 if (value instanceof String) 118 { 119 return new ExplorerFile(_resolver, (String) value); 120 } 121 122 return super.convertValue(value); 123 } 124 125 @Override 126 public String toString(File value) 127 { 128 if (value instanceof ExplorerFile) 129 { 130 return ((ExplorerFile) value).getResourceId(); 131 } 132 else 133 { 134 throw new UnsupportedOperationException("Unable to convert a binary to a string value"); 135 } 136 } 137 138 public Object fromJSONForClient(Object json) 139 { 140 if (json == null) 141 { 142 return json; 143 } 144 else if (json instanceof String) 145 { 146 return convertValue(json); 147 } 148 else if (json instanceof Map) 149 { 150 Map data = (Map) json; 151 if (data.isEmpty()) 152 { 153 return null; 154 } 155 156 String type = (String) ((Map) json).get("type"); 157 String id = (String) ((Map) json).get("id"); 158 if ("explorer".equals(type)) 159 { 160 return new ExplorerFile(_resolver, id); 161 } 162 else if ("attribute".equals(type)) 163 { 164 if (id.equals(AbstractBinaryElementType.UNTOUCHED)) 165 { 166 return new UntouchedValue(); 167 } 168 169 Upload upload = _uploadManager.getUpload(_userProvider.getUser(), id); 170 return ResourceElementTypeHelper.binaryFromUpload(upload); 171 } 172 173 throw new IllegalArgumentException("Try to convert the non file JSON object '" + json + "' into a file"); 174 } 175 else if (json instanceof List) 176 { 177 @SuppressWarnings("unchecked") 178 List<String> jsonList = (List<String>) json; 179 List<File> fileList = new ArrayList<>(); 180 for (String singleJSON : jsonList) 181 { 182 fileList.add(castValue(singleJSON)); 183 } 184 return fileList.toArray(new File[fileList.size()]); 185 } 186 else 187 { 188 throw new IllegalArgumentException("Try to convert the non file time JSON object '" + json + "' into a file"); 189 } 190 } 191 192 @Override 193 public Object valueToJSONForClient(Object value, DataContext context) 194 { 195 if (value == null) 196 { 197 return value; 198 } 199 else if (value instanceof String) 200 { 201 ExplorerFile file = new ExplorerFile(_resolver, (String) value); 202 return _singleExplorerFileToJSON(file); 203 } 204 else if (value instanceof String[]) 205 { 206 return Arrays.asList((String[]) value).stream() 207 .map(fileId -> new ExplorerFile(_resolver, fileId)) 208 .map(file -> _singleExplorerFileToJSON(file)) 209 .collect(Collectors.toList()); 210 } 211 else if (value instanceof ExplorerFile) 212 { 213 return _singleExplorerFileToJSON((ExplorerFile) value); 214 } 215 else if (value instanceof ExplorerFile[]) 216 { 217 return Arrays.asList((ExplorerFile[]) value).stream() 218 .map(file -> _singleExplorerFileToJSON(file)) 219 .collect(Collectors.toList()); 220 } 221 else if (value instanceof Binary) 222 { 223 return ResourceElementTypeHelper.singleBinaryToJSON((Binary) value, _binaryType, context); 224 } 225 else if (value instanceof Binary[]) 226 { 227 return Arrays.asList((Binary[]) value).stream() 228 .map(file -> ResourceElementTypeHelper.singleBinaryToJSON(file, _binaryType, context)) 229 .collect(Collectors.toList()); 230 } 231 else 232 { 233 throw new IllegalArgumentException("Try to convert the non file value '" + value + "' to JSON"); 234 } 235 } 236 237 @Override 238 public boolean isCompatible(Object value) 239 { 240 return super.isCompatible(value) || value instanceof UntouchedValue; 241 } 242 243 private Object _singleExplorerFileToJSON(ExplorerFile explorerFile) 244 { 245 String explorerFileId = explorerFile.getResourceId(); 246 try 247 { 248 Map<String, Object> singleFileToJSON = ResourceElementTypeHelper.singleFileToJSON(explorerFile, __EXPLORER_FILE_TYPE, explorerFileId, explorerFileId); 249 singleFileToJSON.put("id", explorerFileId); 250 251 return singleFileToJSON; 252 } 253 catch (UnknownAmetysObjectException e) 254 { 255 getLogger().warn("The resource of id '{}' does not exist", explorerFileId, e); 256 return null; 257 } 258 } 259 260 @Override 261 protected File _singleValueFromXML(Element element, Optional<Object> additionalData) throws TransformerException, IOException 262 { 263 String type = element.getAttribute("type"); 264 if (__EXPLORER_FILE_TYPE.equals(type)) 265 { 266 String resourceId = element.getAttribute("path"); 267 return new ExplorerFile(_resolver, resourceId); 268 } 269 else 270 { 271 Binary binary = new Binary(); 272 ResourceElementTypeHelper.resourceFromXML(binary, element, additionalData, _context); 273 return binary; 274 } 275 } 276 277 @Override 278 protected void _valueToSAX(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context, AttributesImpl attributes) throws SAXException, IOException 279 { 280 AttributesImpl localAttributes = new AttributesImpl(attributes); 281 282 if (value instanceof String) 283 { 284 ExplorerFile file = new ExplorerFile(_resolver, (String) value); 285 _singleExplorerFileToSAX(contentHandler, tagName, file, attributes); 286 } 287 else if (value instanceof String[]) 288 { 289 if (((String[]) value).length <= 0) 290 { 291 XMLUtils.createElement(contentHandler, tagName, localAttributes); 292 } 293 else 294 { 295 for (String fileId : (String[]) value) 296 { 297 ExplorerFile file = new ExplorerFile(_resolver, fileId); 298 _singleExplorerFileToSAX(contentHandler, tagName, file, attributes); 299 } 300 } 301 } 302 else if (value instanceof ExplorerFile) 303 { 304 ExplorerFile file = (ExplorerFile) value; 305 _singleExplorerFileToSAX(contentHandler, tagName, file, attributes); 306 } 307 else if (value instanceof ExplorerFile[]) 308 { 309 if (((ExplorerFile[]) value).length <= 0) 310 { 311 XMLUtils.createElement(contentHandler, tagName, localAttributes); 312 } 313 else 314 { 315 for (ExplorerFile file : (ExplorerFile[]) value) 316 { 317 _singleExplorerFileToSAX(contentHandler, tagName, file, attributes); 318 } 319 } 320 } 321 else if (value instanceof Binary) 322 { 323 ResourceElementTypeHelper.singleBinaryToSAX(contentHandler, tagName, (Binary) value, _binaryType, context, localAttributes); 324 } 325 else if (value instanceof Binary[]) 326 { 327 if (((Binary[]) value).length <= 0) 328 { 329 XMLUtils.createElement(contentHandler, tagName, localAttributes); 330 } 331 else 332 { 333 for (Binary binary : (Binary[]) value) 334 { 335 ResourceElementTypeHelper.singleBinaryToSAX(contentHandler, tagName, binary, _binaryType, context, localAttributes); 336 } 337 } 338 } 339 } 340 341 private void _singleExplorerFileToSAX(ContentHandler contentHandler, String tagName, ExplorerFile explorerFile, AttributesImpl attributes) throws SAXException 342 { 343 String explorerFileId = explorerFile.getResourceId(); 344 345 try 346 { 347 348 AttributesImpl localAttributes = new AttributesImpl(attributes); 349 localAttributes.addCDATAAttribute("id", explorerFileId); 350 351 ResourceElementTypeHelper.singleFileToSAX(contentHandler, tagName, explorerFile, __EXPLORER_FILE_TYPE, explorerFileId, explorerFileId, localAttributes); 352 } 353 catch (UnknownAmetysObjectException e) 354 { 355 getLogger().warn("The resource of id '{}' does not exist", explorerFileId, e); 356 } 357 } 358 359 @Override 360 protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareMultipleValues(File[] value1, File[] value2) throws IOException 361 { 362 throw new UnsupportedOperationException("Unable to compare multiple values of type '" + getId() + "'"); 363 } 364 365 @Override 366 protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareSingleValues(File value1, File value2) throws IOException 367 { 368 if (ModelItemTypeHelper.areSingleObjectsBothNotNullAndDifferents(value1, value2)) 369 { 370 if (_havValuesSameFileType(value1, value2)) 371 { 372 if (value1 instanceof Binary) 373 { 374 return ResourceElementTypeHelper.compareSingleBinaries((Binary) value1, (Binary) value2); 375 } 376 else if (value1 instanceof ExplorerFile) 377 { 378 return Stream.of(ModelItemTypeHelper.compareSingleObjects(((ExplorerFile) value1).getResourceId(), ((ExplorerFile) value2).getResourceId(), StringUtils.EMPTY)) 379 .filter(Optional::isPresent) 380 .map(Optional::get); 381 } 382 else 383 { 384 return super._compareSingleValues(value1, value2); 385 } 386 } 387 else 388 { 389 return Stream.of(new ImmutableTriple<>(DataChangeType.MODIFIED, DataChangeTypeDetail.TYPE, StringUtils.EMPTY)); 390 } 391 } 392 else 393 { 394 return Stream.of(ModelItemTypeHelper.compareSingleObjects(value1, value2, StringUtils.EMPTY)) 395 .filter(Optional::isPresent) 396 .map(Optional::get); 397 } 398 } 399 400 private boolean _havValuesSameFileType(File value1, File value2) 401 { 402 return (value1 instanceof ExplorerFile && value2 instanceof ExplorerFile) || (value1 instanceof Binary && value2 instanceof Binary); 403 } 404 405 public boolean isSimple() 406 { 407 return false; 408 } 409 410 @Override 411 protected boolean _useJSONForEdition() 412 { 413 return true; 414 } 415}