/** * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to you 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. */ package com.aliyun.odps.volume; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; import com.aliyun.odps.Odps; import com.aliyun.odps.OdpsException; import com.aliyun.odps.Volume; import com.aliyun.odps.VolumeException; import com.aliyun.odps.VolumeFSFile; import com.aliyun.odps.Volumes; import com.aliyun.odps.fs.VolumeFileSystemConfigKeys; import com.aliyun.odps.tunnel.TunnelException; import com.aliyun.odps.tunnel.VolumeFSErrorCode; import com.aliyun.odps.tunnel.VolumeFSTunnel; import com.aliyun.odps.tunnel.io.CompressOption; import com.aliyun.odps.tunnel.io.VolumeOutputStream; import com.aliyun.odps.volume.protocol.VolumeFSConstants; import com.aliyun.odps.volume.protocol.VolumeFSErrorMessageGenerator; /** * Client for Volume * * @author Emerson Zhao [mailto:zhenyi.zzy@alibaba-inc.com] * */ public class VolumeFSClient { private Odps odps; private String tunnelEndpoint; private Configuration conf; public VolumeFSClient(Odps odps, String project, String serviceEndpoint, String tunnelEndpoint, Configuration conf) { this.odps = odps; this.odps.setDefaultProject(project); this.odps.setEndpoint(serviceEndpoint); this.tunnelEndpoint = tunnelEndpoint; this.conf = conf; } /** * Get File meta info * * @param path * @throws VolumeException */ public VolumeFSFile getFileInfo(String path) throws VolumeException { return new VolumeFSJobRunnerProxy<VolumeFSFile>() { @Override public VolumeFSFile doJob(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)).getVolumeFSFile(path); try { file.reload(); } catch (OdpsException e) { throw new VolumeException(e); } return file; } @Override public VolumeFSFile onVolumeMissing(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = VolumeFSFile.getRoot(odps.getDefaultProject(), odps); return file; } @Override public VolumeFSFile onNoSuchVolume(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.noSuchFileOrDirectory(path)); } @Override public VolumeFSFile onInvalidPath(String path, Map<String, Object> params) throws VolumeException { if (VolumeFSUtil.checkPathIsJustVolume(path)) { try { if (!odps.volumes().exists(VolumeFSUtil.getVolumeFromPath(path))) { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.noSuchFileOrDirectory(path)); } } catch (OdpsException e) { throw new VolumeException(e); } Volume v = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)); if (isNewVolume(v)) { return VolumeFSFile.transferVolumeToVolumeFSFile(odps.getDefaultProject(), v, odps.getRestClient()); } else { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.oldVolumeAlert(VolumeFSUtil.getVolumeFromPath(path))); } } else { throw (VolumeException) params.get(CUR_EXCEPTION); } } }.run(path, null); } /** * Make directory * * @param path * @throws VolumeException */ public boolean mkdirs(String path) throws VolumeException { return new VolumeFSJobRunnerProxy<Boolean>() { @Override public Boolean doJob(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile.create(odps.getDefaultProject(), path, true, odps.getRestClient()); return true; } @Override public Boolean onVolumeMissing(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.PathAlreadyExists, VolumeFSErrorMessageGenerator.fileExists(path)); } @Override public Boolean onNoSuchVolume(String path, Map<String, Object> params) throws VolumeException { try { odps.volumes().create(VolumeFSUtil.getVolumeFromPath(path), VolumeFSConstants.CREATE_VOLUME_COMMENT, Volume.Type.NEW); } catch (Exception e) { throw new VolumeException(e); } if (VolumeFSUtil.checkPathIsJustVolume(path)) { return true; } else { return doJob(path, params); } } @Override public Boolean onInvalidPath(String path, Map<String, Object> params) throws VolumeException { // If the path is a volume name , will return true if (VolumeFSUtil.checkPathIsJustVolume(path)) { try { if (!odps.volumes().exists(VolumeFSUtil.getVolumeFromPath(path))) { odps.volumes().create(VolumeFSUtil.getVolumeFromPath(path), VolumeFSConstants.CREATE_VOLUME_COMMENT, Volume.Type.NEW); } } catch (OdpsException e) { throw new VolumeException(e); } return true; } else { throw (VolumeException) params.get(CUR_EXCEPTION); } } @Override public Boolean onPathAlreadyExists(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)).getVolumeFSFile(path); try { file.reload(); } catch (OdpsException oe) { throw new VolumeException(oe); } if (Boolean.TRUE.equals(file.getIsdir())) { return Boolean.TRUE; } else { throw (VolumeException) params.get(CUR_EXCEPTION); } } }.run(path, null); } /** * Set file's replication * * @param path * @param replication * @throws VolumeException */ public boolean setReplication(String path, short replication) throws VolumeException { Map<String, Object> params = new HashMap<String, Object>(); Map<String, String> innerParams = new HashMap<String, String>(); innerParams.put(VolumeFSFile.ParamKey.REPLICATION.name().toLowerCase(), String.valueOf(replication)); params.put("params", innerParams); return new VolumeFSJobRunnerProxy<Boolean>() { @SuppressWarnings("unchecked") @Override public Boolean doJob(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)).getVolumeFSFile(path); file.update((Map<String, String>) params.get("params")); return true; } @Override public Boolean onVolumeMissing(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.NotAcceptableOperation, VolumeFSErrorMessageGenerator.isADirectory(path)); } @Override public Boolean onNoSuchVolume(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.noSuchFileOrDirectory(path)); } }.run(path, params); } /** * List files (and directories) in specific path * * @param path * @throws VolumeException */ public VolumeFSFile[] getFileInfosByPath(String path) throws VolumeException { return new VolumeFSJobRunnerProxy<VolumeFSFile[]>() { @Override public VolumeFSFile[] doJob(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)).getVolumeFSFile(path); Iterator<VolumeFSFile> iterator = file.iterator(); List<VolumeFSFile> files = new ArrayList<VolumeFSFile>(); try { while (iterator.hasNext()) { files.add(iterator.next()); } } catch (RuntimeException e) { if (e.getCause() instanceof VolumeException) { throw (VolumeException) e.getCause(); } else { throw new VolumeException(e); } } return files.toArray(new VolumeFSFile[0]); } @Override public VolumeFSFile[] onVolumeMissing(String path, Map<String, Object> params) throws VolumeException { List<VolumeFSFile> files = new ArrayList<VolumeFSFile>(); Volumes volumes = odps.volumes(); Iterator<Volume> it = volumes.iterator(); while (it.hasNext()) { Volume v = it.next(); if (isNewVolume(v)) { files.add(VolumeFSFile.transferVolumeToVolumeFSFile(odps.getDefaultProject(), v, odps.getRestClient())); } } return files.toArray(new VolumeFSFile[0]); } @Override public VolumeFSFile[] onNoSuchVolume(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.noSuchFileOrDirectory(path)); } @Override public VolumeFSFile[] onInvalidPath(String path, Map<String, Object> params) throws VolumeException { if (VolumeFSUtil.checkPathIsJustVolume(path)) { return doJob(path, params); } else { throw (VolumeException) params.get(CUR_EXCEPTION); } } }.run(path, null); } /** * Rename file (or directory) * * @param src * @param dst * @throws VolumeException */ public boolean rename(String src, String dst) throws VolumeException { Map<String, Object> params = new HashMap<String, Object>(); Map<String, String> innerParams = new HashMap<String, String>(); innerParams.put(VolumeFSFile.ParamKey.PATH.name().toLowerCase(), dst); params.put("params", innerParams); return new VolumeFSJobRunnerProxy<Boolean>() { @SuppressWarnings("unchecked") @Override public Boolean doJob(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)).getVolumeFSFile(path); file.update((Map<String, String>) params.get("params")); return true; } @Override public Boolean onVolumeMissing(String path, Map<String, Object> params) throws VolumeException { return false; } @Override public Boolean onNoSuchVolume(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.noSuchFileOrDirectory(path)); } @Override public Boolean onInvalidPath(String path, Map<String, Object> params) throws VolumeException { if (VolumeFSUtil.checkPathIsJustVolume(path)) { throw new VolumeException(VolumeFSErrorCode.NotAcceptableOperation, VolumeFSErrorMessageGenerator .theOpreationIsNotAllowed("mv volume (The first level directory)")); } else { throw (VolumeException) params.get(CUR_EXCEPTION); } } }.run(src, params); } /** * Delete file (or directory) * * @param path * @param recursive * @throws VolumeException */ public boolean delete(String path, boolean recursive) throws VolumeException { Map<String, Object> params = new HashMap<String, Object>(); params.put(VolumeFSFile.ParamKey.RECURSIVE.name().toLowerCase(), recursive); return new VolumeFSJobRunnerProxy<Boolean>() { @Override public Boolean doJob(String path, Map<String, Object> params) throws VolumeException { VolumeFSFile file = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)).getVolumeFSFile(path); file.delete((Boolean) params.get(VolumeFSFile.ParamKey.RECURSIVE.name().toLowerCase())); return true; } @Override public Boolean onVolumeMissing(String path, Map<String, Object> params) throws VolumeException { return false; } @Override public Boolean onNoSuchVolume(String path, Map<String, Object> params) throws VolumeException { throw new VolumeException(VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.noSuchFileOrDirectory(path)); } @Override public Boolean onInvalidPath(String path, Map<String, Object> params) throws VolumeException { // If the path is a volume name , will call volume API if (VolumeFSUtil.checkPathIsJustVolume(path)) { try { Volume v = odps.volumes().get(VolumeFSUtil.getVolumeFromPath(path)); if (isNewVolume(v)) { odps.volumes().delete(VolumeFSUtil.getVolumeFromPath(path)); return true; } else { throw new VolumeException( VolumeFSErrorCode.NoSuchPath, VolumeFSErrorMessageGenerator.oldVolumeAlert(VolumeFSUtil.getVolumeFromPath(path))); } } catch (OdpsException e) { throw new VolumeException(e); } } else { throw (VolumeException) params.get(CUR_EXCEPTION); } } }.run(path, params); } /** * Download volume file * * @param path * @param start start of range * @param end start of range * @param targetFile * @param append * @throws IOException */ public void downloadFile(String path, Long start, Long end, File targetFile, boolean append) throws VolumeException { VolumeFSUtil.checkPath(path); OutputStream outputStream = null; try { outputStream = new FileOutputStream(targetFile, append); } catch (FileNotFoundException e) { throw new VolumeException(e); } InputStream inputStream = null; try { inputStream = openInputStream(path, start, end); IOUtils.copy(inputStream, outputStream); } catch (IOException e) { throw new VolumeException(e); } finally { IOUtils.closeQuietly(outputStream); IOUtils.closeQuietly(inputStream); } } /** * Create an {@link InputStream} at the indicated Path * * @param path * @param start * @param end * @throws IOException */ public InputStream openInputStream(String path, Long start, Long end) throws VolumeException { VolumeFSUtil.checkPath(path); boolean compress = conf.getBoolean(VolumeFileSystemConfigKeys.ODPS_VOLUME_TRANSFER_COMPRESS_ENABLED, false); CompressOption compressOption = null; if (compress) { CompressOption.CompressAlgorithm a = CompressOption.CompressAlgorithm.valueOf(conf.get( VolumeFileSystemConfigKeys.ODPS_VOLUME_TRANSFER_COMPRESS_ALGORITHM, VolumeFSConstants.ODPS_ZLIB)); compressOption = new CompressOption(a, 1, 0); } InputStream in = null; try { in = getVolumeTunnel().openInputStream(odps.getDefaultProject(), path, start, end, compressOption); } catch (TunnelException e) { throw new VolumeException(e); } return in; } /** * Create an {@link OutputStream} at the indicated Path * * @param path * @param overwrite * @param replication * @throws IOException */ public OutputStream openOutputStream(String path, boolean overwrite, short replication) throws VolumeException { VolumeFSUtil.checkPath(path); VolumeFSFile file = null; try { if (!odps.volumes().exists(VolumeFSUtil.getVolumeFromPath(path))) { odps.volumes().create(VolumeFSUtil.getVolumeFromPath(path), VolumeFSConstants.CREATE_VOLUME_COMMENT, Volume.Type.NEW); } } catch (OdpsException oe) { throw new VolumeException(oe); } try { file = getFileInfo(path); } catch (VolumeException e) { if (!VolumeFSErrorCode.NoSuchPath.equalsIgnoreCase(e.getErrCode())) { throw e; } } if (file != null) { if (overwrite) { if (file.getIsdir()) { throw new VolumeException(VolumeFSErrorCode.PathAlreadyExists, VolumeFSErrorMessageGenerator.fileExists(path)); } else { delete(path, true); } } else { throw new VolumeException(VolumeFSErrorCode.PathAlreadyExists, VolumeFSErrorMessageGenerator.fileExists(path)); } } VolumeFSTunnel tunnel = getVolumeTunnel(); // create a zero-byte file to satisfy visibility before wrote data into file createZeroByteFile(path, replication, tunnel); boolean compress = conf.getBoolean(VolumeFileSystemConfigKeys.ODPS_VOLUME_TRANSFER_COMPRESS_ENABLED, false); CompressOption compressOption = null; if (compress) { CompressOption.CompressAlgorithm a = CompressOption.CompressAlgorithm.valueOf(conf.get( VolumeFileSystemConfigKeys.ODPS_VOLUME_TRANSFER_COMPRESS_ALGORITHM, VolumeFSConstants.ODPS_ZLIB)); compressOption = new CompressOption(a, 1, 0); } OutputStream out = null; try { out = tunnel.openOutputStream(odps.getDefaultProject(), path, new Integer(replication), compressOption); } catch (TunnelException e) { throw new VolumeException(e); } return out; } /** * Create a zero-byte file * * @param path * @param replication * @param tunnel * @throws VolumeException */ public void createZeroByteFile(String path, short replication, VolumeFSTunnel tunnel) throws VolumeException { VolumeFSUtil.checkPath(path); OutputStream o = null; try { o = tunnel.openOutputStream(odps.getDefaultProject(), path, new Integer(replication), new CompressOption()); String sessionId = VolumeFSTunnel.getUploadSessionId((VolumeOutputStream) o); tunnel.commit(odps.getDefaultProject(), path, sessionId); } catch (TunnelException e) { throw new VolumeException(e); } } /** * Commit the uploadSession * * @param path * @param out * @param overwrite * @throws VolumeException */ public void commitUploadSession(String path, VolumeOutputStream out, boolean overwrite) throws VolumeException { VolumeFSUtil.checkPath(path); try { out.close(); String sessionId = VolumeFSTunnel.getUploadSessionId(out); VolumeFSFile file = null; try { file = getFileInfo(path); } catch (Exception e) { } if (file != null) { if (overwrite) { if (file.getIsdir()) { throw new VolumeException(VolumeFSErrorCode.PathAlreadyExists, VolumeFSErrorMessageGenerator.fileExists(path)); } else { file.delete(true); } } else { if (file.getLength().longValue() == 0) { // Delete zero-byte file file.delete(true); } else { throw new VolumeException(VolumeFSErrorCode.PathAlreadyExists, VolumeFSErrorMessageGenerator.fileExists(path)); } } } getVolumeTunnel().commit(odps.getDefaultProject(), path, sessionId); } catch (Exception e) { throw new VolumeException(e); } } private VolumeFSTunnel getVolumeTunnel() { VolumeFSTunnel tunnelFS = new VolumeFSTunnel(odps.clone()); if (tunnelEndpoint != null) tunnelFS.setEndpoint(tunnelEndpoint); return tunnelFS; } private boolean isNewVolume(Volume v) { return Volume.Type.NEW.equals(v.getType()); } }