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) manager.lookup(AmetysObjectResolver.ROLE);
090        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
091        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
092        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
093        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
094        _externalizableMetaProvider = (ExternalizableMetadataProviderExtensionPoint) manager.lookup(ExternalizableMetadataProviderExtensionPoint.ROLE);
095        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
096        _lockContentManager = (LockContentManager) manager.lookup(LockContentManager.ROLE);
097        _fileMetadataManager = (FileMetadataManager) manager.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            Map<String, Object> metadataJsonObject = new HashMap<>();
187            for (String metadataPath : metadataPaths)
188            {
189                Map<String, Object> contentMetadata2Json = _contentMetadata2Json(metadataSetName, content, metadataPath);
190                metadataJsonObject.put(metadataPath, contentMetadata2Json);
191            }
192            jsonObject.put("data", metadataJsonObject);
193        }
194
195        request.setAttribute(JSonReader.OBJECT_TO_READ, jsonObject);
196        return EMPTY_MAP;
197    }
198
199    /**
200     * Check if the content is locked
201     * @param content The content
202     * @return UserIdentity of the locker, of null if not locked
203     */
204    protected UserIdentity isContentLocked(Content content)
205    {
206        if (!(content instanceof JCRAmetysObject))
207        {
208            return null;
209        }
210
211        try
212        {
213            Node node = ((JCRAmetysObject) content).getNode();
214            LockManager lockManager = node.getSession().getWorkspace().getLockManager();
215
216            if (lockManager.isLocked(node.getPath()))
217            {
218                Node lockHolder = lockManager.getLock(node.getPath()).getNode();
219
220                AmetysObject ao = _resolver.resolve(lockHolder, false);
221                if (ao instanceof LockableAmetysObject)
222                {
223                    LockableAmetysObject lockableAO = (LockableAmetysObject) ao;
224                    if (!LockHelper.isLockOwner(lockableAO, _currentUserProvider.getUser()))
225                    {
226                        return lockableAO.getLockOwner();
227                    }
228                }
229            }
230        }
231        catch (RepositoryException e)
232        {
233            getLogger().error(String.format("Repository exception during lock checking for ametys object '%s'", content.getId()), e);
234            throw new AmetysRepositoryException(e);
235        }
236        return null;
237    }
238    
239    private Map<String, Object> _contentMetadata2Json(String metadataSetName, Content content, String metadataPath) throws ProcessingException
240    {
241        MetadataDefinition metadataDef = _contentTypesHelper.getMetadataDefinition(metadataPath, content);
242        
243        if (metadataDef == null)
244        {
245            throw new ProcessingException(String.format("Unknown metadata path '%s' in metadata set '%s' of type '%s' for content type(s) '%s'",
246                    metadataPath, metadataSetName, "edition", StringUtils.join(content.getTypes(), ',')));
247        }
248        
249        Set<String> externalAndLocalMetadata = _externalizableMetaProvider.getExternalAndLocalMetadata(content);
250        Map<String, Object> jsonObject = metadataDefinition2JsonObject(content, null, metadataDef, metadataPath, externalAndLocalMetadata);
251        
252        if (_contentTypesHelper.canRead(content, metadataDef))
253        {
254            List<Object> values = _contentHelper.getMetadataValues(content, metadataPath, new Locale(content.getLanguage()), false, false);
255            
256            if (values.size() > 0)
257            {
258                if (MetadataType.RICH_TEXT.equals(metadataDef.getType()))
259                {
260                    ModifiableRichText value = (ModifiableRichText) values.get(0);
261                    
262                    StringBuilder result = new StringBuilder(2048);
263                    try
264                    {
265                        RichTextTransformer richTextTransformer = metadataDef.getRichTextTransformer();
266                        richTextTransformer.transformForEditing(value, result);
267                        jsonObject.put("value", result.toString());
268                    }
269                    catch (IOException e)
270                    {
271                        throw new AmetysRepositoryException("Unable to transform a rich text into a string", e);
272                    }
273                }
274                else if (MetadataType.FILE.equals(metadataDef.getType()))
275                {
276                    Object value = values.get(0);
277                    
278                    if (value instanceof BinaryMetadata)
279                    {
280                        jsonObject.put("value", _fileMetadataManager.readBinaryMetadata(content, (BinaryMetadata) value, metadataPath));
281                    }
282                    else if (value instanceof Resource)
283                    {
284                        jsonObject.put("value", _fileMetadataManager.readResource((Resource) value));
285                    }
286                    else if (value instanceof String)
287                    {
288                        try
289                        {
290                            Resource resource = _resolver.resolveById((String) value);
291                            jsonObject.put("value", _fileMetadataManager.readResource(resource));
292                        }
293                        catch (AmetysRepositoryException e)
294                        {
295                            getLogger().info("Cannot sax existing value for metadata '" + metadataPath + "' of content '" + content.getId() + "'. The user will see an empty value", e);
296                        }
297                    }
298                }
299                else
300                {
301                    List<String> valuesAsStr = values.stream()
302                        .map(v -> ParameterHelper.valueToString(v))
303                        .collect(Collectors.toList());
304                    
305                    jsonObject.put("value", metadataDef.isMultiple() ? valuesAsStr.toArray(new String[valuesAsStr.size()]) : valuesAsStr.get(0));
306                }
307            }
308        }
309        
310        return jsonObject;
311    }
312}