001/* 002 * Copyright 2023 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.runtime.plugins.admin.statistics; 017 018import java.io.IOException; 019import java.nio.charset.StandardCharsets; 020import java.util.Map; 021import java.util.Set; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.http.client.config.RequestConfig; 026import org.apache.http.client.entity.UrlEncodedFormEntity; 027import org.apache.http.client.methods.CloseableHttpResponse; 028import org.apache.http.client.methods.HttpPost; 029import org.apache.http.impl.client.CloseableHttpClient; 030import org.apache.http.impl.client.HttpClientBuilder; 031import org.apache.http.message.BasicNameValuePair; 032import org.apache.http.util.EntityUtils; 033import org.quartz.JobExecutionContext; 034 035import org.ametys.core.util.JSONUtils; 036import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable; 037import org.ametys.runtime.config.Config; 038import org.ametys.runtime.servlet.RuntimeServlet; 039 040/** 041 * Compute and optionally sent the anonymous statistics to the central ametys.org server 042 */ 043public class StatisticsSchedulable extends AbstractStaticSchedulable 044{ 045 private static final String CENTRAL_SERVER_URL = "https://statistics.ametys.org/_update-version/statistics/1.0.0/upload.json"; 046 private static final String CENTRAL_SERVER_HEADER = "X-Ametys-Statistics"; 047 048 private JSONUtils _jsonUtils; 049 private StatisticsProviderExtensionPoint _statisticsExtensionPoint; 050 051 @Override 052 public void service(ServiceManager manager) throws ServiceException 053 { 054 super.service(manager); 055 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 056 _statisticsExtensionPoint = (StatisticsProviderExtensionPoint) manager.lookup(StatisticsProviderExtensionPoint.ROLE); 057 } 058 059 @Override 060 public void execute(JobExecutionContext context) throws Exception 061 { 062 getLogger().info("Preparing statistics"); 063 Map<String, Object> jsonStatistics = _statisticsExtensionPoint.computeStatistics(); 064 065 if (Config.getInstance().getValue("runtime.statistics.send-at-night", false, false)) 066 { 067 getLogger().info("Sending remote statistics"); 068 _sendReport(jsonStatistics); 069 } 070 } 071 072 private void _sendReport(Map<String, Object> report) 073 { 074 RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000).setSocketTimeout(30000).build(); 075 076 try (CloseableHttpClient httpclient = HttpClientBuilder.create().useSystemProperties().setDefaultRequestConfig(requestConfig).build()) 077 { 078 // Prepare a request object 079 HttpPost request = new HttpPost(CENTRAL_SERVER_URL); 080 request.addHeader(CENTRAL_SERVER_HEADER, RuntimeServlet.getInstanceId()); 081 request.setEntity(new UrlEncodedFormEntity(Set.of(new BasicNameValuePair("value", _jsonUtils.convertObjectToJson(report))), StandardCharsets.UTF_8)); 082 083 // Execute the request 084 try (CloseableHttpResponse response = httpclient.execute(request)) 085 { 086 if (response.getStatusLine().getStatusCode() != 200) 087 { 088 throw new IllegalStateException("Could not join the central ametys.org server at " + CENTRAL_SERVER_URL + ". Error code " + response.getStatusLine().getStatusCode()); 089 } 090 else if (!response.containsHeader(CENTRAL_SERVER_HEADER)) 091 { 092 throw new IllegalStateException("Could not join the central ametys.org server at " + CENTRAL_SERVER_URL + ". Response code is 200, but there is not " + CENTRAL_SERVER_HEADER + " header"); 093 } 094 095 try 096 { 097 String responseAsString = EntityUtils.toString(response.getEntity(), "UTF-8"); 098 Map<String, Object> convertJsonToMap = _jsonUtils.convertJsonToMap(responseAsString); 099 100 if (!(convertJsonToMap.get("success") instanceof Boolean b && b == Boolean.TRUE)) 101 { 102 throw new IllegalStateException("Joined the central ametys.org server at " + CENTRAL_SERVER_URL + ". But the operation failed."); 103 } 104 } 105 catch (IllegalArgumentException e) 106 { 107 throw new IllegalStateException("Joined the central ametys.org server at " + CENTRAL_SERVER_URL + ". But cannot parse the response.", e); 108 } 109 } 110 } 111 catch (IOException e) 112 { 113 throw new IllegalStateException("Could not join the central ametys.org server at " + CENTRAL_SERVER_URL, e); 114 } 115 } 116}