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.plugins.frontedition; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import javax.jcr.Node; 029import javax.jcr.RepositoryException; 030import javax.jcr.lock.LockManager; 031 032import org.apache.avalon.framework.parameters.Parameters; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.cocoon.ProcessingException; 036import org.apache.cocoon.environment.ObjectModelHelper; 037import org.apache.cocoon.environment.Redirector; 038import org.apache.cocoon.environment.Request; 039import org.apache.cocoon.environment.SourceResolver; 040import org.apache.commons.lang.StringUtils; 041 042import org.ametys.cms.content.GetMetadataSetDefAction; 043import org.ametys.cms.content.external.ExternalizableMetadataProviderExtensionPoint; 044import org.ametys.cms.contenttype.ContentTypesHelper; 045import org.ametys.cms.contenttype.MetadataDefinition; 046import org.ametys.cms.contenttype.MetadataType; 047import org.ametys.cms.lock.LockContentManager; 048import org.ametys.cms.repository.Content; 049import org.ametys.cms.transformation.RichTextTransformer; 050import org.ametys.core.cocoon.JSonReader; 051import org.ametys.core.right.RightManager; 052import org.ametys.core.user.CurrentUserProvider; 053import org.ametys.core.user.UserIdentity; 054import org.ametys.plugins.core.user.UserHelper; 055import org.ametys.plugins.explorer.resources.Resource; 056import org.ametys.plugins.repository.AmetysObject; 057import org.ametys.plugins.repository.AmetysObjectResolver; 058import org.ametys.plugins.repository.AmetysRepositoryException; 059import org.ametys.plugins.repository.jcr.JCRAmetysObject; 060import org.ametys.plugins.repository.lock.LockHelper; 061import org.ametys.plugins.repository.lock.LockableAmetysObject; 062import org.ametys.plugins.repository.metadata.BinaryMetadata; 063import org.ametys.plugins.repository.metadata.ModifiableRichText; 064import org.ametys.plugins.repository.version.VersionableAmetysObject; 065import org.ametys.runtime.parameter.ParameterHelper; 066import org.ametys.web.renderingcontext.RenderingContext; 067import org.ametys.web.renderingcontext.RenderingContextHandler; 068 069/** 070 * Check if the content can be edited, and return the value 071 */ 072public class GetServerValuesAction extends GetMetadataSetDefAction 073{ 074 /** The ametys object resolver */ 075 protected AmetysObjectResolver _resolver; 076 /** The rendering context handler */ 077 protected RenderingContextHandler _renderingContextHandler; 078 /** User helper */ 079 protected UserHelper _userHelper; 080 /** Lock Content Manager */ 081 protected LockContentManager _lockContentManager; 082 /** Our metatadata manager for file */ 083 protected FileMetadataManager _fileMetadataManager; 084 085 @Override 086 public void service(ServiceManager serviceManager) throws ServiceException 087 { 088 super.service(serviceManager); 089 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 090 _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE); 091 _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 092 _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); 093 _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 094 _externalizableMetaProvider = (ExternalizableMetadataProviderExtensionPoint) serviceManager.lookup(ExternalizableMetadataProviderExtensionPoint.ROLE); 095 _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE); 096 _lockContentManager = (LockContentManager) serviceManager.lookup(LockContentManager.ROLE); 097 _fileMetadataManager = (FileMetadataManager) serviceManager.lookup(FileMetadataManager.ROLE); 098 } 099 100 @Override 101 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 102 { 103 Request request = ObjectModelHelper.getRequest(objectModel); 104 105 String contentId = parameters.getParameter("contentId"); 106 String metadataPathsAsString = parameters.getParameter("metadataPaths"); 107 String workflowIdsAsString = parameters.getParameter("workflowIds", null); 108 109 Map<String, Object> jsonObject = new HashMap<>(); 110 boolean success = true; 111 112 if (metadataPathsAsString == null) 113 { 114 success = false; 115 jsonObject.put("error", "no metadata"); 116 request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); 117 return EMPTY_MAP; 118 } 119 List<String> metadataPaths = Arrays.asList(metadataPathsAsString.split(";")); 120 121 boolean validateContent = parameters.getParameterAsBoolean("validateContent", false); 122 String metadataSetName = parameters.getParameter("metadataSetName", "main"); 123 124 boolean isEditionMode = "true".equals(request.getParameter("_edition")); 125 126 validateContent &= !isEditionMode; //no validation if in edition mode 127 128 Content content = _resolver.resolveById(contentId); 129 // lock validation 130 UserIdentity locker = isContentLocked(content); 131 if (locker != null) 132 { 133 success = false; 134 String userFullName = _userHelper.getUserFullName(locker); 135 jsonObject.put("error", "locked"); 136 Map<String, String> userIdentyJson = new HashMap<>(); 137 userIdentyJson.put("fullName", userFullName); 138 jsonObject.put("locker", userIdentyJson); 139 } 140 else if (validateContent) 141 { 142 // draft/live validation 143 RenderingContext context = _renderingContextHandler.getRenderingContext(); 144 if (context == RenderingContext.FRONT && content instanceof VersionableAmetysObject) 145 { 146 String[] labels = ((VersionableAmetysObject) content).getLabels(); 147 if (!Arrays.asList(labels).contains("Live")) 148 { 149 success = false; 150 jsonObject.put("error", "draft"); 151 } 152 } 153 } 154 155 // workflow validation 156 if (success) 157 { 158 if (workflowIdsAsString == null) 159 { 160 success = false; 161 jsonObject.put("error", "no workflow Ids"); 162 request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); 163 return EMPTY_MAP; 164 } 165 List<String> workflowIdsAsStrings = Arrays.asList(workflowIdsAsString.split(";")); 166 List<Integer> workflowIds = new ArrayList<>(); 167 for (String workflowIdAsString : workflowIdsAsStrings) 168 { 169 workflowIds.add(Integer.parseInt(workflowIdAsString)); 170 } 171 boolean workflowRightsOk = AmetysFrontEditionHelper.hasWorkflowRight(workflowIds, contentId, false); 172 if (!workflowRightsOk) 173 { 174 success = false; 175 jsonObject.put("error", "workflow-rights"); 176 request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); 177 return EMPTY_MAP; 178 } 179 } 180 181 if (success) 182 { 183 List<String> contentIds = new ArrayList<>(1); 184 contentIds.add(contentId); 185 _lockContentManager.unlockOrLock(contentIds, "lock"); 186 List<String> languages = getLanguages(request); 187 188 Map<String, Object> metadataJsonObject = new HashMap<>(); 189 for (String metadataPath : metadataPaths) 190 { 191 Map<String, Object> contentMetadata2Json = _contentMetadata2Json(metadataSetName, content, metadataPath, languages); 192 metadataJsonObject.put(metadataPath, contentMetadata2Json); 193 } 194 jsonObject.put("data", metadataJsonObject); 195 } 196 197 request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject); 198 return EMPTY_MAP; 199 } 200 201 /** 202 * Check if the content is locked 203 * @param content The content 204 * @return UserIdentity of the locker, of null if not locked 205 */ 206 protected UserIdentity isContentLocked(Content content) 207 { 208 if (!(content instanceof JCRAmetysObject)) 209 { 210 return null; 211 } 212 213 try 214 { 215 Node node = ((JCRAmetysObject) content).getNode(); 216 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 217 218 if (lockManager.isLocked(node.getPath())) 219 { 220 Node lockHolder = lockManager.getLock(node.getPath()).getNode(); 221 222 AmetysObject ao = _resolver.resolve(lockHolder, false); 223 if (ao instanceof LockableAmetysObject) 224 { 225 LockableAmetysObject lockableAO = (LockableAmetysObject) ao; 226 if (!LockHelper.isLockOwner(lockableAO, _currentUserProvider.getUser())) 227 { 228 return lockableAO.getLockOwner(); 229 } 230 } 231 } 232 } 233 catch (RepositoryException e) 234 { 235 getLogger().error(String.format("Repository exception during lock checking for ametys object '%s'", content.getId()), e); 236 throw new AmetysRepositoryException(e); 237 } 238 return null; 239 } 240 241 private Map<String, Object> _contentMetadata2Json(String metadataSetName, Content content, String metadataPath, List<String> languages) throws ProcessingException 242 { 243 MetadataDefinition metadataDef = _contentTypesHelper.getMetadataDefinition(metadataPath, content); 244 245 if (metadataDef == null) 246 { 247 throw new ProcessingException(String.format("Unknown metadata path '%s' in metadata set '%s' of type '%s' for content type(s) '%s'", 248 metadataPath, metadataSetName, "edition", StringUtils.join(content.getTypes(), ','))); 249 } 250 251 Set<String> externalAndLocalMetadata = _externalizableMetaProvider.getExternalAndLocalMetadata(content); 252 Map<String, Object> jsonObject = metadataDefinition2JsonObject(content, null, metadataDef, metadataPath, externalAndLocalMetadata, languages); 253 254 if (_contentTypesHelper.canRead(content, metadataDef)) 255 { 256 List<Object> values = _contentHelper.getMetadataValues(content, metadataPath, new Locale(content.getLanguage()), false, false); 257 258 if (values.size() > 0) 259 { 260 if (MetadataType.RICH_TEXT.equals(metadataDef.getType())) 261 { 262 ModifiableRichText value = (ModifiableRichText) values.get(0); 263 264 StringBuilder result = new StringBuilder(2048); 265 try 266 { 267 RichTextTransformer richTextTransformer = metadataDef.getRichTextTransformer(); 268 richTextTransformer.transformForEditing(value, result); 269 jsonObject.put("value", result.toString()); 270 } 271 catch (IOException e) 272 { 273 throw new AmetysRepositoryException("Unable to transform a rich text into a string", e); 274 } 275 } 276 else if (MetadataType.FILE.equals(metadataDef.getType())) 277 { 278 Object value = values.get(0); 279 280 if (value instanceof BinaryMetadata) 281 { 282 jsonObject.put("value", _fileMetadataManager.readBinaryMetadata(content, (BinaryMetadata) value, metadataPath)); 283 } 284 else if (value instanceof Resource) 285 { 286 jsonObject.put("value", _fileMetadataManager.readResource((Resource) value)); 287 } 288 else if (value instanceof String) 289 { 290 try 291 { 292 Resource resource = _resolver.resolveById((String) value); 293 jsonObject.put("value", _fileMetadataManager.readResource(resource)); 294 } 295 catch (AmetysRepositoryException e) 296 { 297 getLogger().info("Cannot sax existing value for metadata '" + metadataPath + "' of content '" + content.getId() + "'. The user will see an empty value", e); 298 } 299 } 300 } 301 else 302 { 303 List<String> valuesAsStr = values.stream() 304 .map(v -> ParameterHelper.valueToString(v)) 305 .collect(Collectors.toList()); 306 307 jsonObject.put("value", metadataDef.isMultiple() ? valuesAsStr.toArray(new String[valuesAsStr.size()]) : valuesAsStr.get(0)); 308 } 309 } 310 } 311 312 return jsonObject; 313 } 314}