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:workspace_name:path
181            properties.put("container", 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    }