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}