001package org.opengion.plugin.cloud; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.text.SimpleDateFormat; 006import java.util.ArrayList; 007import java.util.HashMap; 008import java.util.Iterator; 009import java.util.List; 010import java.util.Map; 011 012import javax.servlet.http.HttpSession; 013 014import org.opengion.fukurou.util.Closer; 015import org.opengion.fukurou.util.FileUtil; 016import org.opengion.hayabusa.common.HybsSystemException; 017import org.opengion.hayabusa.io.StorageAPI; 018import org.openstack4j.api.OSClient.OSClientV3; 019import org.openstack4j.api.storage.ObjectStorageService; 020import org.openstack4j.model.common.DLPayload; 021import org.openstack4j.model.common.Identifier; 022import org.openstack4j.model.common.Payload; 023import org.openstack4j.model.storage.object.SwiftObject; 024import org.openstack4j.model.storage.object.options.ObjectListOptions; 025import org.openstack4j.model.storage.object.options.ObjectLocation; 026import org.openstack4j.openstack.OSFactory; 027import org.openstack4j.openstack.internal.OSClientSession; 028 029import com.fasterxml.jackson.databind.JsonNode; 030import com.fasterxml.jackson.databind.ObjectMapper; 031 032/** 033 * bluemix用のクラウドストレージ操作実装 034 * 035 * bluemix上での利用を想定しているため、ユーザ情報は環境変数VCAP_SERVICESから取得可能という前提です。 036 * この環境変数はbluemix上でオブジェクトストレージを接続設定する事で自動設定されます。 037 * 038 * このクラスのコンパイルには 039 * openstack4j-core及び openstack4j-okhttpが必要です。 040 * 実行にはそれ以外に以下のモジュールが必要です。(バージョンは作成時のもの) 041 * btf-1.2.jar ,guava-20.0.jar, jackson-coreutils-1.6.jar, jackson-dataformat-yaml-2.8.8.jar, json-patch-1.9.jar, jsr305-2.0.0.jar 042 * ,msg-simple-1.1.jar, okhttp-3.2.0.jar, okio-1.6.0.jar, slf4j-api-1.7.21.jar, slf4j-simple-1.7.21.jar, snakeyaml-1.15.jar 043 * 044 * 045 * @og.group クラウド 046 * @og.rev 5.9.25.0 (2017/10/06) 新規作成 047 * 048 * @version 5.0 049 * @author T.OTA 050 * @sinse JDK7.0 051 */ 052public class StorageAPI_bluemix implements StorageAPI { 053 054 // クラス変数 055 // コンテナ名 056 String container = null; 057 // ユーザ名 058 String username = null; 059 // パスワード 060 String password = null; 061 // ドメインID 062 String domainId = null; 063 // プロジェクトID 064 String projectId = null; 065 // 認証URL 066 String auth_url = null; 067 068 /** 069 * コンストラクタ 070 * bluemixに設定されているユーザ情報の取得。 071 * システムIDを名称としたコンテナを作成する。 072 * @param container 073 * @param hsession セッション 074 */ 075 public StorageAPI_bluemix(String container, HttpSession hsession) { 076 // クラス変数に設定 077 this.container = container; 078 // CloudFoundryの環境変数から、接続情報を取得します。 079 String env = System.getenv("VCAP_SERVICES"); 080 ObjectMapper mapper = new ObjectMapper(); 081 try { 082 JsonNode node = mapper.readTree(env); 083 Iterator<JsonNode> userNode = node.get("Object-Storage").elements(); 084 JsonNode cred = (JsonNode) userNode.next().get("credentials"); 085 086 // ユーザ名 087 username = cred.get("username").textValue(); 088 // パスワード 089 password = cred.get("password").textValue(); 090 // ドメインID 091 domainId = cred.get("domainId").textValue(); 092 // プロジェクトID 093 projectId = cred.get("projectId").textValue(); 094 // 認証url 095 auth_url = cred.get("auth_url").textValue() + "/v3"; 096 } catch (Exception e) { 097 String errMsg = "VCAP_SERVICESの取得に失敗しました。ストレージと接続されているか確認して下さい。" + e; 098 throw new HybsSystemException(errMsg); 099 } 100 101 // コンテナの作成(既に存在する場合は、そのまま通過する) 102 try{ 103 ObjectStorageService objectStorage = auth(hsession); 104 objectStorage.containers().create(container); 105 }catch(Exception e){ 106 StringBuilder sbErrMsg = new StringBuilder(); 107 sbErrMsg.append("コンテナの作成に失敗しました。container:"); 108 sbErrMsg.append(container); 109 sbErrMsg.append(" errInfo:"); 110 sbErrMsg.append(e); 111 throw new HybsSystemException(sbErrMsg.toString()); 112 } 113 } 114 115 /** 116 * 認証処理 117 * @param hsession セッション 118 * @return ObjectStorageService 119 */ 120 private ObjectStorageService auth(HttpSession hsession) { 121 OSClientSession<?, ?> session = OSClientSession.getCurrent(); 122 if (session != null) { 123 // 既に認証されている場合は、認証情報を返却 124 return session.objectStorage(); 125 } else { 126 // セッションから認証トークンを取得 127 String token = (String) hsession.getAttribute(SESSION_CLOUD_TOKEN); 128 // 認証トークンがある場合は、トークンによる認証を行う 129 if (token != null && !"".equals(token)) { 130 // トークンによる認証 131 OSClientV3 os = OSFactory.builderV3().endpoint(auth_url).token(token) 132 .scopeToProject(Identifier.byId(projectId)) 133 .authenticate(); 134 return os.objectStorage(); 135 } 136 } 137 138 // ユーザによる認証(スレッド間はOSClientSessionに保持される) 139 Identifier domainIdentifier = Identifier.byId(domainId); 140 OSClientV3 os = OSFactory.builderV3().endpoint(auth_url).credentials(username, password, domainIdentifier) 141 .scopeToProject(Identifier.byId(projectId)) 142 .authenticate(); 143 144 // 認証トークンの保持 145 hsession.setAttribute(SESSION_CLOUD_TOKEN, os.getToken().getId()); 146 147 ObjectStorageService objectStorage = os.objectStorage(); 148 149 return objectStorage; 150 } 151 152 /** 153 * アップロード 154 * 155 * @param partInputStream アップロード対象のストリーム 156 * @param updFolder アップロードフォルタ名 157 * @param updFileName アップロードファイル名 158 * @param hsession セッション 159 */ 160 @Override 161 public void add(InputStream partInputStream, String updFolder, String updFileName, HttpSession hsession) { 162 // 認証 163 ObjectStorageService objectStorage = auth(hsession); 164 // アップロードストレーム 165 Payload<InputStream> payload = new InputPayload<InputStream>(partInputStream); 166 try { 167 // アップロード処理 168 objectStorage.objects().put(this.container, updFolder + updFileName, payload); 169 } catch (Exception e) { 170 StringBuilder sbErrMsg = new StringBuilder(); 171 sbErrMsg.append("ストレージへのファイルアップロードに失敗しました。updFolder:"); 172 sbErrMsg.append(updFolder); 173 sbErrMsg.append(" updFileName:"); 174 sbErrMsg.append(updFileName); 175 sbErrMsg.append(" errInfo:"); 176 sbErrMsg.append(e); 177 throw new HybsSystemException(sbErrMsg.toString()); 178 } finally { 179 // クローズ処理 180 Closer.ioClose(partInputStream); 181 Closer.ioClose(payload); 182 } 183 } 184 185 /** 186 * ダウンロード 187 * 188 * @param filePath ダウンロード対象のファイルパス 189 * @param hsession セッション 190 * @return ストリーム 191 */ 192 @Override 193 public InputStream get(String filePath, HttpSession hsession) { 194 // 認証 195 ObjectStorageService objectStorage = auth(hsession); 196 DLPayload payload = null; 197 // ダウンロード 198 try { 199 SwiftObject swiftObject = objectStorage.objects().get(ObjectLocation.create(this.container, filePath)); 200 payload = swiftObject.download(); 201 } catch (Exception e) { 202 StringBuilder sbErrMsg = new StringBuilder(); 203 sbErrMsg.append("ストレージからのファイルダウンロードに失敗しました。filePath:"); 204 sbErrMsg.append(filePath); 205 sbErrMsg.append(" errInfo:"); 206 sbErrMsg.append(e); 207 throw new HybsSystemException(sbErrMsg.toString()); 208 } 209 210 return payload.getInputStream(); 211 } 212 213 /** 214 * コピー 215 * 216 * @param oldFilePath コピー元ファイルパス 217 * @param newFilePath コピー先ファイルパス 218 * @param hsession セッション 219 */ 220 @Override 221 public void copy(String oldFilePath, String newFilePath, HttpSession hsession) { 222 // コピー処理 223 Payload<InputStream> payload = null; 224 InputStream is = null; 225 try { 226 // openstack4jにcopyメソッドは実装されているが、全角文字が利用できないため、 227 // ダウンロード・アップロードで対応 228// objectStorage.objects().copy(ObjectLocation.create(container, oldFilePath), 229// ObjectLocation.create(container, newFilePath)); 230 // コピー元情報の取得 231 is = get(oldFilePath, hsession); 232 // コピー先に登録 233 payload = new InputPayload<InputStream>(is); 234 235 // 認証 236 ObjectStorageService objectStorage = auth(hsession); 237 objectStorage.objects().put(this.container, newFilePath, payload); 238 } catch (Exception e) { 239 StringBuilder sbErrMsg = new StringBuilder(); 240 sbErrMsg.append("ストレージのファイルコピー処理に失敗しました。oldFilePath:"); 241 sbErrMsg.append(oldFilePath); 242 sbErrMsg.append(" newFilePath:"); 243 sbErrMsg.append(newFilePath); 244 sbErrMsg.append(" errInfo:"); 245 sbErrMsg.append(e); 246 throw new HybsSystemException(sbErrMsg.toString()); 247 }finally{ 248 // クローズ処理 249 Closer.ioClose(payload); 250 Closer.ioClose(is); 251 } 252 } 253 254 /** 255 * 削除 256 * 257 * @param filePath 削除ファイルのパス 258 * @param hsession セッション 259 */ 260 @Override 261 public void delete(String filePath, HttpSession hsession) { 262 // 認証 263 ObjectStorageService objectStorage = auth(hsession); 264 // 削除 265 try { 266 objectStorage.objects().delete(ObjectLocation.create(this.container, filePath)); 267 } catch (Exception e) { 268 StringBuilder sbErrMsg = new StringBuilder(); 269 sbErrMsg.append("ストレージのファイル削除に失敗しました。filePath:"); 270 sbErrMsg.append(filePath); 271 sbErrMsg.append(" errInfo:"); 272 sbErrMsg.append(e); 273 throw new HybsSystemException(sbErrMsg.toString()); 274 } 275 } 276 277 /** 278 * ファイル名変更 279 * 280 * @param filePath ファイルパス 281 * @param oldFileName 変更前ファイル名 282 * @param newFileName 変更後ファイル名 283 * @param useBackup 変更後ファイル名が既に存在する場合のバックアップ作成フラグ 284 * @param hsession セッション 285 */ 286 public void rename(String filePath, String oldFileName, String newFileName, final boolean useBackup, 287 HttpSession hsession) { 288 String newFilePath = filePath + newFileName; 289 String oldFilePath = filePath + oldFileName; 290 291 // 変更先のファイルが存在した場合の処理 292 if (exists(newFilePath, hsession)) { 293 // バックアップ作成する場合 294 if (useBackup) { 295 // バックアップファイル名は、元のファイル名(拡張子含む) + "_" + 現在時刻のlong値 + "." + 296 // 元のファイルの拡張子 297 String bkupPath = filePath + "_backup/" + newFileName + "_" + System.currentTimeMillis() 298 + FileUtil.EXTENSION_SEPARATOR + FileUtil.getExtension(newFileName); 299 // バックアップフォルダに移動 300 copy(newFilePath, bkupPath, hsession); 301 } 302 } 303 304 // コピー 305 copy(oldFilePath, newFilePath, hsession); 306 // 削除 307 delete(oldFilePath, hsession); 308 } 309 310 /** 311 * ファイル存在チェック 312 * 313 * @param filePath ファイルパス 314 * @param hsession セッション 315 * @return true:存在 false:存在しない 316 */ 317 @Override 318 public boolean exists(String filePath, HttpSession hsession) { 319 boolean blnRtn = true; 320 321 // 認証 322 ObjectStorageService objectStorage = auth(hsession); 323 324 try { 325 SwiftObject so = objectStorage.objects().get(ObjectLocation.create(this.container, filePath)); 326 327 if (so == null) { 328 // ファイルが取得できなかった場合は、falseを設定 329 blnRtn = false; 330 } 331 } catch (Exception e) { 332 StringBuilder sbErrMsg = new StringBuilder(); 333 sbErrMsg.append("ストレージのファイル取得に失敗しました。filePath:"); 334 sbErrMsg.append(filePath); 335 sbErrMsg.append(" errInfo:"); 336 sbErrMsg.append(e); 337 throw new HybsSystemException(sbErrMsg.toString()); 338 } 339 340 return blnRtn; 341 } 342 343 /** 344 * ファイル一覧取得 345 * 346 * @param startsWith パスの前方一致 347 * @param hsession セッション 348 * @return ファイルパス一覧 349 */ 350 @Override 351 public String[] list(String startsWith, HttpSession hsession) { 352 // 認証 353 ObjectStorageService objectStorage = auth(hsession); 354 List<? extends SwiftObject> list = null; 355 List<String> rtnList = new ArrayList<String>(); 356 try{ 357 // オプションの指定 358 ObjectListOptions olo = ObjectListOptions.create().startsWith(startsWith); 359 // 一覧の取得 360 list = objectStorage.objects().list(this.container, olo); 361 for(SwiftObject so: list){ 362 rtnList.add(so.getName()); 363 } 364 } catch (Exception e){ 365 StringBuilder sbErrMsg = new StringBuilder(); 366 sbErrMsg.append("ファイル一覧の取得に失敗しました。startsWith:"); 367 sbErrMsg.append(startsWith); 368 sbErrMsg.append(" errInfo:"); 369 sbErrMsg.append("e"); 370 throw new HybsSystemException(sbErrMsg.toString()); 371 } 372 return rtnList.toArray(new String[rtnList.size()]); 373 } 374 375 /** 376 * ファイル情報取得 377 * 378 * @param path ファイルパス 379 * @param hsession セッション 380 * @return ファイル情報格納Map 381 */ 382 @Override 383 public Map<String, String> getInfo(String path, HttpSession hsession) { 384 Map<String, String> rtnMap = new HashMap<String,String>(); 385 386 // 認証 387 ObjectStorageService objectStorage = auth(hsession); 388 389 SwiftObject so = null; 390 try{ 391 // ファイルオブジェクトの取得 392 so = objectStorage.objects().get(ObjectLocation.create(this.container, path)); 393 }catch(Exception e){ 394 StringBuilder sbErrMsg = new StringBuilder(); 395 sbErrMsg.append("ファイルの取得に失敗しました。path:"); 396 sbErrMsg.append(path); 397 sbErrMsg.append(" errInfo:"); 398 sbErrMsg.append(e); 399 throw new HybsSystemException(sbErrMsg.toString()); 400 } 401 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss"); 402 403 // ファイルサイズ 404 rtnMap.put(FILEINFO_SIZE, String.valueOf(so.getSizeInBytes())); 405 // 最終更新時刻 406 rtnMap.put(FILEINFO_LASTMODIFIED, sdf.format(so.getLastModified())); 407 408 return rtnMap; 409 } 410 411 412 /** 413 * payloadを利用するための内部クラス 414 * (AWSやAzureで利用するか不明なので、とりあえず内部クラスとしておきます) 415 * 416 * @param <T> 417 */ 418 public class InputPayload<T extends InputStream> implements Payload<T>{ 419 private T stream = null; 420 421 /** 422 * @param stream 423 */ 424 public InputPayload(T stream) { 425 this.stream = stream; 426 } 427 428 @Override 429 public void close() throws IOException { 430 stream.close(); 431 } 432 433 @Override 434 public T open() { 435 return stream; 436 } 437 438 @Override 439 public void closeQuietly() { 440 Closer.ioClose(stream); 441 } 442 443 @Override 444 public T getRaw() { 445 return stream; 446 } 447 } 448}