View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.jsch;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.StringTokenizer;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.argeo.slc.SlcException;
27  import org.argeo.slc.core.deploy.DigestCheck;
28  import org.argeo.slc.core.deploy.ResourceSet;
29  import org.springframework.core.io.Resource;
30  
31  import com.jcraft.jsch.Session;
32  
33  public class SshFilesDeployment extends AbstractJschTask implements Runnable {
34  	private final static Log log = LogFactory.getLog(SshFilesDeployment.class);
35  	private String targetBase = "";
36  	private ResourceSet resourceSet;
37  	/**
38  	 * Activate with algorithm as per
39  	 * http://java.sun.com/j2se/1.5.0/docs/guide/security/CryptoSpec.html#AppA
40  	 */
41  	private String checksum = "MD5";
42  	private int remoteChecksumsPerCall = 20;
43  
44  	public SshFilesDeployment() {
45  	}
46  
47  	public SshFilesDeployment(SshTarget sshTarget, ResourceSet resourceSet) {
48  		setSshTarget(sshTarget);
49  		this.resourceSet = resourceSet;
50  	}
51  
52  	@Override
53  	void run(Session session) {
54  		JschMultiTasks multiTasks = new JschMultiTasks();
55  
56  		Map<String, Resource> resources = resourceSet.listResources();
57  
58  		// Analyze set
59  		List<String> subDirs = new ArrayList<String>();
60  		Map<String, String> targetPaths = new HashMap<String, String>();
61  		for (String relPath : resources.keySet()) {
62  			String parentDir;
63  			int lastIndexSubDir = relPath.lastIndexOf('/');
64  			if (lastIndexSubDir > 0)
65  				parentDir = targetBase + '/'
66  						+ relPath.substring(0, lastIndexSubDir);
67  			else
68  				parentDir = targetBase;
69  
70  			boolean skipDir = false;
71  			registerDirs: for (String registeredDir : new ArrayList<String>(
72  					subDirs)) {
73  				if (parentDir.equals(registeredDir)) {
74  					if (log.isTraceEnabled())
75  						log.trace("Already registered, skip " + parentDir);
76  					skipDir = true;
77  					break registerDirs;
78  				}
79  
80  				if (parentDir.startsWith(registeredDir))
81  					if (subDirs.contains(registeredDir)) {
82  						subDirs.remove(registeredDir);
83  						if (log.isTraceEnabled())
84  							log.trace("Remove parent " + registeredDir + " of "
85  									+ parentDir);
86  						continue registerDirs;
87  					}
88  
89  				if (registeredDir.startsWith(parentDir)) {
90  					skipDir = true;
91  					if (log.isTraceEnabled())
92  						log.trace("Skip " + parentDir
93  								+ " because child already registered.");
94  					break registerDirs;
95  				}
96  			}
97  
98  			if (!subDirs.contains(parentDir) && !skipDir) {
99  				subDirs.add(parentDir);
100 			}
101 
102 			targetPaths.put(relPath, targetBase + "/" + relPath);
103 		}
104 
105 		// checksum
106 		List<String> targetPathsEqualsToLocal = new ArrayList<String>();
107 		if (checksum != null) {
108 			Map<String, String> remoteChecksums = new HashMap<String, String>();
109 			List<String> csLines = new ArrayList<String>();
110 			String csExecutable;
111 			if ("MD5".equals(checksum))
112 				csExecutable = "/usr/bin/md5sum";
113 			else if ("SHA".equals(checksum))
114 				csExecutable = "/usr/bin/sha1sum";
115 			else if ("SHA-256".equals(checksum))
116 				csExecutable = "/usr/bin/sha256sum";
117 			else if ("SHA-512".equals(checksum))
118 				csExecutable = "/usr/bin/sha512sum";
119 			else
120 				throw new SlcException(
121 						"Don't know how to remotely execute checksum "
122 								+ checksum);
123 
124 			StringBuffer csCmd = new StringBuffer(csExecutable);
125 			int numberOfPaths = targetPaths.size();
126 			int count = 0;
127 			for (String targetPath : targetPaths.values()) {
128 				csCmd.append(" ").append(targetPath);
129 				count++;
130 
131 				if ((count % remoteChecksumsPerCall == 0)
132 						|| count == numberOfPaths) {
133 					RemoteExec remoteCs = new RemoteExec();
134 					remoteCs.setSshTarget(getSshTarget());
135 					remoteCs.setCommand(csCmd.toString());
136 					remoteCs.setStdOutLines(csLines);
137 					remoteCs.setFailOnBadExitStatus(false);
138 					remoteCs.run(session);
139 					csCmd = new StringBuffer(csExecutable);
140 				}
141 
142 			}
143 
144 			remoteChecksums: for (String csLine : csLines) {
145 				StringTokenizer st = new StringTokenizer(csLine, ": ");
146 				String cs = st.nextToken();
147 				if (cs.equals(csExecutable)) {
148 					// remote does not exist
149 					continue remoteChecksums;
150 				} else {
151 					String targetPath = st.nextToken();
152 					if (log.isTraceEnabled())
153 						log.trace("REMOTE: " + targetPath + "=" + cs);
154 					remoteChecksums.put(targetPath, cs);
155 				}
156 			}
157 
158 			// Local checksums
159 			for (String relPath : resources.keySet()) {
160 				Resource resource = resources.get(relPath);
161 				String targetPath = targetPaths.get(relPath);
162 				if (remoteChecksums.containsKey(targetPath)) {
163 					String cs = DigestCheck.digest(checksum, resource);
164 					if (log.isTraceEnabled())
165 						log.trace("LOCAL : " + targetPath + "=" + cs);
166 					if (remoteChecksums.get(targetPath).equals(cs))
167 						targetPathsEqualsToLocal.add(targetPath);
168 				}
169 			}
170 		}
171 
172 		// Prepare multitask
173 
174 		// Create dirs
175 		StringBuffer mkdirCmd = new StringBuffer("mkdir -p");
176 		RemoteExec remoteExec = new RemoteExec();
177 		for (String dir : subDirs) {
178 			// remoteExec.getCommands().add("mkdir -p " + dir);
179 			mkdirCmd.append(' ');
180 			if (dir.indexOf(' ') >= 0)
181 				mkdirCmd.append('\"').append(dir).append('\"');
182 			else
183 				mkdirCmd.append(dir);
184 		}
185 		remoteExec.setCommand(mkdirCmd.toString());
186 		multiTasks.getTasks().add(remoteExec);
187 
188 		// Perform copies
189 		int copied = 0;
190 		int skipped = 0;
191 		copy: for (String relPath : resources.keySet()) {
192 			String targetPath = targetPaths.get(relPath);
193 			if (targetPathsEqualsToLocal.contains(targetPath)) {
194 				if (log.isTraceEnabled())
195 					log.trace("Skip copy of " + relPath
196 							+ " since it is equal to remote " + targetPath);
197 				skipped++;
198 				continue copy;
199 			}
200 			// Copy resource
201 			Resource resource = resources.get(relPath);
202 			ScpTo scpTo = new ScpTo();
203 			scpTo.setLocalResource(resource);
204 			scpTo.setRemotePath(targetPath);
205 			multiTasks.getTasks().add(scpTo);
206 			copied++;
207 			// TODO: set permissions
208 		}
209 
210 		multiTasks.setSshTarget(getSshTarget());
211 		multiTasks.run(session);
212 
213 		if (checksum != null && log.isDebugEnabled())
214 			log.debug("Copied " + copied + " files, skipped " + skipped
215 					+ " with same checksum.");
216 	}
217 
218 	public void setTargetBase(String targetBase) {
219 		this.targetBase = targetBase;
220 	}
221 
222 	public void setResourceSet(ResourceSet resourceSet) {
223 		this.resourceSet = resourceSet;
224 	}
225 
226 	public void setChecksum(String checksum) {
227 		this.checksum = checksum;
228 	}
229 
230 	/** Number of remote checksums per remote call */
231 	public void setRemoteChecksumsPerCall(int remoteChecksumsPerCall) {
232 		this.remoteChecksumsPerCall = remoteChecksumsPerCall;
233 	}
234 }