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.impl; 017 018import java.util.Collection; 019import java.util.Optional; 020 021import org.apache.cocoon.xml.AttributesImpl; 022import org.apache.cocoon.xml.XMLUtils; 023import org.apache.commons.lang3.math.NumberUtils; 024import org.xml.sax.Attributes; 025import org.xml.sax.ContentHandler; 026import org.xml.sax.SAXException; 027 028import org.ametys.cms.data.holder.impl.IndexableDataHolderHelper; 029import org.ametys.core.model.type.AbstractModelItemType; 030import org.ametys.core.model.type.ModelItemTypeHelper; 031import org.ametys.plugins.repository.RepositoryConstants; 032import org.ametys.plugins.repository.data.UnknownDataException; 033import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater; 034import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry; 035import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 036import org.ametys.plugins.repository.data.type.RepositoryModelItemGroupType; 037import org.ametys.plugins.repository.model.RepeaterDefinition; 038import org.ametys.runtime.model.ModelItemContainer; 039import org.ametys.runtime.model.ModelViewItemGroup; 040import org.ametys.runtime.model.ViewItem; 041import org.ametys.runtime.model.ViewItemAccessor; 042import org.ametys.runtime.model.type.DataContext; 043 044/** 045 * Class for repeater type 046 */ 047public class RepeaterRepositoryModelItemType extends AbstractModelItemType implements RepositoryModelItemGroupType 048{ 049 public void valueToSAXForEdition(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context) throws SAXException 050 { 051 _valueToSAX(contentHandler, tagName, value, viewItem, context, true); 052 } 053 054 public void valueToSAX(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context) throws SAXException 055 { 056 _valueToSAX(contentHandler, tagName, value, viewItem, context, false); 057 } 058 059 private void _valueToSAX(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context, boolean isEdition) throws SAXException 060 { 061 AttributesImpl attributes = ModelItemTypeHelper.getXMLAttributesFromDataContext(context); 062 063 if (value == null) 064 { 065 XMLUtils.createElement(contentHandler, tagName, attributes); 066 } 067 else if (value instanceof ModelAwareRepeater repeater) 068 { 069 attributes.addCDATAAttribute("entryCount", String.valueOf(repeater.getSize())); 070 071 ViewItemAccessor viewItemAccessor = viewItem.filter(ViewItemAccessor.class::isInstance) 072 .map(ViewItemAccessor.class::cast) 073 .orElse(ModelViewItemGroup.of(repeater.getModel())); 074 075 XMLUtils.startElement(contentHandler, tagName, attributes); 076 for (ModelAwareRepeaterEntry entry : repeater.getEntries()) 077 { 078 _repeaterEntryToSAX(contentHandler, entry, viewItemAccessor, context, isEdition); 079 } 080 XMLUtils.endElement(contentHandler, tagName); 081 } 082 else if (value instanceof ModelAwareRepeaterEntry entry) 083 { 084 ViewItemAccessor viewItemAccessor = viewItem.filter(ViewItemAccessor.class::isInstance) 085 .map(ViewItemAccessor.class::cast) 086 .orElse(ModelViewItemGroup.of(_getRepeaterEntryDefinition(entry))); 087 088 _repeaterEntryToSAX(contentHandler, entry, viewItemAccessor, context, isEdition); 089 } 090 else 091 { 092 throw new IllegalArgumentException("Try to sax the non repeater value '" + value + "' in tag name '" + tagName + "'"); 093 } 094 } 095 096 private RepeaterDefinition _getRepeaterEntryDefinition(ModelAwareRepeaterEntry entry) 097 { 098 Collection<? extends ModelItemContainer> modelItemContainers = entry.getModel(); 099 assert modelItemContainers.size() == 1; 100 101 ModelItemContainer modelItemContainer = modelItemContainers.iterator().next(); 102 assert modelItemContainer instanceof RepeaterDefinition; 103 104 return (RepeaterDefinition) modelItemContainer; 105 } 106 107 private void _repeaterEntryToSAX(ContentHandler contentHandler, ModelAwareRepeaterEntry entry, ViewItemAccessor viewItemAccessor, DataContext context, boolean isEdition) throws SAXException 108 { 109 DataContext newContext = context.cloneContext() 110 .addSuffixToLastSegment("[" + entry.getPosition() + "]"); 111 112 // Don't sax model path on entry, to avoid the same value as the repeater level 113 XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry, newContext)); 114 IndexableDataHolderHelper.dataToSAX(entry, contentHandler, viewItemAccessor, newContext, isEdition); 115 XMLUtils.endElement(contentHandler, "entry"); 116 } 117 118 private Attributes _getEntryAttributes(ModelAwareRepeaterEntry entry, DataContext context) 119 { 120 AttributesImpl entryAttrs = ModelItemTypeHelper.getXMLAttributesFromDataContext(context, false); 121 String entryName = Integer.toString(entry.getPosition()); 122 entryAttrs.addCDATAAttribute("name", entryName); 123 return entryAttrs; 124 } 125 126 public Object valueToJSONForClient(Object value, Optional<ViewItem> viewItem, DataContext context) 127 { 128 return _valueToJSON(value, viewItem, context, false); 129 } 130 131 public Object valueToJSONForEdition(Object value, Optional<ViewItem> viewItem, DataContext context) 132 { 133 return _valueToJSON(value, viewItem, context, true); 134 } 135 136 private Object _valueToJSON(Object value, Optional<ViewItem> viewItem, DataContext context, boolean isEdition) 137 { 138 if (value == null) 139 { 140 return null; 141 } 142 else if (value instanceof ModelAwareRepeater repeater) 143 { 144 return viewItem.filter(ViewItemAccessor.class::isInstance) 145 .map(ViewItemAccessor.class::cast) 146 .map(viewItemAccessor -> repeater.dataToJSON(viewItemAccessor, context)) 147 .orElseGet(() -> repeater.dataToJSON(context)); 148 } 149 else if (value instanceof ModelAwareRepeaterEntry entry) 150 { 151 DataContext entryContext = context.cloneContext().addSuffixToLastSegment("[" + entry.getPosition() + "]"); 152 Optional<ViewItemAccessor> optViewItemAccessor = viewItem.filter(ViewItemAccessor.class::isInstance) 153 .map(ViewItemAccessor.class::cast); 154 155 return optViewItemAccessor.isEmpty() ? entry.dataToJSON(entryContext) 156 : isEdition ? entry.dataToJSONForEdition(optViewItemAccessor.get(), entryContext) 157 : entry.dataToJSON(optViewItemAccessor.get(), entryContext); 158 } 159 else 160 { 161 throw new IllegalArgumentException("Try to convert the non repeater value '" + value + "'"); 162 } 163 } 164 165 public boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException 166 { 167 if (RepositoryModelItemGroupType.super.isCompatible(parentData, name)) 168 { 169 return true; 170 } 171 // TODO NEWATTRIBUTEAPI: This part of the test is here just to be compatible with the old compositMetadata type. When all repeaters are migrated, remove this part of the test 172 else if (RepositoryConstants.COMPOSITE_METADTA_NODETYPE.equals(parentData.getType(name))) 173 { 174 RepositoryData possibleRepeater = parentData.getRepositoryData(name); 175 for (String dataName : possibleRepeater.getDataNames()) 176 { 177 if (!NumberUtils.isParsable(dataName)) 178 { 179 // If there is at least one data that is not a number (entry position), this data can not be a repeater 180 return false; 181 } 182 } 183 184 // All data are numbers and could be entry positions, the data could be a repeater 185 return true; 186 } 187 else 188 { 189 return false; 190 } 191 } 192 193 public String getRepositoryDataType() 194 { 195 return RepositoryConstants.REPEATER_NODETYPE; 196 } 197}