1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you
5  * may not use this file except in compliance with the License. You may
6  * obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13  * implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16 
17 package com.android.vts.job;
18 
19 import com.android.vts.entity.ApiCoverageExcludedEntity;
20 import com.android.vts.entity.DashboardEntity;
21 import com.google.api.client.auth.oauth2.Credential;
22 import com.google.api.client.extensions.appengine.datastore.AppEngineDataStoreFactory;
23 import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
24 import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
25 import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
26 import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
27 import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
28 import com.google.api.client.http.javanet.NetHttpTransport;
29 import com.google.api.client.json.JsonFactory;
30 import com.google.api.client.json.jackson2.JacksonFactory;
31 import com.google.api.services.sheets.v4.Sheets;
32 import com.google.api.services.sheets.v4.model.ValueRange;
33 
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.security.GeneralSecurityException;
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 import javax.servlet.ServletConfig;
42 import javax.servlet.ServletException;
43 import javax.servlet.http.HttpServletRequest;
44 import javax.servlet.http.HttpServletResponse;
45 import java.io.IOException;
46 import java.util.logging.Level;
47 import java.util.logging.Logger;
48 
49 /** Job to sync excluded API data in google spreadsheet with datastore's entity. */
50 public class VtsSpreadSheetSyncServlet extends BaseJobServlet {
51 
52     protected static final Logger logger =
53             Logger.getLogger(VtsSpreadSheetSyncServlet.class.getName());
54 
55     private static final String APPLICATION_NAME = "VTS Dashboard";
56     private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
57 
58     private String CREDENTIALS_KEY_FILE = "";
59 
60     /** GoogleClientSecrets for GoogleAuthorizationCodeFlow Builder */
61     private GoogleClientSecrets clientSecrets;
62 
63     /** This is the ID of google spreadsheet. */
64     private String SPREAD_SHEET_ID = "";
65 
66     /** This is the range to read of google spreadsheet. */
67     private String SPREAD_SHEET_RANGE = "";
68 
69     @Override
init(ServletConfig servletConfig)70     public void init(ServletConfig servletConfig) throws ServletException {
71         super.init(servletConfig);
72 
73         try {
74             CREDENTIALS_KEY_FILE = systemConfigProp.getProperty("api.coverage.keyFile");
75             SPREAD_SHEET_ID = systemConfigProp.getProperty("api.coverage.spreadSheetId");
76             SPREAD_SHEET_RANGE = systemConfigProp.getProperty("api.coverage.spreadSheetRange");
77 
78             InputStream keyFileInputStream =
79                     this.getClass()
80                             .getClassLoader()
81                             .getResourceAsStream("keys/" + CREDENTIALS_KEY_FILE);
82             InputStreamReader keyFileStreamReader = new InputStreamReader(keyFileInputStream);
83 
84             this.clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, keyFileStreamReader);
85         } catch (IOException ioe) {
86             logger.log(Level.SEVERE, ioe.getMessage());
87         } catch (Exception exception) {
88             logger.log(Level.SEVERE, exception.getMessage());
89         }
90     }
91 
92     /**
93      * Creates an authorized Credential object.
94      *
95      * @param HTTP_TRANSPORT The network HTTP Transport.
96      * @param appEngineDataStoreFactory The credential will be persisted using the Google App Engine
97      *     Data Store API.
98      * @param SCOPES Scopes are strings that enable access to particular resources, such as user
99      *     data.
100      * @return An authorized Credential object.
101      * @throws IOException If the credentials.json file cannot be found.
102      */
getCredentials( final NetHttpTransport HTTP_TRANSPORT, final AppEngineDataStoreFactory appEngineDataStoreFactory, final List<String> SCOPES)103     private Credential getCredentials(
104             final NetHttpTransport HTTP_TRANSPORT,
105             final AppEngineDataStoreFactory appEngineDataStoreFactory,
106             final List<String> SCOPES)
107             throws IOException {
108 
109         // Build flow and trigger user authorization request.
110         GoogleAuthorizationCodeFlow flow =
111                 new GoogleAuthorizationCodeFlow.Builder(
112                                 HTTP_TRANSPORT, JSON_FACTORY, this.clientSecrets, SCOPES)
113                         .setDataStoreFactory(appEngineDataStoreFactory)
114                         .setAccessType("offline")
115                         .build();
116         LocalServerReceiver localServerReceiver = new LocalServerReceiver();
117         return new AuthorizationCodeInstalledApp(flow, localServerReceiver).authorize("user");
118     }
119 
120     @Override
doGet(HttpServletRequest request, HttpServletResponse response)121     public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
122 
123         try {
124             // Build a new authorized API client service.
125             final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
126             final AppEngineDataStoreFactory appEngineDataStoreFactory =
127                     (AppEngineDataStoreFactory)
128                             request.getServletContext().getAttribute("dataStoreFactory");
129             final List<String> googleApiScopes =
130                     (List<String>) request.getServletContext().getAttribute("googleApiScopes");
131 
132             Sheets service =
133                     new Sheets.Builder(
134                                     HTTP_TRANSPORT,
135                                     JSON_FACTORY,
136                                     getCredentials(
137                                             HTTP_TRANSPORT,
138                                             appEngineDataStoreFactory,
139                                             googleApiScopes))
140                             .setApplicationName(APPLICATION_NAME)
141                             .build();
142 
143             ValueRange valueRange =
144                     service.spreadsheets()
145                             .values()
146                             .get(SPREAD_SHEET_ID, SPREAD_SHEET_RANGE)
147                             .execute();
148 
149             List<ApiCoverageExcludedEntity> apiCoverageExcludedEntities = new ArrayList<>();
150             List<List<Object>> values = valueRange.getValues();
151             if (values == null || values.isEmpty()) {
152                 logger.log(Level.WARNING, "No data found in google spreadsheet.");
153             } else {
154                 for (List row : values) {
155                     ApiCoverageExcludedEntity apiCoverageExcludedEntity =
156                             new ApiCoverageExcludedEntity(
157                                     row.get(0).toString(),
158                                     row.get(1).toString(),
159                                     row.get(2).toString(),
160                                     row.get(3).toString(),
161                                     row.get(4).toString());
162                     apiCoverageExcludedEntities.add(apiCoverageExcludedEntity);
163                 }
164             }
165 
166             DashboardEntity.saveAll(apiCoverageExcludedEntities, MAX_ENTITY_SIZE_PER_TRANSACTION);
167 
168         } catch (GeneralSecurityException gse) {
169             logger.log(Level.SEVERE, gse.getMessage());
170         }
171     }
172 }
173