001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.plugins.repository.sftp;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Iterator;
026: import java.util.List;
027:
028: import org.apache.ivy.plugins.repository.BasicResource;
029: import org.apache.ivy.plugins.repository.Resource;
030: import org.apache.ivy.plugins.repository.TransferEvent;
031: import org.apache.ivy.plugins.repository.ssh.AbstractSshBasedRepository;
032: import org.apache.ivy.plugins.repository.ssh.SshCache;
033: import org.apache.ivy.util.Message;
034:
035: import com.jcraft.jsch.ChannelSftp;
036: import com.jcraft.jsch.JSchException;
037: import com.jcraft.jsch.Session;
038: import com.jcraft.jsch.SftpATTRS;
039: import com.jcraft.jsch.SftpException;
040: import com.jcraft.jsch.SftpProgressMonitor;
041: import com.jcraft.jsch.ChannelSftp.LsEntry;
042:
043: /**
044: * SFTP Repository, allow to use a repository accessed by sftp protocol. It supports all operations:
045: * get, put and list. It relies on jsch for sftp handling, and thus is compatible with sftp version
046: * 0, 1, 2 and 3
047: */
048: public class SFTPRepository extends AbstractSshBasedRepository {
049: private final class MyProgressMonitor implements
050: SftpProgressMonitor {
051: private long totalLength;
052:
053: public void init(int op, String src, String dest, long max) {
054: totalLength = max;
055: fireTransferStarted(max);
056: }
057:
058: public void end() {
059: fireTransferCompleted(totalLength);
060: }
061:
062: public boolean count(long count) {
063: fireTransferProgress(count);
064: return true;
065: }
066: }
067:
068: public SFTPRepository() {
069: }
070:
071: public Resource getResource(String source) {
072: return new SFTPResource(this , source);
073: }
074:
075: /**
076: * This method is similar to getResource, except that the returned resource is fully initialised
077: * (resolved in the sftp repository), and that the given string is a full remote path
078: *
079: * @param path
080: * the full remote path in the repository of the resource
081: * @return a fully initialised resource, able to answer to all its methods without needing any
082: * further connection
083: */
084: public Resource resolveResource(String path) {
085: try {
086: ChannelSftp c = getSftpChannel(path);
087: Collection r = c.ls(path);
088: if (r != null) {
089: for (Iterator iter = r.iterator(); iter.hasNext();) {
090: Object obj = iter.next();
091: if (obj instanceof LsEntry) {
092: LsEntry entry = (LsEntry) obj;
093: SftpATTRS attrs = entry.getAttrs();
094: return new BasicResource(path, true, attrs
095: .getSize(), attrs.getMTime() * 1000,
096: false);
097: }
098: }
099: }
100: } catch (Exception e) {
101: Message.debug("reolving resource error: " + e.getMessage());
102: // silent fail, return unexisting resource
103: }
104: return new BasicResource(path, false, 0, 0, false);
105: }
106:
107: public InputStream openStream(SFTPResource resource)
108: throws IOException {
109: ChannelSftp c = getSftpChannel(resource.getName());
110: try {
111: return c.get(resource.getName());
112: } catch (SftpException e) {
113: e.printStackTrace();
114: IOException ex = new IOException(
115: "impossible to open stream for "
116: + resource
117: + " on "
118: + getHost()
119: + (e.getMessage() != null ? ": "
120: + e.getMessage() : ""));
121: ex.initCause(e);
122: throw ex;
123: }
124: }
125:
126: public void get(String source, File destination) throws IOException {
127: fireTransferInitiated(getResource(source),
128: TransferEvent.REQUEST_GET);
129: ChannelSftp c = getSftpChannel(source);
130: try {
131: c.get(source, destination.getAbsolutePath(),
132: new MyProgressMonitor());
133: } catch (SftpException e) {
134: e.printStackTrace();
135: IOException ex = new IOException("impossible to get "
136: + source
137: + " on "
138: + getHost()
139: + (e.getMessage() != null ? ": " + e.getMessage()
140: : ""));
141: ex.initCause(e);
142: throw ex;
143: }
144: }
145:
146: public void put(File source, String destination, boolean overwrite)
147: throws IOException {
148: fireTransferInitiated(getResource(destination),
149: TransferEvent.REQUEST_PUT);
150: ChannelSftp c = getSftpChannel(destination);
151: try {
152: if (!overwrite && checkExistence(destination, c)) {
153: throw new IOException(
154: "destination file exists and overwrite == true");
155: }
156: if (destination.indexOf('/') != -1) {
157: mkdirs(destination.substring(0, destination
158: .lastIndexOf('/')), c);
159: }
160: c.put(source.getAbsolutePath(), destination,
161: new MyProgressMonitor());
162: } catch (SftpException e) {
163: IOException ex = new IOException(e.getMessage());
164: ex.initCause(e);
165: throw ex;
166: }
167: }
168:
169: private void mkdirs(String directory, ChannelSftp c)
170: throws IOException, SftpException {
171: try {
172: SftpATTRS att = c.stat(directory);
173: if (att != null) {
174: if (att.isDir()) {
175: return;
176: }
177: }
178: } catch (SftpException ex) {
179: if (directory.indexOf('/') != -1) {
180: mkdirs(directory.substring(0, directory
181: .lastIndexOf('/')), c);
182: }
183: c.mkdir(directory);
184: }
185: }
186:
187: public List list(String parent) throws IOException {
188: try {
189: ChannelSftp c = getSftpChannel(parent);
190: Collection r = c.ls(parent);
191: if (r != null) {
192: if (!parent.endsWith("/")) {
193: parent = parent + "/";
194: }
195: List result = new ArrayList();
196: for (Iterator iter = r.iterator(); iter.hasNext();) {
197: Object obj = iter.next();
198: if (obj instanceof LsEntry) {
199: LsEntry entry = (LsEntry) obj;
200: if (".".equals(entry.getFilename())
201: || "..".equals(entry.getFilename())) {
202: continue;
203: }
204: result.add(parent + entry.getFilename());
205: }
206: }
207: return result;
208: }
209: } catch (Exception e) {
210: // silent fail, return null listing
211: }
212: return null;
213: }
214:
215: /**
216: * Checks the existence for a remote file
217: *
218: * @param file
219: * to check
220: * @param channel
221: * to use
222: * @returns true if file exists, false otherwise
223: * @throws IOException
224: * @throws SftpException
225: */
226: private boolean checkExistence(String file, ChannelSftp channel)
227: throws IOException, SftpException {
228: try {
229: return channel.stat(file) != null;
230: } catch (SftpException ex) {
231: return false;
232: }
233: }
234:
235: /**
236: * Establish the connection to the server if not yet connected, and listen to ivy events for
237: * closing connection when resolve is finished. Not meant to be used in multi threaded
238: * environment.
239: *
240: * @return the ChannelSftp with which a connection is established
241: * @throws IOException
242: * if any connection problem occurs
243: */
244: private ChannelSftp getSftpChannel(String pathOrUri)
245: throws IOException {
246: Session session = getSession(pathOrUri);
247: String host = session.getHost();
248: ChannelSftp channel = SshCache.getInstance().getChannelSftp(
249: session);
250: if (channel == null) {
251: try {
252: channel = (ChannelSftp) session.openChannel("sftp");
253: channel.connect();
254: Message
255: .verbose(":: SFTP :: connected to " + host
256: + "!");
257: SshCache.getInstance().attachChannelSftp(session,
258: channel);
259: } catch (JSchException e) {
260: IOException ex = new IOException(e.getMessage());
261: ex.initCause(e);
262: throw ex;
263: }
264: }
265: return channel;
266: }
267:
268: protected String getRepositoryScheme() {
269: // use the Resolver type name here?
270: // would be nice if it would be static, so we could use SFTPResolver.getTypeName()
271: return "sftp";
272: }
273: }
|