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.ssh;
019:
020: import java.io.BufferedReader;
021: import java.io.ByteArrayInputStream;
022: import java.io.ByteArrayOutputStream;
023: import java.io.File;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.io.StringReader;
027: import java.net.URI;
028: import java.net.URISyntaxException;
029: import java.util.ArrayList;
030: import java.util.List;
031:
032: import org.apache.ivy.plugins.repository.Resource;
033: import org.apache.ivy.util.Message;
034:
035: import com.jcraft.jsch.ChannelExec;
036: import com.jcraft.jsch.JSchException;
037: import com.jcraft.jsch.Session;
038:
039: /**
040: * Ivy Repository based on SSH
041: */
042: public class SshRepository extends AbstractSshBasedRepository {
043:
044: private static final int BUFFER_SIZE = 64 * 1024;
045:
046: private static final String ARGUMENT_PLACEHOLDER = "%arg";
047:
048: private static final int POLL_SLEEP_TIME = 500;
049:
050: private char fileSeparator = '/';
051:
052: private String listCommand = "ls -1";
053:
054: private String existCommand = "ls";
055:
056: private String createDirCommand = "mkdir";
057:
058: /**
059: * create a new resource with lazy initializing
060: */
061: public Resource getResource(String source) {
062: Message.debug("SShRepository:getResource called: " + source);
063: return new SshResource(this , source);
064: }
065:
066: /**
067: * Fetch the needed file information for a given file (size, last modification time) and report
068: * it back in a SshResource
069: *
070: * @param source
071: * ssh uri for the file to get info for
072: * @return SshResource filled with the needed informations
073: * @see org.apache.ivy.plugins.repository.Repository#getResource(java.lang.String)
074: */
075: public SshResource resolveResource(String source) {
076: Message
077: .debug("SShRepository:resolveResource called: "
078: + source);
079: SshResource result = null;
080: Session session = null;
081: try {
082: session = getSession(source);
083: Scp myCopy = new Scp(session);
084: Scp.FileInfo fileInfo = myCopy.getFileinfo(new URI(source)
085: .getPath());
086: result = new SshResource(this , source, true, fileInfo
087: .getLength(), fileInfo.getLastModified());
088: } catch (IOException e) {
089: if (session != null) {
090: releaseSession(session, source);
091: }
092: result = new SshResource();
093: } catch (URISyntaxException e) {
094: if (session != null) {
095: releaseSession(session, source);
096: }
097: result = new SshResource();
098: } catch (RemoteScpException e) {
099: result = new SshResource();
100: }
101: Message.debug("SShRepository:resolveResource end.");
102: return result;
103: }
104:
105: /**
106: * Reads out the output of a ssh session exec
107: *
108: * @param channel
109: * Channel to read from
110: * @param strStdout
111: * StringBuffer that receives Session Stdout output
112: * @param strStderr
113: * StringBuffer that receives Session Stderr output
114: * @throws IOException
115: * in case of trouble with the network
116: */
117: private void readSessionOutput(ChannelExec channel,
118: StringBuffer strStdout, StringBuffer strStderr)
119: throws IOException {
120: InputStream stdout = channel.getInputStream();
121: InputStream stderr = channel.getErrStream();
122:
123: try {
124: channel.connect();
125: } catch (JSchException e1) {
126: throw (IOException) new IOException(
127: "Channel connection problems").initCause(e1);
128: }
129:
130: byte[] buffer = new byte[BUFFER_SIZE];
131: while (true) {
132: int avail = 0;
133: while ((avail = stdout.available()) > 0) {
134: int len = stdout
135: .read(buffer, 0,
136: (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE
137: : avail));
138: strStdout.append(new String(buffer, 0, len));
139: }
140: while ((avail = stderr.available()) > 0) {
141: int len = stderr
142: .read(buffer, 0,
143: (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE
144: : avail));
145: strStderr.append(new String(buffer, 0, len));
146: }
147: if (channel.isClosed()) {
148: break;
149: }
150: try {
151: Thread.sleep(POLL_SLEEP_TIME);
152: } catch (Exception ee) {
153: // ignored
154: }
155: }
156: int avail = 0;
157: while ((avail = stdout.available()) > 0) {
158: int len = stdout.read(buffer, 0,
159: (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail));
160: strStdout.append(new String(buffer, 0, len));
161: }
162: while ((avail = stderr.available()) > 0) {
163: int len = stderr.read(buffer, 0,
164: (avail > BUFFER_SIZE - 1 ? BUFFER_SIZE : avail));
165: strStderr.append(new String(buffer, 0, len));
166: }
167: }
168:
169: /*
170: * (non-Javadoc)
171: *
172: * @see org.apache.ivy.repository.Repository#list(java.lang.String)
173: */
174: public List list(String parent) throws IOException {
175: Message.debug("SShRepository:list called: " + parent);
176: ArrayList result = new ArrayList();
177: Session session = null;
178: ChannelExec channel = null;
179: session = getSession(parent);
180: channel = getExecChannel(session);
181: URI parentUri = null;
182: try {
183: parentUri = new URI(parent);
184: } catch (URISyntaxException e1) {
185: // failed earlier
186: }
187: String fullCmd = replaceArgument(listCommand, parentUri
188: .getPath());
189: channel.setCommand(fullCmd);
190: StringBuffer stdOut = new StringBuffer();
191: StringBuffer stdErr = new StringBuffer();
192: readSessionOutput(channel, stdOut, stdErr);
193: if (channel.getExitStatus() != 0) {
194: Message.error("Ssh ListCommand exited with status != 0");
195: Message.error(stdErr.toString());
196: return null;
197: } else {
198: BufferedReader br = new BufferedReader(new StringReader(
199: stdOut.toString()));
200: String line = null;
201: while ((line = br.readLine()) != null) {
202: result.add(line);
203: }
204: }
205: return result;
206: }
207:
208: /**
209: * @param session
210: * @return
211: * @throws JSchException
212: */
213: private ChannelExec getExecChannel(Session session)
214: throws IOException {
215: ChannelExec channel;
216: try {
217: channel = (ChannelExec) session.openChannel("exec");
218: } catch (JSchException e) {
219: throw new IOException();
220: }
221: return channel;
222: }
223:
224: /**
225: * Replace the argument placeholder with argument or append the argument if no placeholder is
226: * present
227: *
228: * @param command
229: * with argument placeholder or not
230: * @param argument
231: * @return replaced full command
232: */
233: private String replaceArgument(String command, String argument) {
234: String fullCmd;
235: if (command.indexOf(ARGUMENT_PLACEHOLDER) == -1) {
236: fullCmd = command + " " + argument;
237: } else {
238: fullCmd = command
239: .replaceAll(ARGUMENT_PLACEHOLDER, argument);
240: }
241: return fullCmd;
242: }
243:
244: /*
245: * (non-Javadoc)
246: *
247: * @see org.apache.ivy.repository.Repository#put(java.io.File, java.lang.String, boolean)
248: */
249: public void put(File source, String destination, boolean overwrite)
250: throws IOException {
251: Message.debug("SShRepository:put called: " + destination);
252: Session session = getSession(destination);
253: try {
254: URI destinationUri = null;
255: try {
256: destinationUri = new URI(destination);
257: } catch (URISyntaxException e) {
258: // failed earlier in getSession()
259: }
260: String filePath = destinationUri.getPath();
261: int lastSep = filePath.lastIndexOf(fileSeparator);
262: String path;
263: String name;
264: if (lastSep == -1) {
265: name = filePath;
266: path = null;
267: } else {
268: name = filePath.substring(lastSep + 1);
269: path = filePath.substring(0, lastSep);
270: }
271: if (!overwrite) {
272: if (checkExistence(filePath, session)) {
273: throw new IOException(
274: "destination file exists and overwrite == true");
275: }
276: }
277: if (path != null) {
278: makePath(path, session);
279: }
280: Scp myCopy = new Scp(session);
281: myCopy.put(source.getCanonicalPath(), path, name);
282: } catch (IOException e) {
283: if (session != null) {
284: releaseSession(session, destination);
285: }
286: throw e;
287: } catch (RemoteScpException e) {
288: throw new IOException(e.getMessage());
289: }
290: }
291:
292: /**
293: * Tries to create a directory path on the target system
294: *
295: * @param path
296: * to create
297: * @param connnection
298: * to use
299: */
300: private void makePath(String path, Session session)
301: throws IOException {
302: ChannelExec channel = null;
303: String trimmed = path;
304: try {
305: while (trimmed.length() > 0
306: && trimmed.charAt(trimmed.length() - 1) == fileSeparator) {
307: trimmed = trimmed.substring(0, trimmed.length() - 1);
308: }
309: if (trimmed.length() == 0
310: || checkExistence(trimmed, session)) {
311: return;
312: }
313: int nextSlash = trimmed.lastIndexOf(fileSeparator);
314: if (nextSlash > 0) {
315: String parent = trimmed.substring(0, nextSlash);
316: makePath(parent, session);
317: }
318: channel = getExecChannel(session);
319: String mkdir = replaceArgument(createDirCommand, trimmed);
320: Message.debug("SShRepository: trying to create path: "
321: + mkdir);
322: channel.setCommand(mkdir);
323: StringBuffer stdOut = new StringBuffer();
324: StringBuffer stdErr = new StringBuffer();
325: readSessionOutput(channel, stdOut, stdErr);
326: } finally {
327: if (channel != null) {
328: channel.disconnect();
329: }
330: }
331: }
332:
333: /**
334: * check for existence of file or dir on target system
335: *
336: * @param filePath
337: * to the object to check
338: * @param session
339: * to use
340: * @return true: object exists, false otherwise
341: */
342: private boolean checkExistence(String filePath, Session session)
343: throws IOException {
344: Message.debug("SShRepository: checkExistence called: "
345: + filePath);
346: ChannelExec channel = null;
347: channel = getExecChannel(session);
348: String fullCmd = replaceArgument(existCommand, filePath);
349: channel.setCommand(fullCmd);
350: StringBuffer stdOut = new StringBuffer();
351: StringBuffer stdErr = new StringBuffer();
352: readSessionOutput(channel, stdOut, stdErr);
353: return channel.getExitStatus() == 0;
354: }
355:
356: /*
357: * (non-Javadoc)
358: *
359: * @see org.apache.ivy.repository.Repository#get(java.lang.String, java.io.File)
360: */
361: public void get(String source, File destination) throws IOException {
362: Message.debug("SShRepository:get called: " + source + " to "
363: + destination.getCanonicalPath());
364: if (destination.getParentFile() != null) {
365: destination.getParentFile().mkdirs();
366: }
367: Session session = getSession(source);
368: try {
369: URI sourceUri = null;
370: try {
371: sourceUri = new URI(source);
372: } catch (URISyntaxException e) {
373: // fails earlier
374: }
375: if (sourceUri == null) {
376: Message.error("could not parse URI " + source);
377: return;
378: }
379: Scp myCopy = new Scp(session);
380: myCopy.get(sourceUri.getPath(), destination
381: .getCanonicalPath());
382: } catch (IOException e) {
383: if (session != null) {
384: releaseSession(session, source);
385: }
386: throw e;
387: } catch (RemoteScpException e) {
388: throw new IOException(e.getMessage());
389: }
390: }
391:
392: /**
393: * sets the list command to use for a directory listing listing must be only the filename and
394: * each filename on a separate line
395: *
396: * @param cmd
397: * to use. default is "ls -1"
398: */
399: public void setListCommand(String cmd) {
400: this .listCommand = cmd.trim();
401: }
402:
403: /**
404: * @return the list command to use
405: */
406: public String getListCommand() {
407: return listCommand;
408: }
409:
410: /**
411: * @return the createDirCommand
412: */
413: public String getCreateDirCommand() {
414: return createDirCommand;
415: }
416:
417: /**
418: * @param createDirCommand
419: * the createDirCommand to set
420: */
421: public void setCreateDirCommand(String createDirCommand) {
422: this .createDirCommand = createDirCommand;
423: }
424:
425: /**
426: * @return the existCommand
427: */
428: public String getExistCommand() {
429: return existCommand;
430: }
431:
432: /**
433: * @param existCommand
434: * the existCommand to set
435: */
436: public void setExistCommand(String existCommand) {
437: this .existCommand = existCommand;
438: }
439:
440: /**
441: * The file separator is the separator to use on the target system On a unix system it is '/',
442: * but I don't know, how this is solved on different ssh implementations. Using the default
443: * might be fine
444: *
445: * @param fileSeparator
446: * The fileSeparator to use. default '/'
447: */
448: public void setFileSeparator(char fileSeparator) {
449: this .fileSeparator = fileSeparator;
450: }
451:
452: /**
453: * return ssh as scheme use the Resolver type name here? would be nice if it would be static, so
454: * we could use SshResolver.getTypeName()
455: */
456: protected String getRepositoryScheme() {
457: return "ssh";
458: }
459:
460: /**
461: * Not really streaming...need to implement a proper streaming approach?
462: *
463: * @param resource
464: * to stream
465: * @return InputStream of the resource data
466: */
467: public InputStream openStream(SshResource resource)
468: throws IOException {
469: Session session = getSession(resource.getName());
470: Scp scp = new Scp(session);
471: ByteArrayOutputStream os = new ByteArrayOutputStream();
472: try {
473: scp.get(resource.getName(), os);
474: } catch (IOException e) {
475: if (session != null) {
476: releaseSession(session, resource.getName());
477: }
478: throw e;
479: } catch (RemoteScpException e) {
480: throw new IOException(e.getMessage());
481: }
482: return new ByteArrayInputStream(os.toByteArray());
483: }
484: }
|