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 <String, Object> 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}