001/* 002 * Copyright 2010 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.plugins.odfweb.repository; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.Objects; 025import java.util.Optional; 026import java.util.stream.Collectors; 027 028import org.apache.commons.lang3.StringUtils; 029 030import org.ametys.cms.FilterNameHelper; 031import org.ametys.odf.program.Program; 032import org.ametys.plugins.repository.AmetysObject; 033import org.ametys.plugins.repository.AmetysObjectIterable; 034import org.ametys.plugins.repository.AmetysRepositoryException; 035import org.ametys.plugins.repository.CollectionIterable; 036import org.ametys.plugins.repository.UnknownAmetysObjectException; 037import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 038import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder; 039import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 040import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData; 041import org.ametys.web.repository.page.Page; 042import org.ametys.web.repository.page.UnknownZoneException; 043import org.ametys.web.repository.page.Zone; 044import org.ametys.web.repository.site.Site; 045import org.ametys.web.repository.sitemap.Sitemap; 046 047/** 048 * Page representing a first-level page. 049 */ 050public class FirstLevelPage extends AbstractLevelPage 051{ 052 private FirstLevelPageFactory _factory; 053 054 private Page _root; 055 private String _name; 056 private String _title; 057 058 059 /** 060 * Constructor. 061 * @param factory the corresponding factory 062 * @param root the odf root page. 063 * @param name the page's name. 064 * @param title the page's title. 065 */ 066 public FirstLevelPage(FirstLevelPageFactory factory, Page root, String name, String title) 067 { 068 _factory = factory; 069 _root = root; 070 _name = name; 071 _title = title; 072 } 073 074 @Override 075 public int getDepth() throws AmetysRepositoryException 076 { 077 return _root.getDepth() + 1; 078 } 079 080 @Override 081 public String getTitle() throws AmetysRepositoryException 082 { 083 return _title; 084 } 085 086 @Override 087 public String getLongTitle() throws AmetysRepositoryException 088 { 089 return _title; 090 } 091 092 @Override 093 public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException 094 { 095 if (!"default".equals(name)) 096 { 097 throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual program pages."); 098 } 099 100 return new FirstLevelZone(this, _factory.getZoneDataTypeEP(), _factory.getServiceEP(), _factory.getZoneItemDataTypeEP()); 101 } 102 103 @Override 104 public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException 105 { 106 ArrayList<Zone> zones = new ArrayList<>(); 107 zones.add(new FirstLevelZone(this, _factory.getZoneDataTypeEP(), _factory.getServiceEP(), _factory.getZoneItemDataTypeEP())); 108 return new CollectionIterable<>(zones); 109 } 110 111 @Override 112 public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException 113 { 114 if (!_hasSecondLevelPages()) 115 { 116 List<ProgramPage> pages = _getChildrenProgramPageFromCache() 117 .orElseGet(() -> _factory.getODFPageHandler().getProgramsWithRestrictions(_root, _name, null, null, null)) 118 .stream() 119 .map(this::_toProgramPage) 120 .collect(Collectors.toList()); 121 122 return new CollectionIterable<>(pages); 123 124 } 125 else 126 { 127 Map<String, Collection<Program>> secondLevelCache = Optional.ofNullable(_factory.getODFPageCache().getProgramCache(_root, true).get(_name)).orElseGet(HashMap::new); 128 Map<String, String> secondLevelValues = _factory.getODFPageHandler().getLevel2Values(_root); 129 130 List<Page> children = secondLevelCache.keySet().stream() 131 .map(secondLevelCode -> _findSecondLevelValueEntry(secondLevelValues, secondLevelCode).orElse(null)) 132 .filter(Objects::nonNull) 133 .map(entry -> _toSecondLevelPage(entry)) 134 .collect(Collectors.toList()); 135 136 return new CollectionIterable<>(children); 137 } 138 } 139 140 private boolean _hasSecondLevelPages() 141 { 142 String level2Metadata = _factory.getODFPageHandler().getLevel2Metadata(_root); 143 return StringUtils.isNotBlank(level2Metadata); 144 } 145 146 private Optional<AmetysObjectIterable<Program>> _getChildrenProgramPageFromCache() 147 { 148 return _factory.getODFPageCache().getPrograms(_root, _name, null, false); 149 } 150 151 private Optional<Entry<String, String>> _findSecondLevelValueEntry(String levelCode) 152 { 153 Map<String, String> secondLevelValues = _factory.getODFPageHandler().getLevel2Values(_root); 154 return _findSecondLevelValueEntry(secondLevelValues, levelCode); 155 } 156 157 private Optional<Entry<String, String>> _findSecondLevelValueEntry(Map<String, String> secondLevelValues, String levelCode) 158 { 159 return secondLevelValues.entrySet().stream() 160 // entry = (code, title) 161 .filter(entry -> entry.getKey().equals(levelCode)) 162 .findFirst(); 163 } 164 165 private SecondLevelPage _toSecondLevelPage(Map.Entry<String, String> secondLevelValueEntry) 166 { 167 String childName = secondLevelValueEntry.getKey(); 168 String title = secondLevelValueEntry.getValue(); 169 170 return new SecondLevelPage(_factory.getSecondLevelPageFactory(), _root, _name, childName, title, this); 171 } 172 173 private ProgramPage _toProgramPage(Program program) 174 { 175 return new ProgramPage(_factory.getProgramPageFactory(), _root, program, null, null, this); 176 } 177 178 @Override 179 public String getPathInSitemap() throws AmetysRepositoryException 180 { 181 return _root.getPathInSitemap() + "/" + getName(); 182 } 183 184 @Override 185 public Site getSite() throws AmetysRepositoryException 186 { 187 return _root.getSite(); 188 } 189 190 @Override 191 public String getSiteName() throws AmetysRepositoryException 192 { 193 return _root.getSiteName(); 194 } 195 196 @Override 197 public Sitemap getSitemap() throws AmetysRepositoryException 198 { 199 return _root.getSitemap(); 200 } 201 202 @Override 203 public String getSitemapName() throws AmetysRepositoryException 204 { 205 return _root.getSitemapName(); 206 } 207 208 @SuppressWarnings("unchecked") 209 @Override 210 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 211 { 212 if (path.isEmpty()) 213 { 214 throw new AmetysRepositoryException("path must be non empty"); 215 } 216 217 if (!_hasSecondLevelPages()) 218 { 219 return (A) _factory.getODFPageCache().getChildProgramPage(_root, this, _name, null, path); 220 } 221 else 222 { 223 int i = path.indexOf('/'); 224 225 String name = i == -1 ? path : path.substring(0, path.indexOf('/')); 226 227 int index = name.lastIndexOf("-"); 228 String level2Code = index != -1 ? _factory.getODFPageHandler().decodeLevelValue(name.substring(index + 1)) : _factory.getODFPageHandler().decodeLevelValue(name); 229 230 Page level2Page = _findSecondLevelValueEntry(level2Code) 231 .map(this::_toSecondLevelPage) 232 .orElseThrow(() -> 233 new UnknownAmetysObjectException("There's no virtual child page at path " + path + " for page " + _name)); 234 235 if (i == -1) 236 { 237 return (A) level2Page; 238 } 239 else 240 { 241 return (A) level2Page.getChild(path.substring(i + 1)); 242 } 243 } 244 245 } 246 247 @SuppressWarnings("unchecked") 248 @Override 249 public AmetysObjectIterable<? extends Page> getChildren() throws AmetysRepositoryException 250 { 251 return getChildrenPages(); 252 } 253 254 @Override 255 public boolean hasChild(String childName) throws AmetysRepositoryException 256 { 257 // FIXME should use page cache, because can return true when page does 258 // not exist. Maybe done like this for performance reasons? 259 if (!_hasSecondLevelPages()) 260 { 261 return _factory.getODFPageCache().getProgramFromPageName(_root, _name, null, childName).isPresent(); 262 } 263 else 264 { 265 Map<String, String> secondLevelValues = _factory.getODFPageHandler().getLevel2Values(_root); 266 return secondLevelValues.containsKey(childName); 267 } 268 } 269 270 @Override 271 public String getId() throws AmetysRepositoryException 272 { 273 // E.g: odfLevel1://XA?rootId=... 274 return "odfLevel1://" + _factory.getODFPageHandler().encodeLevelValue(_name) + "?rootId=" + _root.getId(); 275 } 276 277 @Override 278 public String getName() throws AmetysRepositoryException 279 { 280 // E.g: licence-lmd-XA 281 return FilterNameHelper.filterName(_title) + "-" + _factory.getODFPageHandler().encodeLevelValue(_name); 282 } 283 284 @SuppressWarnings("unchecked") 285 @Override 286 public Page getParent() throws AmetysRepositoryException 287 { 288 return _root; 289 } 290 291 @Override 292 public String getParentPath() throws AmetysRepositoryException 293 { 294 return _root.getPath(); 295 } 296 297 public ModelLessDataHolder getDataHolder() 298 { 299 RepositoryData repositoryData = new MemoryRepositoryData(getName()); 300 return new DefaultModelLessDataHolder(_factory.getPageDataTypeEP(), repositoryData); 301 } 302}