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