package de.tuberlin.onedrivesdk.common; import com.google.gson.Gson; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import de.tuberlin.onedrivesdk.OneDriveException; import de.tuberlin.onedrivesdk.OneDriveSDK; import de.tuberlin.onedrivesdk.drive.ConcreteOneDrive; import de.tuberlin.onedrivesdk.drive.OneDrive; import de.tuberlin.onedrivesdk.file.ConcreteOneFile; import de.tuberlin.onedrivesdk.file.OneFile; import de.tuberlin.onedrivesdk.folder.ConcreteOneFolder; import de.tuberlin.onedrivesdk.folder.OneFolder; import de.tuberlin.onedrivesdk.networking.*; import de.tuberlin.onedrivesdk.uploadFile.UploadSession; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.json.simple.parser.ParseException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * This class provides the functionality to authenticate to OneDrive and handles the communication. */ public class ConcreteOneDriveSDK implements OneDriveSDK { private static final Logger logger = LogManager.getLogger(OneDriveSession.class); private static final Gson gson = new Gson(); private String baseUrl = "https://api.onedrive.com/v1.0/"; private OneDriveSession session; /** * Instantiates a new ConcreteOneDriveSDK. * * @param session */ private ConcreteOneDriveSDK(OneDriveSession session) { this.session = session; } /** * Instantiates a new ConcreteOneDriveSDK. * * @param clientId * @param clientSecret * @param scopes * @return OneDriveSDK */ public static OneDriveSDK createOneDriveConnection(String clientId, String clientSecret,String redirect_uri,ExceptionEventHandler handler, OneDriveScope[] scopes) { OkHttpClient cli = new OkHttpClient(); cli.setFollowRedirects(false); OneDriveSession session = OneDriveSession.initializeSession(cli, clientId, clientSecret,redirect_uri, scopes); return new ConcreteOneDriveSDK(session); } static OneDriveSDK createFromSession(OneDriveSession session) { return new ConcreteOneDriveSDK(session); } @Override public List<OneDrive> getAllDrives() throws IOException, OneDriveException { String requestURL = "drives/"; PreparedRequest request = new PreparedRequest(requestURL, PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); List<OneDrive> drives = null; try { drives = ConcreteOneDrive.parseDrivesFromJson(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } for (OneDrive drive : drives) { ((ConcreteOneDrive)drive).setApi(this); } return drives; } @Override public OneDrive getDefaultDrive() throws IOException, OneDriveException { String requestURL = "drive/"; PreparedRequest request = new PreparedRequest(requestURL, PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); ConcreteOneDrive oneDrive = null; try { oneDrive = ConcreteOneDrive.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } oneDrive.setApi(this); return oneDrive; } @Override public OneDrive getDrive(String driveId) throws IOException, OneDriveException { String requestURL = "drives/%s"; PreparedRequest request = new PreparedRequest(String.format(requestURL, driveId), PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); ConcreteOneDrive oneDrive = null; try { oneDrive = ConcreteOneDrive.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } oneDrive.setApi(this); return oneDrive; } @Override public OneFolder getFolderById(String id) throws IOException, OneDriveException { String requestURL = "drive/items/%s"; PreparedRequest request = new PreparedRequest(String.format(requestURL, id), PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); ConcreteOneFolder oneFolder = null; try { oneFolder = ConcreteOneFolder.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } oneFolder.setApi(this); return oneFolder; } @Override public OneFolder getFolderByPath(String pathToFolder) throws IOException, OneDriveException { return getFolderByPath(pathToFolder, null); } @Override public OneFolder getRootFolder() throws IOException, OneDriveException { return getRootFolder(null); } @Override public OneFolder getRootFolder(OneDrive drive) throws IOException, OneDriveException { PreparedRequest request; if (drive == null) { request = new PreparedRequest("drive/root", PreparedRequestMethod.GET); } else { request = new PreparedRequest(String.format("drives/%s/root/", drive.getId()), PreparedRequestMethod.GET); } String json = this.makeRequest(request).getBodyAsString(); ConcreteOneFolder oneFolder = null; try { oneFolder = ConcreteOneFolder.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } oneFolder.setApi(this); return oneFolder; } @Override public OneFile getFileById(String id) throws IOException, OneDriveException { String requestURL = "drive/items/%s"; PreparedRequest request = new PreparedRequest(String.format(requestURL, id), PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); ConcreteOneFile file = null; try { file = ConcreteOneFile.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } file.setApi(this); return file; } @Override public OneFile getFileByPath(String pathToFile) throws IOException, OneDriveException { return getFileByPath(pathToFile, null); } @Override public OneFolder getFolderByPath(String pathToFolder, OneDrive drive) throws IOException, OneDriveException { String requestURL = (drive == null) ? "drive" : String.format("drives/%s", drive.getId()); requestURL += "/%s"; PreparedRequest request = new PreparedRequest(String.format(requestURL, this.convertPathToApiPath(pathToFolder)), PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); ConcreteOneFolder oneFolder = null; try { oneFolder = ConcreteOneFolder.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } oneFolder.setApi(this); return oneFolder; } @Override public OneFile getFileByPath(String pathToFile, OneDrive drive) throws IOException, OneDriveException { String requestURL = (drive == null) ? "drive" : String.format("drives/%s", drive.getId()); requestURL += "/%s"; PreparedRequest request = new PreparedRequest(String.format(requestURL, this.convertPathToApiPath(pathToFile)), PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); ConcreteOneFile file = null; try { file = ConcreteOneFile.fromJSON(json); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } file.setApi(this); return file; } @Override public void authenticate(String oAuthCode) throws IOException, OneDriveException { OneDriveSession.authorizeSession(this.session, oAuthCode); } @Override public void authenticateWithRefreshToken(String refreshToken) throws IOException, OneDriveException { OneDriveSession.refreshSession(this.session, refreshToken); } @Override public String getRefreshToken() throws OneDriveException { if(!this.session.isAuthenticated() || this.session.getRefreshToken() == null || this.session.getRefreshToken().isEmpty()){ throw new OneDriveException("can not return a valid refreshToken"); } return this.session.getRefreshToken(); } @Override public void disconnect() throws IOException { this.session.terminate(); } @Override public String getAuthenticationURL() { return session.getAccessURL(); } @Override public boolean isAuthenticated() { return session.isAuthenticated(); } /** * Create a new upload session in preparation of a file upload. * * @param folder on OneDrive * @param fileName on OneDrive * @return UploadSession * @throws IOException */ public UploadSession createUploadSession(ConcreteOneFolder folder, String fileName) throws IOException, OneDriveAuthenticationException { String requestURL = "drive/items/%s:/%s:/upload.createSession"; String url = String.format(requestURL, folder.getId(), fileName); PreparedRequest request = new PreparedRequest(url, PreparedRequestMethod.POST); String json = this.makeRequest(request).getBodyAsString(); return gson.fromJson(json, UploadSession.class); } /** * Gets all children of the given folder depending on the type. * * @param concreteOneFolder * @param type * @return children * @throws IOException * @throws OneDriveException */ public List<OneItem> getChildren(ConcreteOneFolder concreteOneFolder, OneItemType type) throws IOException, OneDriveException { String requestURL = String.format("drive/items/%s/children", concreteOneFolder.getId()); PreparedRequest request = new PreparedRequest(requestURL, PreparedRequestMethod.GET); String json = this.makeRequest(request).getBodyAsString(); List<OneItem> items = null; try { items = OneItem.parseItemsFromJson(json, type); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } for (OneItem item : items) { item.setApi(this); } return items; } /** * Gets all child folder of the specified folder. * * @param concreteOneFolder * @return children * @throws IOException * @throws OneDriveException */ public List<OneFolder> getChildFolder(ConcreteOneFolder concreteOneFolder) throws IOException, OneDriveException { List<OneFolder> folder = new ArrayList<>(); for (OneItem item : this.getChildren(concreteOneFolder, OneItemType.FOLDER)) { folder.add((OneFolder) item); } return folder; } /** * Gets all child files of the specified folder. * * @param concreteOneFolder * @return children * @throws IOException * @throws OneDriveException */ public List<OneFile> getChildFiles(ConcreteOneFolder concreteOneFolder) throws IOException, OneDriveException { List<OneFile> files = new ArrayList<>(); for (OneItem item : this.getChildren(concreteOneFolder, OneItemType.FILE)) { files.add((OneFile) item); } return files; } /** * Perform the HTTP request to the OneDrive API with a json body. * * @param url * @param method * @param json body of the request * @return OneResponse * @throws IOException */ public OneResponse makeRequest(String url, PreparedRequestMethod method, String json) throws IOException, OneDriveAuthenticationException { PreparedRequest request = new PreparedRequest(url, method); request.addHeader("Content-Type", "application/json"); request.setBody(json.getBytes()); return makeRequest(request); } /** * Perform the HTTP request to the OneDrive API. * * @param preparedRequest * @return OneResponse * @throws IOException */ public OneResponse makeRequest(PreparedRequest preparedRequest) throws IOException, OneDriveAuthenticationException { if(!this.session.isAuthenticated()){ throw new OneDriveAuthenticationException("Session is no longer valid. Look for a failure of the refresh Thread in the log."); } String url; RequestBody body = null; if (preparedRequest.getBody() != null) { body = RequestBody.create(null, preparedRequest.getBody()); } if (isCompleteURL(preparedRequest.getPath())) { url = preparedRequest.getPath(); } else { url = String.format("%s%s?access_token=%s", this.baseUrl, preparedRequest.getPath(), session.getAccessToken()); } logger.debug(String.format("making request to %s",url)); Request.Builder builder = new Request.Builder().method(preparedRequest.getMethod(), body).url(url); for (String key : preparedRequest.getHeader().keySet()) { builder.addHeader(key, preparedRequest.getHeader().get(key)); } //Add auth permanently to header with redirection builder.header("Authorization", "bearer " + session.getAccessToken()); Request request = builder.build(); Response getDrivesResponse = session.getClient().newCall(request).execute(); return new ConcreteOneResponse(getDrivesResponse); } /** * Create a new folder in OneDrive. * * @param folder the parent folder * @param name name of the new folder * @return OneFolder the newly created folder * @throws IOException * @throws OneDriveException */ public OneFolder createFolder(OneFolder folder, String name) throws IOException, OneDriveException { return createFolder(folder, name, ConflictBehavior.RENAME); } /** * Create a new folder in OneDrive and define the behavior on folder name conflict. * * @param folder the parent folder * @param name * @param behavior * @return OneFolder the newly created folder * @throws IOException * @throws OneDriveException */ public OneFolder createFolder(OneFolder folder, String name, ConflictBehavior behavior) throws IOException, OneDriveException { String requestURL = String.format("drive/items/%s/children", folder.getId()); String createFolderJson = "{\"name\": \"" + name + "\", \"folder\": { }, \"@name.conflictBehavior\": \"" + behavior.name + "\"}"; OneResponse response = makeRequest(requestURL, PreparedRequestMethod.POST, createFolderJson); if (response.getStatusCode() == 201) { ConcreteOneFolder createdFolder = null; try { createdFolder = ConcreteOneFolder.fromJSON(response.getBodyAsString()); } catch (ParseException e) { throw new OneDriveException("API - response could not be processed", e); } createdFolder.setApi(this); return createdFolder; } else { throw new OneDriveException(response.toString()); } } /** * Checks if the given URL is a syntactic correct url. * * @param url * @return boolean */ private boolean isCompleteURL(String url) { try { URL u = new URL(url); // this would check for the protocol u.toURI();// does the extra checking required for validation of URI } catch (URISyntaxException | MalformedURLException e) { // if exception then no url return false; } return true; } /** * Deletes a OneDriveItem form OneDrive. * * @param oneItem to delete * @return true on success * @throws IOException * @throws OneDriveException */ public boolean deleteItem(OneItem oneItem) throws IOException, OneDriveException { String requestURL = String.format("drive/items/%s", oneItem.getId()); PreparedRequest request = new PreparedRequest(requestURL, PreparedRequestMethod.DELETE); OneResponse response = this.makeRequest(request); if (response.getStatusCode() == 204) { return true; } else { throw new OneDriveException(response.toString()); } } /** * Converts a path to api specific path. * "/" -> "root/" * "/folder" -> "root:/folder:/" * * @param path * @return String */ private String convertPathToApiPath(String path) { if (path.equals("/")) { return "root/"; } else { path = this.removeSlashes(path); return "root:/" + path + ":/"; } } /** * Remove slashes from the beginning and the end. * * @param path * @return String */ private String removeSlashes(String path) { if (path.charAt(0) == '/') { path = path.substring(1); } if (path.charAt(path.length() - 1) == '/') { path = path.substring(0, path.length() - 1); } return path; } /** * Download a file from OneDrive by id and returns the byte[]. * * @param fileID the OneDrive file id * @return byte[] * @throws IOException */ public byte[] download(String fileID) throws IOException, OneDriveAuthenticationException { session.getClient().setFollowRedirects(false); String url = "drive/items/%s/content"; url = String.format(url, fileID); PreparedRequest downloadRequest = new PreparedRequest(url, PreparedRequestMethod.GET); OneResponse getResponse = makeRequest(downloadRequest); PreparedRequest contentRequest = new PreparedRequest(getResponse.getHeader("Location"), PreparedRequestMethod.GET); OneResponse contentResponse = makeRequest(contentRequest); return contentResponse.getBodyAsBytes(); } /** * Copy a file in OneDrive to a location in OneDrive. * * @param id OneDrive item id of the file to be copied * @param destinationId id of the target folder * @return OneFile the copied file * @throws IOException * @throws OneDriveException * @throws ParseException * @throws InterruptedException */ public OneFile copy(String id, String destinationId) throws IOException, OneDriveException, ParseException, InterruptedException { return this.copy(id, destinationId, null); } /** * Copy and rename a file in OneDrive to a location in OneDrive. * * @param id OneDrive item id of the file to be copied * @param destinationId id of the target folder * @param newName the new name of the copied file * @return OneFile the copied file * @throws IOException * @throws OneDriveException * @throws ParseException * @throws InterruptedException */ public OneFile copy(String id, String destinationId, String newName) throws IOException, OneDriveException, ParseException, InterruptedException { ParentReference reference = new ParentReference(); reference.setId(destinationId); OneDestinationItem destination = new OneDestinationItem(reference); if (newName != null) destination.setName(newName); String url = String.format("drive/items/%s/action.copy", id); String json = gson.toJson(destination); PreparedRequest request = new PreparedRequest(url, PreparedRequestMethod.POST); request.addHeader("Content-Type", "application/json"); request.addHeader("Prefer", "respond-async"); request.setBody(json.getBytes()); OneResponse response = this.makeRequest(request); if (response.getStatusCode() != 202) { OneDriveError error = gson.fromJson(response.getBodyAsString(), OneDriveError.class); throw new OneDriveException("Request error: " + response.getStatusCode() + " " + error); } else { String redirectUrl = response.getHeader("Location"); do { PreparedRequest contentRequest = new PreparedRequest(redirectUrl, PreparedRequestMethod.GET); response = makeRequest(contentRequest); Thread.sleep(500); } while (response.getStatusCode() != 200 && response.getStatusCode() != 500); if (response.getStatusCode() == 500) { HashMap<String, String> copyStatus = gson.fromJson(response.getBodyAsString(), HashMap.class); throw new OneDriveException("Item copy operation status: " + copyStatus.get("status")); } } return (OneFile) ConcreteOneFile.fromJSON(response.getBodyAsString()).setApi(this); } /** * Move a file in OneDrive. * * @param id OneDrive item id of the file to be moved * @param destinationId id of the target folder * @return OneFile * @throws IOException * @throws OneDriveException * @throws ParseException * @throws InterruptedException */ public OneFile move(String id, String destinationId) throws IOException, OneDriveException, ParseException, InterruptedException { ParentReference reference = new ParentReference(); reference.setId(destinationId); OneDestinationItem destination = new OneDestinationItem(reference); String url = String.format("drive/items/%s", id); String json = gson.toJson(destination); PreparedRequest request = new PreparedRequest(url, PreparedRequestMethod.PATCH); request.addHeader("Content-Type", "application/json"); request.setBody(json.getBytes()); OneResponse response = this.makeRequest(request); if (response.getStatusCode() != 200) { OneDriveError error = gson.fromJson(response.getBodyAsString(), OneDriveError.class); throw new OneDriveException("Request error: " + response.getStatusCode() + " " + error); } return (OneFile) ConcreteOneFile.fromJSON(response.getBodyAsString()).setApi(this); } @Override public void startSessionAutoRefresh() { this.session.startRefreshThread(); } }