package fr.acxio.tools.agia.google;
/*
* Copyright 2014 Acxio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore;
import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.http.FileContent;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Children;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.About;
import com.google.api.services.drive.model.ChildList;
import com.google.api.services.drive.model.ChildReference;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.ParentReference;
import com.google.api.services.drive.model.Property;
import com.google.api.services.drive.model.PropertyList;
public class GoogleDriveServiceImpl implements GoogleDriveService {
private static final String FOLDER_MIMETYPE = "application/vnd.google-apps.folder";
private static final String PATH_SPLIT_REGEX = "/";
private static final Pattern PATH_EXTRACT_PATTERN = Pattern.compile("^(?:(.*)/)?([^/]*)$");
private static final Logger LOGGER = LoggerFactory.getLogger(GoogleDriveServiceImpl.class);
private HttpTransport httpTransport;
private Drive drive;
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
private static final String APPLICATION_NAME = "Acxio-AGIA.2";
private Resource clientSecretsResource;
private Resource userCredentialStoreResource;
private VerificationCodeReceiver verificationCodeReceiver;
private String user;
private String rootFolderId = null;
public Drive getDrive() {
return drive;
}
public void setClientSecretsResource(Resource sClientSecretsResource) {
clientSecretsResource = sClientSecretsResource;
}
public void setUserCredentialStoreResource(Resource sUserCredentialStoreResource) {
userCredentialStoreResource = sUserCredentialStoreResource;
}
public void setVerificationCodeReceiver(VerificationCodeReceiver sVerificationCodeReceiver) {
verificationCodeReceiver = sVerificationCodeReceiver;
}
public void setUser(String sUser) {
user = sUser;
}
protected String getRootFolderID() throws IOException {
if ((drive != null) && (rootFolderId == null)) {
About aAbout = drive.about().get().execute();
rootFolderId = aAbout.get("rootFolderId").toString();
}
return rootFolderId;
}
protected Credential authorize() throws GoogleException, GeneralSecurityException, IOException {
// load client secrets
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, clientSecretsResource.getInputStream());
if (clientSecrets.getDetails().getClientId().startsWith("Enter") || clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
LOGGER.error("Enter Client ID and Secret from https://code.google.com/apis/console/?api=drive " + "into "
+ clientSecretsResource.getFile().getAbsolutePath());
throw new GoogleException("ClientSecrets not configured");
}
// set up file credential store
FileCredentialStore credentialStore = new FileCredentialStore(userCredentialStoreResource.getFile(), JSON_FACTORY);
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(DriveScopes.DRIVE_FILE)).setCredentialStore(credentialStore).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, verificationCodeReceiver).authorize(user);
}
@Override
public void connect() throws GoogleException {
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
Credential credential = authorize();
drive = new Drive.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
} catch (IOException e) {
throw new GoogleException(e);
} catch (GeneralSecurityException e) {
throw new GoogleException(e);
}
}
@Override
public File createFile(String sName, java.io.File sFile, String sMimeType, Map<String, String> sProperties) throws IOException {
File aResult = null;
Matcher aPathMatcher = PATH_EXTRACT_PATTERN.matcher(sName);
if (aPathMatcher.matches()) {
String aPath = aPathMatcher.group(1);
String aName = aPathMatcher.group(2);
if ((aName == null) || aName.isEmpty()) {
throw new IOException("Name is mandatory.");
}
String aParentID = getRootFolderID();
if ((aPath != null) && !aPath.isEmpty()) {
aParentID = createPath(aPath).getId();
}
aResult = createFile(aParentID, aName, sFile, sMimeType, sProperties);
} else {
throw new IOException("Name does not conform to paths syntax.");
}
return aResult;
}
@Override
public File createFile(String sParentID, String sName, java.io.File sFile, String sMimeType, Map<String, String> sProperties) throws IOException {
File aResult = null;
if (drive != null) {
File fileMetadata = new File();
fileMetadata.setTitle(sName);
ParentReference aParentReference = new ParentReference();
aParentReference.setId(sParentID);
fileMetadata.setParents(Arrays.asList(aParentReference));
FileContent mediaContent = new FileContent(sMimeType, sFile);
Drive.Files.Insert insert = drive.files().insert(fileMetadata, mediaContent);
MediaHttpUploader uploader = insert.getMediaHttpUploader();
uploader.setDirectUploadEnabled(false);
aResult = insert.execute();
setProperties(aResult.getId(), sProperties);
}
return aResult;
}
@Override
public File getFileByPath(String sPath) throws IOException {
File aResult = null;
if (drive != null) {
String[] aSubPaths = sPath.split(PATH_SPLIT_REGEX);
String aParentID = getRootFolderID();
if (aSubPaths.length > 0) {
for (int i = 0; i < (aSubPaths.length - 1); i++) {
String aSubPath = aSubPaths[i];
if (!aSubPath.isEmpty()) {
ArrayList<File> aFiles = getChildrenFolders(aParentID, aSubPath);
if (aFiles.isEmpty()) {
throw new IOException("Folder '" + aSubPath + "' not found");
} else {
aResult = aFiles.get(0);
}
aParentID = aResult.getId();
}
}
String aSubPath = aSubPaths[aSubPaths.length - 1];
if (!aSubPath.isEmpty()) {
ArrayList<File> aFiles = getChildren(aParentID, aSubPath);
if (aFiles.isEmpty()) {
throw new IOException("File '" + aSubPath + "' not found");
} else {
aResult = aFiles.get(0);
}
}
}
}
return aResult;
}
@Override
public File createDirectory(String sName, Map<String, String> sProperties) throws IOException {
File aResult = null;
Matcher aPathMatcher = PATH_EXTRACT_PATTERN.matcher(sName);
if (aPathMatcher.matches()) {
String aPath = aPathMatcher.group(1);
String aName = aPathMatcher.group(2);
if ((aName == null) || aName.isEmpty()) {
throw new IOException("Name is mandatory.");
}
String aParentID = getRootFolderID();
if ((aPath != null) && !aPath.isEmpty()) {
aParentID = createPath(aPath).getId();
}
aResult = createDirectory(aParentID, aName, sProperties);
} else {
throw new IOException("Name does not conform to paths syntax.");
}
return aResult;
}
@Override
public File createDirectory(String sParentID, String sName, Map<String, String> sProperties) throws IOException {
File aResult = null;
if (drive != null) {
File fileMetadata = new File();
fileMetadata.setTitle(sName);
fileMetadata.setMimeType(FOLDER_MIMETYPE);
ParentReference aParentReference = new ParentReference();
aParentReference.setId(sParentID);
fileMetadata.setParents(Arrays.asList(aParentReference));
Drive.Files.Insert insert = drive.files().insert(fileMetadata);
aResult = insert.execute();
setProperties(aResult.getId(), sProperties);
}
return aResult;
}
@Override
public File createPath(String sPath) throws IOException {
File aResult = null;
if (drive != null) {
String[] aSubPaths = sPath.split(PATH_SPLIT_REGEX);
String aParentID = getRootFolderID();
for (String aSubPath : aSubPaths) {
if (!aSubPath.isEmpty()) {
ArrayList<File> aFiles = getChildrenFolders(aParentID, aSubPath);
if (aFiles.isEmpty()) {
aResult = createDirectory(aParentID, aSubPath, null);
} else {
aResult = aFiles.get(0);
}
aParentID = aResult.getId();
}
}
}
return aResult;
}
@Override
public String getFolderId(String sPath) throws IOException {
String aResult = null;
if (drive != null) {
String[] aSubPaths = sPath.split(PATH_SPLIT_REGEX);
int aNbSubPaths = aSubPaths.length;
int i = 0;
String aParentID = getRootFolderID();
while (i < aNbSubPaths) {
if (!aSubPaths[i].isEmpty()) {
ArrayList<File> aFiles = getChildrenFolders(aParentID, aSubPaths[i]);
if (!aFiles.isEmpty()) {
aParentID = aFiles.get(0).getId();
} else {
throw new IOException("Folder '" + aSubPaths[i] + "' not found.");
}
}
i++;
}
aResult = (aNbSubPaths > 0) ? aParentID : null;
}
return aResult;
}
interface ChildrenFilter {
public boolean isSelected(File sFile);
}
protected ArrayList<File> getChildrenFolders(String sFolderId, String sChildName) {
final String aChildName = sChildName;
return getChildren(sFolderId, new ChildrenFilter() {
public boolean isSelected(File sFile) {
boolean aResult = false;
if (sFile != null) {
String fileExtension = sFile.getFileExtension();
String mimeType = sFile.getMimeType();
if (mimeType != null && FOLDER_MIMETYPE.equals(mimeType) && (fileExtension == null || fileExtension.isEmpty())) {
aResult = ((aChildName == null) || (aChildName.equals(sFile.getTitle())));
}
}
return aResult;
}
});
}
protected ArrayList<File> getChildren(String sFolderId, String sChildName) {
final String aChildName = sChildName;
return getChildren(sFolderId, new ChildrenFilter() {
public boolean isSelected(File sFile) {
boolean aResult = false;
if (sFile != null) {
aResult = ((aChildName == null) || (aChildName.equals(sFile.getTitle())));
}
return aResult;
}
});
}
protected ArrayList<File> getChildren(String sFolderId, ChildrenFilter sFilter) {
ArrayList<File> aResult = new ArrayList<File>();
Children.List request = null;
boolean ok = true;
do {
try {
request = drive.children().list(sFolderId).setMaxResults(200);
ChildList files = request.execute();
if (files != null) {
List<ChildReference> listChildReference = files.getItems();
for (ChildReference childReference : listChildReference) {
File file = drive.files().get(childReference.getId()).execute();
if (sFilter.isSelected(file)) {
aResult.add(file);
}
}
request.setPageToken(files.getNextPageToken());
}
} catch (IOException exception) {
ok = false;
}
} while (ok && request.getPageToken() != null && request.getPageToken().length() > 0);
return aResult;
}
protected void setProperties(String sNodeId, Map<String, String> sProperties) throws IOException {
if ((sNodeId != null) && (!sNodeId.isEmpty()) && (sProperties != null)) {
for (Entry<String, String> aKeyValue : sProperties.entrySet()) {
Property newProperty = new Property();
newProperty.setKey(aKeyValue.getKey());
newProperty.setValue(aKeyValue.getValue());
newProperty.setVisibility("PUBLIC");
drive.properties().insert(sNodeId, newProperty).execute();
}
}
}
@Override
public Map<String, String> getProperties(String sFileID) throws IOException {
Map<String, String> aResult = new HashMap<String, String>(1);
if ((sFileID != null) && (!sFileID.isEmpty())) {
PropertyList properties = drive.properties().list(sFileID).execute();
for (Property aProperty : properties.getItems()) {
aResult.put(aProperty.getKey(), aProperty.getValue());
}
}
return aResult;
}
}