001/* 002 * (C) Copyright 2015-2016 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Kevin Leturc 018 */ 019package org.nuxeo.ecm.liveconnect.box; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.net.URI; 024 025import javax.servlet.http.HttpServletRequest; 026 027import org.apache.http.HttpStatus; 028import org.nuxeo.ecm.core.blob.BlobManager.UsageHint; 029import org.nuxeo.ecm.core.blob.ManagedBlob; 030import org.nuxeo.ecm.core.model.Document; 031import org.nuxeo.ecm.liveconnect.core.AbstractLiveConnectBlobProvider; 032import org.nuxeo.ecm.liveconnect.core.LiveConnectFile; 033import org.nuxeo.ecm.liveconnect.core.LiveConnectFileInfo; 034import org.nuxeo.ecm.platform.oauth2.tokens.NuxeoOAuth2Token; 035 036import com.box.sdk.BoxAPIConnection; 037import com.box.sdk.BoxAPIException; 038import com.box.sdk.BoxFile; 039import com.box.sdk.BoxSharedLink; 040import com.box.sdk.BoxSharedLink.Access; 041import com.google.api.client.auth.oauth2.Credential; 042import com.google.api.client.http.GenericUrl; 043import com.google.api.client.http.HttpHeaders; 044import com.google.api.client.http.HttpRequest; 045import com.google.api.client.http.HttpRequestFactory; 046import com.google.api.client.http.HttpResponse; 047import com.google.api.client.http.HttpResponseException; 048import com.google.api.client.json.GenericJson; 049import com.google.api.client.util.ArrayMap; 050 051/** 052 * Provider for blobs getting information from Box. 053 * 054 * @since 8.1 055 */ 056public class BoxBlobProvider extends AbstractLiveConnectBlobProvider<BoxOAuth2ServiceProvider> { 057 058 private static final String CACHE_NAME = "box"; 059 060 private static final String BOX_DOCUMENT_TO_BE_UPDATED_PP = "box_document_to_be_updated"; 061 062 private static final String BOX_URL = "https://api.box.com/2.0/"; 063 064 private static final String DOWNLOAD_CONTENT_URL = BOX_URL + "files/%s/content"; 065 066 private static final String THUMBNAIL_CONTENT_URL = BOX_URL + "files/%s/thumbnail.jpg?min_height=320&min_width=320"; 067 068 private static final String EMBED_URL = BOX_URL + "files/%s?fields=expiring_embed_link"; 069 070 @Override 071 protected String getCacheName() { 072 return CACHE_NAME; 073 } 074 075 @Override 076 public String getPageProviderNameForUpdate() { 077 return BOX_DOCUMENT_TO_BE_UPDATED_PP; 078 } 079 080 @Override 081 public URI getURI(ManagedBlob blob, UsageHint usage, HttpServletRequest servletRequest) throws IOException { 082 LiveConnectFileInfo fileInfo = toFileInfo(blob); 083 String url = null; 084 switch (usage) { 085 case STREAM: 086 case DOWNLOAD: 087 url = getDownloadUrl(fileInfo); 088 break; 089 case VIEW: 090 url = retrieveSharedLink(fileInfo).getURL(); 091 break; 092 case EMBED: 093 url = getEmbedUrl(fileInfo); 094 break; 095 } 096 return url == null ? null : asURI(url); 097 } 098 099 @Override 100 public InputStream getStream(ManagedBlob blob) throws IOException { 101 URI uri = getURI(blob, UsageHint.STREAM, null); 102 return uri == null ? null : doGet(uri.toString()).getContent(); 103 } 104 105 @Override 106 public InputStream getThumbnail(ManagedBlob blob) throws IOException { 107 // TODO update method when https://github.com/box/box-java-sdk/issues/54 will be done 108 LiveConnectFileInfo fileInfo = toFileInfo(blob); 109 GenericUrl url = new GenericUrl(String.format(THUMBNAIL_CONTENT_URL, fileInfo.getFileId())); 110 111 HttpResponse response = executeAuthenticate(fileInfo, url, false); 112 int statusCode = response.getStatusCode(); 113 if (statusCode == HttpStatus.SC_OK) { 114 return response.getContent(); 115 } else if (statusCode == HttpStatus.SC_MOVED_TEMPORARILY || statusCode == HttpStatus.SC_ACCEPTED) { 116 response.disconnect(); 117 return doGet(response.getHeaders().getLocation()).getContent(); 118 } 119 throw new HttpResponseException(response); 120 } 121 122 @Override 123 public ManagedBlob freezeVersion(ManagedBlob blob, Document doc) throws IOException { 124 LiveConnectFileInfo fileInfo = toFileInfo(blob); 125 if (fileInfo.getRevisionId().isPresent()) { 126 // already frozen 127 return null; 128 } 129 BoxFile.Info boxFileInfo = retrieveBoxFileInfo(fileInfo); 130 // put the latest file version in cache 131 putFileInCache(new BoxLiveConnectFile(fileInfo, boxFileInfo)); 132 String revisionId = boxFileInfo.getVersion().getVersionID(); 133 134 fileInfo = new LiveConnectFileInfo(fileInfo.getUser(), fileInfo.getFileId(), revisionId); 135 BoxLiveConnectFile file = new BoxLiveConnectFile(fileInfo, boxFileInfo); 136 return toBlob(file); 137 } 138 139 protected BoxAPIConnection getBoxClient(NuxeoOAuth2Token token) throws IOException { 140 return getBoxClient(getCredential(token)); 141 } 142 143 protected BoxAPIConnection getBoxClient(Credential credential) throws IOException { 144 return new BoxAPIConnection(credential.getAccessToken()); 145 } 146 147 @Override 148 protected LiveConnectFile retrieveFile(LiveConnectFileInfo fileInfo) throws IOException { 149 return new BoxLiveConnectFile(fileInfo, retrieveBoxFileInfo(fileInfo)); 150 } 151 152 protected BoxFile.Info retrieveBoxFileInfo(LiveConnectFileInfo fileInfo) throws IOException { 153 try { 154 return prepareBoxFile(fileInfo).getInfo(); 155 } catch (BoxAPIException e) { 156 throw new IOException("Failed to retrieve Box file metadata", e); 157 } 158 } 159 160 private BoxSharedLink retrieveSharedLink(LiveConnectFileInfo fileInfo) throws IOException { 161 try { 162 return prepareBoxFile(fileInfo).createSharedLink(Access.OPEN, null, null); 163 } catch (BoxAPIException e) { 164 throw new IOException("Failed to retrieve Box shared link", e); 165 } 166 } 167 168 private BoxFile prepareBoxFile(LiveConnectFileInfo fileInfo) throws IOException { 169 BoxAPIConnection boxClient = getBoxClient(getCredential(fileInfo)); 170 return new BoxFile(boxClient, fileInfo.getFileId()); 171 } 172 173 /** 174 * Returns the temporary download url for input file. 175 * 176 * @param fileInfo the file info 177 * @return the temporary download url for input file 178 */ 179 private String getDownloadUrl(LiveConnectFileInfo fileInfo) throws IOException { 180 // TODO update method when https://github.com/box/box-java-sdk/issues/182 will be done 181 GenericUrl url = new GenericUrl(String.format(DOWNLOAD_CONTENT_URL, fileInfo.getFileId())); 182 fileInfo.getRevisionId().ifPresent(revId -> url.put("version", revId)); 183 184 HttpResponse response = executeAuthenticate(fileInfo, url, false); 185 response.disconnect(); 186 if (response.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) { 187 return response.getHeaders().getLocation(); 188 } 189 throw new HttpResponseException(response); 190 } 191 192 /** 193 * Returns the temporary embed url for input file. 194 * 195 * @param fileInfo the file info 196 * @return the temporary embed url for input file 197 */ 198 @SuppressWarnings("unchecked") 199 private String getEmbedUrl(LiveConnectFileInfo fileInfo) throws IOException { 200 // TODO change below by box client call when it'll be available 201 GenericUrl url = new GenericUrl(String.format(EMBED_URL, fileInfo.getFileId())); 202 203 try { 204 HttpResponse response = executeAuthenticate(fileInfo, url, true); 205 GenericJson boxInfo = response.parseAs(GenericJson.class); 206 return ((ArrayMap<String, Object>) boxInfo.get("expiring_embed_link")).get("url").toString(); 207 } catch (HttpResponseException e) { 208 // Extension not supported 209 if (e.getStatusCode() == HttpStatus.SC_REQUEST_URI_TOO_LONG) { 210 return null; 211 } 212 throw e; 213 } 214 } 215 216 private HttpResponse executeAuthenticate(LiveConnectFileInfo fileInfo, GenericUrl url, boolean followRedirect) 217 throws IOException { 218 Credential credential = getCredential(fileInfo); 219 220 HttpRequest request = getOAuth2Provider().getRequestFactory().buildGetRequest(url); 221 request.setHeaders(new HttpHeaders().setAuthorization("Bearer " + credential.getAccessToken())); 222 request.setFollowRedirects(followRedirect); 223 request.setThrowExceptionOnExecuteError(followRedirect); 224 225 return request.execute(); 226 } 227 228 /** 229 * Executes a GET request 230 */ 231 private HttpResponse doGet(String url) throws IOException { 232 HttpRequestFactory requestFactory = getOAuth2Provider().getRequestFactory(); 233 return requestFactory.buildGetRequest(new GenericUrl(url)).execute(); 234 } 235 236}