001/*
002 *  Copyright 2020 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 */
016
017package org.ametys.plugins.workspaces.documents.onlyoffice;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URL;
022import java.nio.charset.StandardCharsets;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import javax.servlet.http.HttpServletRequest;
029
030import org.apache.avalon.framework.parameters.Parameters;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.acting.ServiceableAction;
034import org.apache.cocoon.environment.ObjectModelHelper;
035import org.apache.cocoon.environment.Redirector;
036import org.apache.cocoon.environment.Request;
037import org.apache.cocoon.environment.SourceResolver;
038import org.apache.cocoon.environment.http.HttpEnvironment;
039import org.apache.commons.io.IOUtils;
040
041import org.ametys.core.cocoon.JSonReader;
042import org.ametys.core.util.JSONUtils;
043import org.ametys.plugins.explorer.resources.Resource;
044import org.ametys.plugins.repository.AmetysObject;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.workspaces.documents.WorkspaceExplorerResourceDAO;
047
048/**
049 * Action to handle OnlyOffice comunications
050 */
051public class GetOnlyOfficeReponse extends ServiceableAction
052{
053    /** json util */
054    protected JSONUtils _jsonUtils;
055    
056    /** resolver */
057    protected AmetysObjectResolver _resolver;
058    
059    /** workspace explorer resource DAO */
060    protected WorkspaceExplorerResourceDAO _workspaceExplorerResourceDAO;
061    
062    @Override
063    public void service(ServiceManager smanager) throws ServiceException
064    {
065        super.service(smanager);
066        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
067        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
068        _workspaceExplorerResourceDAO = (WorkspaceExplorerResourceDAO) smanager.lookup(WorkspaceExplorerResourceDAO.ROLE);
069    }
070    
071    @Override
072    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
073    {
074        Request request = ObjectModelHelper.getRequest(objectModel);
075        HttpServletRequest httpRequest = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT);
076        Integer value = 0;
077        String resourceId = request.getParameter("id");
078        
079        try (InputStream is = httpRequest.getInputStream())
080        {
081            String string = IOUtils.toString(is, StandardCharsets.UTF_8);
082            Map<String, Object> jsonMap = _jsonUtils.convertJsonToMap(string);
083            value = callbackHandler(jsonMap, resourceId);
084        }
085        
086        Map<String, Object> result = new HashMap<>();
087        result.put("error", value);
088        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
089
090        return EMPTY_MAP;
091    }
092    
093    /**
094     * Analyze sample of JSON object from document editing service on OnlyOffice Server
095     * @param message Map &lt;String, Object&gt;
096     * @param resourceId id of the file
097     * @return value of error for the document editing service
098     */
099    protected Integer callbackHandler(Map<String, Object> message, String resourceId)
100    {
101        if (message.get("key") == null || message.get("status") == null)
102        {
103            getLogger().error("Json message invalid");
104            return 1;
105        }
106        
107        Integer status = -1;
108        Object objectStatus = message.get("status");
109        if (objectStatus instanceof Integer)
110        {
111            status = (Integer) objectStatus;
112        } 
113        else if (objectStatus instanceof String)
114        {
115            status = Integer.parseInt((String) objectStatus);
116        }
117        
118        switch (status)
119        {
120            case 0:
121                return documentNotFound(message);
122            case 1:
123                return userConnection(message);
124            case 2:
125                return 0; //saveDocument(message, resourceId);
126            case 3:
127                return 1; //TODO Check OnlyOffice doc about this action
128            case 4:
129                return documentClosedWithNoChanges(message);
130            case 6:
131                return forceSaveDocument(message, resourceId);
132            case 7:
133                return 1; //TODO Check OnlyOffice doc about this action
134            default:
135                getLogger().error("Status '" + status + "' invalid.");
136                return 1;
137        }
138    }
139    
140    /**
141     * take actions parameter and cast all informations inside
142     * @param message is a JSON object received from Document Editing Service
143     * @return Lists of actions or null
144     */
145    protected List<Map<String, Object>> actionFromUser(Map<String, Object> message)
146    {
147        if (!message.containsKey("actions"))
148        {
149            return null;
150        }
151        
152        List<Map<String, Object>> result = new ArrayList<>();
153        Object actionsObject = message.get("actions");
154        
155        if (actionsObject instanceof List)
156        {
157            List<Object> actionsList = (List<Object>) actionsObject;
158            for (int index = 0; actionsList.size() > index; index++)
159            {
160                Object actionObject = actionsList.get(index);
161                if (actionObject instanceof Map)
162                {
163                    Map<String, Object> action = (Map<String, Object>) actionObject;
164                    result.add(action);
165                }
166                else
167                {
168                    return null;
169                }
170            }
171        }
172        else
173        {
174            return null;
175        }
176        return result;
177    }
178    
179    /**
180     * no document with the key identifier could be found
181     * @param message is a JSON object received from Document Editing Service
182     * @return 0 (success)
183     */
184    protected Integer documentNotFound(Map<String, Object> message)
185    {
186        return 0;
187    }
188    
189    /**
190     * Check connection of user
191     * @param message is a JSON object received from Document Editing Service
192     * @return 0 (success) or 1 (error)
193     */
194    protected Integer userConnection(Map<String, Object> message)
195    {
196        List<Map<String, Object>> actions = actionFromUser(message);
197        
198        if (actions == null)
199        {
200            return 1;
201        }
202        
203        for (Map<String, Object> action : actions)
204        {
205            Object typeObject = action.get("type");
206            if (!(typeObject instanceof Integer))
207            {
208                return 1;
209            }
210            
211            Integer type = (Integer) typeObject;
212            
213            if (type == 1)
214            {
215                // Nothing
216            }
217            else if (type == 0)
218            {
219                // Nothing
220            }
221            else
222            {
223                return 1;
224            }
225        }
226        return 0;
227    }
228
229    /**
230     * Save the document
231     * @param downloadUri link of the new file to save
232     * @param resourceId id of the resource to update
233     * @return null or the map of the new file
234     */
235    protected Map<String, Object> processSave(String downloadUri, String resourceId)
236    {
237        URL url;
238        try
239        {
240            url = new URL(downloadUri);
241            InputStream fileStream = url.openStream();
242            AmetysObject ametysObject = _resolver.resolveById(resourceId);
243            String parentId = ametysObject.getParent().getId();
244            
245            if (ametysObject instanceof Resource)
246            {
247                Resource resource = (Resource) ametysObject;
248                String name = resource.getName();
249                
250                return _workspaceExplorerResourceDAO.addFile(fileStream, name, parentId, false, false, true);
251            }
252        }
253        catch (IOException e)
254        {
255            // Auto-generated catch block
256            e.printStackTrace();
257            return null;
258        }
259        catch (IllegalAccessException e)
260        {
261            // Auto-generated catch block
262            e.printStackTrace();
263            return null;
264        }
265        return null;
266    }
267    
268    /**
269     * the force saving request is performed.
270     * @param message is a JSON object received from Document Editing Service
271     * @param resourceId id document
272     * @return 0 (success) or 1 (error)
273     */
274    protected Integer forceSaveDocument(Map<String, Object> message, String resourceId)
275    {
276        String downloadUri = (String) message.get("url");
277        
278        Map<String, Object> addFile = processSave(downloadUri, resourceId);
279        if (addFile == null)
280        {
281            getLogger().error("Save failed of " + resourceId);
282            return 1;
283        }
284        
285        return 0;
286    }
287    
288    /**
289     * Save the document
290     * @param message is a JSON object received from Document Editing Service
291     * @param resourceId id document
292     * @return 0 (success) or 1 (error)
293     */
294    protected Integer saveDocument(Map<String, Object> message, String resourceId)
295    {
296        /* If "notmodified" is true, save the document is useless */
297        if (message.get("notmodified") != null && (boolean) message.get("notmodified"))
298        {
299            return documentClosedWithNoChanges(message);
300        }
301        
302        if (userConnection(message) == 1)
303        {
304            return 1;
305        }
306
307        return forceSaveDocument(message, resourceId);
308    }
309    
310    /**
311     * The document is closed for editing with no changes by the last user.
312     * Display who closed it.
313     * @param message is a JSON object received from Document Editing Service
314     * @return 0 (success) or 1 (error)
315     */
316    protected Integer documentClosedWithNoChanges(Map<String, Object> message)
317    {
318        if (message.get("actions") != null)
319        {
320            return userConnection(message);
321        }
322        
323        return 0;
324    }
325}