001/*
002 * Copyright (C) 2012 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 */
019package org.crsh.jcr;
020
021import org.apache.sshd.server.Environment;
022import org.crsh.ssh.term.AbstractCommand;
023import org.crsh.ssh.term.SSHLifeCycle;
024
025import javax.jcr.Credentials;
026import javax.jcr.Repository;
027import javax.jcr.Session;
028import javax.jcr.SimpleCredentials;
029import java.io.ByteArrayOutputStream;
030import java.io.IOException;
031import java.io.InputStream;
032import java.util.HashMap;
033import java.util.Map;
034import java.util.logging.Level;
035import java.util.logging.Logger;
036
037public abstract class SCPCommand extends AbstractCommand implements Runnable {
038
039  /** . */
040  protected final Logger log = Logger.getLogger(getClass().getName());
041
042  /** . */
043  protected static final int OK = 0;
044
045  /** . */
046  protected static final int ERROR = 2;
047
048  /** . */
049  private Thread thread;
050
051  /** . */
052  private Environment env;
053
054  /** . */
055  private final String target;
056
057  protected SCPCommand(String target) {
058    this.target = target;
059  }
060
061  /**
062   * Read from the input stream an exact amount of bytes.
063   *
064   * @param length the expected data length to read
065   * @return an input stream for reading
066   * @throws IOException any io exception
067   */
068  final InputStream read(final int length) throws IOException {
069    log.log(Level.FINE, "Returning stream for length " + length);
070    return new InputStream() {
071
072      /** How many we've read so far. */
073      int count = 0;
074
075      @Override
076      public int read() throws IOException {
077        if (count < length) {
078          int value = in.read();
079          if (value == -1) {
080            throw new IOException("Abnormal end of stream reached");
081          }
082          count++;
083          return value;
084        } else {
085          return -1;
086        }
087      }
088    };
089  }
090
091  final protected void ack() throws IOException {
092      out.write(0);
093      out.flush();
094  }
095
096  final protected void readAck() throws IOException {
097    int c = in.read();
098    switch (c) {
099      case 0:
100        break;
101      case 1:
102        log.log(Level.FINE, "Received warning: " + readLine());
103        break;
104      case 2:
105        throw new IOException("Received nack: " + readLine());
106    }
107  }
108
109  final protected String readLine() throws IOException {
110    ByteArrayOutputStream baos = new ByteArrayOutputStream();
111    while (true) {
112      int c = in.read();
113      if (c == '\n') {
114        return baos.toString();
115      }
116      else if (c == -1) {
117        throw new IOException("End of stream");
118      }
119      else {
120        baos.write(c);
121      }
122    }
123  }
124
125  final public void start(Environment env) throws IOException {
126    this.env = env;
127
128    //
129    thread = new Thread(this, "CRaSH");
130    thread.start();
131  }
132
133  final public void destroy() {
134    thread.interrupt();
135  }
136
137  final public void run() {
138    int exitStatus = OK;
139    String exitMsg = null;
140
141    //
142    try {
143      execute();
144    }
145    catch (Exception e) {
146      log.log(Level.SEVERE, "Error during command execution", e);
147      exitMsg = e.getMessage();
148      exitStatus = ERROR;
149    }
150    finally {
151      // Say we are done
152      if (callback != null) {
153        callback.onExit(exitStatus, exitMsg);
154      }
155    }
156  }
157
158  private void execute() throws Exception {
159    Map<String, String> properties = new HashMap<String, String>();
160
161    // Need portal container name ?
162    int pos1 = target.indexOf(':');
163    String path;
164    String workspaceName;
165    if (pos1 != -1) {
166      int pos2 = target.indexOf(':', pos1 + 1);
167      if (pos2 != -1) {
168        // container:workspace_name:path
169        properties.put("container", target.substring(0, pos1));
170        workspaceName = target.substring(pos1 + 1, pos2);
171        path = target.substring(pos2 + 1);
172      }
173      else {
174        // workspace_name:path
175        workspaceName = target.substring(0, pos1);
176        path = target.substring(pos1 + 1);
177      }
178    }
179    else {
180      workspaceName = null;
181      path = target;
182    }
183
184    //
185    Repository repository = JCRPlugin.findRepository(properties);
186
187    // Obtain credentials from SSH
188    String userName = session.getAttribute(SSHLifeCycle.USERNAME);
189    String password = session.getAttribute(SSHLifeCycle.PASSWORD);
190    Credentials credentials = new SimpleCredentials(userName, password.toCharArray());
191
192    //
193    Session session;
194    if (workspaceName != null) {
195      session = repository.login(credentials, workspaceName);
196    }
197    else {
198      session = repository.login(credentials);
199    }
200
201    //
202    try {
203      execute(session, path);
204    }
205    finally {
206      session.logout();
207    }
208  }
209
210  protected abstract void execute(Session session, String path) throws Exception;
211
212}