001 /*
002 * Copyright (C) 2003-2009 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019 package org.crsh.jcr;
020
021 import org.apache.sshd.server.Environment;
022 import org.crsh.ssh.term.AbstractCommand;
023 import org.crsh.ssh.term.SSHLifeCycle;
024 import org.slf4j.Logger;
025 import org.slf4j.LoggerFactory;
026
027 import javax.jcr.Credentials;
028 import javax.jcr.Repository;
029 import javax.jcr.Session;
030 import javax.jcr.SimpleCredentials;
031 import java.io.ByteArrayOutputStream;
032 import java.io.IOException;
033 import java.io.InputStream;
034 import java.util.HashMap;
035 import java.util.Map;
036
037 /**
038 *
039 * Three internal options in SCP:
040 * <ul>
041 * <li><code>-f</code> (from) indicates source mode</li>
042 * <li><code>-t</code> (to) indicates sink mode</li>
043 * <li><code>-d</code> indicates that the target is expected to be a directory</li>
044 * </ul>
045 *
046 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
047 * @version $Revision$
048 */
049 public abstract class SCPCommand extends AbstractCommand implements Runnable {
050
051 /** . */
052 protected final Logger log = LoggerFactory.getLogger(getClass());
053
054 /** . */
055 protected static final int OK = 0;
056
057 /** . */
058 protected static final int ERROR = 2;
059
060 /** . */
061 private Thread thread;
062
063 /** . */
064 private Environment env;
065
066 /** . */
067 private final String target;
068
069 protected SCPCommand(String target) {
070 this.target = target;
071 }
072
073 /**
074 * Read from the input stream an exact amount of bytes.
075 *
076 * @param length the expected data length to read
077 * @return an input stream for reading
078 * @throws IOException any io exception
079 */
080 final InputStream read(final int length) throws IOException {
081 log.debug("Returning stream for length " + length);
082 return new InputStream() {
083
084 /** How many we've read so far. */
085 int count = 0;
086
087 @Override
088 public int read() throws IOException {
089 if (count < length) {
090 int value = in.read();
091 if (value == -1) {
092 throw new IOException("Abnormal end of stream reached");
093 }
094 count++;
095 return value;
096 } else {
097 return -1;
098 }
099 }
100 };
101 }
102
103 final protected void ack() throws IOException {
104 out.write(0);
105 out.flush();
106 }
107
108 final protected void readAck() throws IOException {
109 int c = in.read();
110 switch (c) {
111 case 0:
112 break;
113 case 1:
114 log.debug("Received warning: " + readLine());
115 break;
116 case 2:
117 throw new IOException("Received nack: " + readLine());
118 }
119 }
120
121 final protected String readLine() throws IOException {
122 ByteArrayOutputStream baos = new ByteArrayOutputStream();
123 while (true) {
124 int c = in.read();
125 if (c == '\n') {
126 return baos.toString();
127 }
128 else if (c == -1) {
129 throw new IOException("End of stream");
130 }
131 else {
132 baos.write(c);
133 }
134 }
135 }
136
137 final public void start(Environment env) throws IOException {
138 this.env = env;
139
140 //
141 thread = new Thread(this, "CRaSH");
142 thread.start();
143 }
144
145 final public void destroy() {
146 thread.interrupt();
147 }
148
149 final public void run() {
150 int exitStatus = OK;
151 String exitMsg = null;
152
153 //
154 try {
155 execute();
156 }
157 catch (Exception e) {
158 log.error("Error during command execution", e);
159 exitMsg = e.getMessage();
160 exitStatus = ERROR;
161 }
162 finally {
163 // Say we are done
164 if (callback != null) {
165 callback.onExit(exitStatus, exitMsg);
166 }
167 }
168 }
169
170 private void execute() throws Exception {
171 Map<String, String> properties = new HashMap<String, String>();
172
173 // Need portal container name ?
174 int pos1 = target.indexOf(':');
175 String path;
176 String workspaceName;
177 if (pos1 != -1) {
178 int pos2 = target.indexOf(':', pos1 + 1);
179 if (pos2 != -1) {
180 // container_name:workspace_name:path
181 properties.put("exo.container.name", target.substring(0, pos1));
182 workspaceName = target.substring(pos1 + 1, pos2);
183 path = target.substring(pos2 + 1);
184 }
185 else {
186 // workspace_name:path
187 workspaceName = target.substring(0, pos1);
188 path = target.substring(pos1 + 1);
189 }
190 }
191 else {
192 workspaceName = null;
193 path = target;
194 }
195
196 //
197 Repository repository = JCRPlugin.findRepository(properties);
198
199 // Obtain credentials from SSH
200 String userName = session.getAttribute(SSHLifeCycle.USERNAME);
201 String password = session.getAttribute(SSHLifeCycle.PASSWORD);
202 Credentials credentials = new SimpleCredentials(userName, password.toCharArray());
203
204 //
205 Session session;
206 if (workspaceName != null) {
207 session = repository.login(credentials, workspaceName);
208 }
209 else {
210 session = repository.login(credentials);
211 }
212
213 //
214 try {
215 execute(session, path);
216 }
217 finally {
218 session.logout();
219 }
220 }
221
222 protected abstract void execute(Session session, String path) throws Exception;
223
224 }