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