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}