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