001/* 002 * Copyright 2017 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.content.referencetable; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.parameters.Parameters; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.acting.ServiceableAction; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Redirector; 031import org.apache.cocoon.environment.Request; 032import org.apache.cocoon.environment.SourceResolver; 033import org.apache.commons.lang.StringUtils; 034import org.apache.excalibur.source.Source; 035import org.apache.tika.io.IOUtils; 036 037import org.ametys.cms.content.ContentHelper; 038import org.ametys.cms.contenttype.ContentType; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.contenttype.ContentTypesHelper; 041import org.ametys.cms.repository.Content; 042import org.ametys.core.cocoon.JSonReader; 043import org.ametys.plugins.repository.AmetysObjectIterable; 044import org.ametys.plugins.repository.AmetysObjectResolver; 045import org.ametys.runtime.i18n.I18nizableText; 046 047/** 048 * Action for getting information about the hierarchy of a reference table 049 */ 050public class GetHierarchicalReferenceTablesAction extends ServiceableAction 051{ 052 /** The extension point for content types */ 053 protected ContentTypeExtensionPoint _contentTypeEP; 054 /** The resolver for Ametys Objects */ 055 protected AmetysObjectResolver _ametysResolver; 056 /** The helper component for hierarchical reference tables */ 057 protected HierarchicalReferenceTablesHelper _hierarchicalReferenceTablesHelper; 058 /** Content types helper */ 059 protected ContentTypesHelper _cTypeHelper; 060 /** The content helper */ 061 protected ContentHelper _contentHelper; 062 /** The source resolver */ 063 protected org.apache.excalibur.source.SourceResolver _srcResolver; 064 065 @Override 066 public void service(ServiceManager smanager) throws ServiceException 067 { 068 _srcResolver = (org.apache.excalibur.source.SourceResolver) smanager.lookup(org.apache.excalibur.source.SourceResolver.ROLE); 069 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 070 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 071 _ametysResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 072 _hierarchicalReferenceTablesHelper = (HierarchicalReferenceTablesHelper) smanager.lookup(HierarchicalReferenceTablesHelper.ROLE); 073 _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 074 } 075 076 @Override 077 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 078 { 079 Map<String, Object> result = new HashMap<>(); 080 Request request = ObjectModelHelper.getRequest(objectModel); 081 String contextPath = request.getContextPath(); 082 083 @SuppressWarnings("unchecked") 084 Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 085 086 String contentId = jsParameters != null ? (String) jsParameters.get("contentId") : request.getParameter("contentId"); 087 String leafContentTypeId = jsParameters != null ? (String) jsParameters.get("leafContentType") : request.getParameter("leafContentType"); 088 ContentType leafContentType = _contentTypeEP.getExtension(leafContentTypeId); 089 090 if (leafContentType == null) 091 { 092 getLogger().warn(String.format("Unknown content type id '%s'", leafContentTypeId)); 093 return EMPTY_MAP; 094 } 095 096 boolean checkSelection = jsParameters != null ? jsParameters.get("checkSelection") != null && (Boolean) jsParameters.get("checkSelection") : false; 097 ContentType topLevelContentType = _hierarchicalReferenceTablesHelper.getTopLevelType(leafContentType); 098 List<Map<String, Object>> children = new ArrayList<>(); 099 100 try (AmetysObjectIterable<Content> contents = _getChildren(contentId, topLevelContentType, leafContentType)) 101 { 102 for (Content child : contents) 103 { 104 children.add(_contentToJson(child, false, topLevelContentType, leafContentType, checkSelection, contextPath)); 105 } 106 } 107 108 if ("root".equals(contentId) && _hierarchicalReferenceTablesHelper.supportCandidates(leafContentType)) 109 { 110 // Add root for candidates 111 children.add(_rootCandidateToJson(leafContentType, checkSelection)); 112 } 113 114 result.put("children", children); 115 116 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 117 return EMPTY_MAP; 118 } 119 120 private AmetysObjectIterable<Content> _getChildren(String parentId, ContentType topLevelContentType, ContentType leafContentType) 121 { 122 if ("root".equals(parentId)) 123 { 124 return _hierarchicalReferenceTablesHelper.getRootChildren(topLevelContentType, true); 125 } 126 else if ("rootCandidate".equals(parentId)) 127 { 128 return _hierarchicalReferenceTablesHelper.getCandidates(leafContentType.getId()); 129 } 130 else 131 { 132 return _hierarchicalReferenceTablesHelper.getDirectChildren((Content) _ametysResolver.resolveById(parentId)); 133 } 134 } 135 136 private Map<String, Object> _contentToJson(Content content, boolean full, ContentType topContentType, ContentType leafContentType, boolean checkSelection, String contextPath) 137 { 138 Map<String, Object> map = new HashMap<>(); 139 140 map.put("contentId", content.getId()); 141 map.put("contenttypesIds", content.getTypes()); 142 map.put("name", content.getName()); 143 map.put("title", _contentHelper.getTitle(content)); 144 map.put("lang", content.getLanguage()); 145 146 map.put("iconGlyph", _cTypeHelper.getIconGlyph(content)); 147 map.put("iconDecorator", _cTypeHelper.getIconDecorator(content)); 148 map.put("iconSmall", _cTypeHelper.getSmallIcon(content)); 149 map.put("iconMedium", _cTypeHelper.getMediumIcon(content)); 150 map.put("iconLarge", _cTypeHelper.getLargeIcon(content)); 151 map.put("tooltip", _getTooltip(content)); 152 153 if (checkSelection) 154 { 155 // Check if the node if selectable 156 map.put("disabled", !_cTypeHelper.isInstanceOf(content, leafContentType.getId())); 157 } 158 159 map.put("leaf", _isLeaf(content, topContentType.getId(), leafContentType.getId())); 160 map.put("isSimple", leafContentType.isSimple()); 161 map.put("isCandidate", _hierarchicalReferenceTablesHelper.isCandidate(content)); 162 map.put("areCandidatesAllowed", _hierarchicalReferenceTablesHelper.supportCandidates(leafContentType)); 163 map.put("mixin", content.getMixinTypes()); 164 map.put("type", "content"); 165 166 AmetysObjectIterable<Content> children = _hierarchicalReferenceTablesHelper.getDirectChildren(content); 167 if (children.getSize() == 0) 168 { 169 // No child 170 map.put("children", new ArrayList()); 171 } 172 else if (full) 173 { 174 List<Map<String, Object>> childrenInfos = new ArrayList<>(); 175 for (Content child : children) 176 { 177 childrenInfos.add(_contentToJson(child, false, topContentType, leafContentType, checkSelection, contextPath)); 178 } 179 map.put("children", childrenInfos); 180 } 181 182 return map; 183 } 184 185 private String _getTooltip(Content content) 186 { 187 if (_cTypeHelper.getMetadataSetForView("tooltip", content.getTypes(), content.getMixinTypes()) != null) 188 { 189 String uri = "cocoon://_content.html?contentId=" + content.getId() + "&metadataSetName=tooltip"; 190 Source source = null; 191 try 192 { 193 source = _srcResolver.resolveURI(uri); 194 try (InputStream is = source.getInputStream()) 195 { 196 String tooltip = IOUtils.toString(is, "utf-8"); 197 198 // Lets's keep only inside <body> 199 tooltip = StringUtils.substringAfter(tooltip, "<body"); 200 tooltip = StringUtils.substringAfter(tooltip, ">"); 201 tooltip = StringUtils.substringBefore(tooltip, "</body>"); 202 203 return tooltip; 204 } 205 } 206 catch (IOException e) 207 { 208 getLogger().error("Failed to get tooltip view for content " + content.getId(), e); 209 } 210 finally 211 { 212 _srcResolver.release(source); 213 } 214 } 215 216 return StringUtils.EMPTY; 217 } 218 219 private Map<String, Object> _rootCandidateToJson(ContentType leafContentType, boolean checkSelection) 220 { 221 ContentType candidateType = _contentTypeEP.getExtension(HierarchicalReferenceTablesHelper.CANDIDATE_CONTENT_TYPE); 222 Map<String, Object> infos = _cTypeHelper.getContentTypeProperties(candidateType); 223 224 infos.put("contentId", "rootCandidate"); 225 infos.put("contenttypesIds", new String[] {leafContentType.getId()}); 226 infos.put("name", "rootCandidate"); 227 infos.put("title", new I18nizableText("plugin.cms", "PLUGINS_CMS_HIERARCHICAL_REFERENCE_TABLES_CANDIDATES_LABEL")); 228 infos.put("leaf", false); 229 infos.put("type", "root-candidate"); 230 231 if (checkSelection) 232 { 233 // Check if the node if selectable 234 infos.put("disabled", true); 235 } 236 237 AmetysObjectIterable<Content> candidates = _hierarchicalReferenceTablesHelper.getCandidates(leafContentType.getId()); 238 if (candidates.getSize() == 0) 239 { 240 // No child 241 infos.put("children", new ArrayList()); 242 } 243 244 return infos; 245 } 246 247 private boolean _isLeaf(Content content, String topContentTypeId, String leafContentTypeId) 248 { 249 if (_cTypeHelper.isInstanceOf(content, leafContentTypeId)) 250 { 251 // a child can be a leaf, or it can be a hierarchy with only one content type, referencing itself as parent 252 // hierarchy with only one content type, referencing itself as parent 253 return !topContentTypeId.equals(leafContentTypeId); 254 } 255 else 256 { 257 return false; 258 } 259 } 260}