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     */
019    
020    package org.crsh.shell.impl.command;
021    
022    import org.crsh.Pipe;
023    import org.crsh.command.CommandInvoker;
024    import org.crsh.command.NoSuchCommandException;
025    import org.crsh.command.PipeCommand;
026    import org.crsh.command.ScriptException;
027    import org.crsh.command.ShellCommand;
028    import org.crsh.shell.ErrorType;
029    import org.crsh.shell.ShellResponse;
030    import org.crsh.shell.ShellProcessContext;
031    import org.crsh.text.Chunk;
032    import org.crsh.text.ChunkAdapter;
033    import org.crsh.text.ChunkBuffer;
034    import org.crsh.text.RenderingContext;
035    
036    import java.io.IOException;
037    import java.util.regex.Pattern;
038    
039    class PipeLine {
040    
041      /** . */
042      final String line;
043    
044      /** . */
045      final String name;
046    
047      /** . */
048      final String rest;
049    
050      /** . */
051      private ShellCommand command;
052    
053      /** . */
054      private CommandInvoker invoker;
055    
056      /** . */
057      final PipeLine next;
058    
059      public String getLine() {
060        return line;
061      }
062    
063      PipeLine(String line, PipeLine next) {
064    
065        Pattern p = Pattern.compile("^\\s*(\\S+)");
066        java.util.regex.Matcher m = p.matcher(line);
067        String name = null;
068        String rest = null;
069        if (m.find()) {
070          name = m.group(1);
071          rest = line.substring(m.end());
072        }
073    
074        //
075        this.name = name;
076        this.rest = rest;
077        this.line = line;
078        this.next = next;
079      }
080    
081      private static class PipeProxy extends PipeCommand {
082    
083        /** . */
084        private final CRaSHSession session;
085    
086        /** . */
087        private final ShellProcessContext context;
088    
089        /** . */
090        private final PipeLine pipeLine;
091    
092        /** . */
093        private Pipe next;
094    
095        /** . */
096        private PipeCommand command;
097    
098        private PipeProxy(CRaSHSession session, ShellProcessContext context, PipeLine pipeLine) {
099          this.session = session;
100          this.context = context;
101          this.pipeLine = pipeLine;
102        }
103    
104        public void open() throws ScriptException {
105          if (pipeLine.next != null) {
106    
107            // Open the next
108            // Try to do some type adaptation
109            if (pipeLine.invoker.getProducedType() == Chunk.class) {
110              if (pipeLine.next.invoker.getConsumedType() == Chunk.class) {
111                PipeProxy proxy = new PipeProxy(session, context, pipeLine.next);
112                proxy.setPiped(true);
113                next = proxy;
114                proxy.open();
115              } else {
116                throw new UnsupportedOperationException("Not supported yet");
117              }
118            } else {
119              if (pipeLine.invoker.getProducedType().isAssignableFrom(pipeLine.next.invoker.getConsumedType())) {
120                PipeProxy proxy = new PipeProxy(session, context, pipeLine.next);
121                proxy.setPiped(true);
122                next = proxy;
123                proxy.open();
124              } else {
125                final PipeProxy proxy = new PipeProxy(session, context, pipeLine.next);
126                proxy.setPiped(true);
127                proxy.open();
128                next = new ChunkAdapter(new RenderingContext() {
129                  public int getWidth() {
130                    return context.getWidth();
131                  }
132                  public void provide(Chunk element) throws IOException {
133                    proxy.provide(element);
134                  }
135                  public void flush() throws IOException {
136                    proxy.flush();
137                  }
138                });
139              }
140            }
141    
142          } else {
143    
144            // We use this chunk buffer to buffer stuff
145            // but also because it optimises the chunks
146            // which provides better perormances on the client
147            final ChunkBuffer buffer = new ChunkBuffer(context);
148    
149            //
150            next = new ChunkAdapter(new RenderingContext() {
151              public int getWidth() {
152                return context.getWidth();
153              }
154              public void provide(Chunk element) throws IOException {
155                buffer.provide(element);
156              }
157              public void flush() throws IOException {
158                buffer.flush();
159              }
160            });
161          }
162    
163          //
164          CRaSHInvocationContext invocationContext = new CRaSHInvocationContext(
165              context,
166              session,
167              session.crash.getContext().getAttributes(),
168              next);
169    
170          // Now open command
171          command = pipeLine.invoker.invoke(invocationContext);
172          command.setPiped(isPiped());
173          command.open();
174        }
175    
176        public void provide(Object element) throws IOException {
177          if (pipeLine.invoker.getConsumedType().isInstance(element)) {
178            command.provide(element);
179          }
180        }
181    
182        public void flush() throws IOException {
183    
184          // First flush the command
185          command.flush();
186    
187          // Flush the next because the command may not call it
188          next.flush();
189        }
190    
191        public void close() throws ScriptException {
192          command.close();
193        }
194      }
195    
196      PipeLine getLast() {
197        if (next != null) {
198          return next.getLast();
199        }
200        return this;
201      }
202    
203       CRaSHProcess create(CRaSHSession session, String request) throws NoSuchCommandException {
204    
205         //
206         CommandInvoker invoker = null;
207         if (name != null) {
208           command = session.crash.getCommand(name);
209           if (command != null) {
210             invoker = command.resolveInvoker(rest);
211           }
212         }
213    
214         //
215         if (invoker == null) {
216           throw new NoSuchCommandException(name);
217         } else {
218           this.invoker = invoker;
219         }
220    
221         //
222         if (next != null) {
223          next.create(session, request);
224        }
225        return new CRaSHProcess(session, request) {
226          @Override
227          ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
228    
229            PipeProxy proxy = new PipeProxy(crash, context, PipeLine.this);
230    
231            try {
232              proxy.open();
233              proxy.flush();
234              proxy.close();
235            }
236            catch (ScriptException e) {
237              // Should we handle InterruptedException here ?
238              return build(e);
239            } catch (Throwable t) {
240              return build(t);
241            }
242            return ShellResponse.ok();
243          }
244        };
245      }
246    
247      private ShellResponse.Error build(Throwable throwable) {
248        ErrorType errorType;
249        if (throwable instanceof ScriptException) {
250          errorType = ErrorType.EVALUATION;
251          Throwable cause = throwable.getCause();
252          if (cause != null) {
253            throwable = cause;
254          }
255        } else {
256          errorType = ErrorType.INTERNAL;
257        }
258        String result;
259        String msg = throwable.getMessage();
260        if (throwable instanceof ScriptException) {
261          if (msg == null) {
262            result = name + ": failed";
263          } else {
264            result = name + ": " + msg;
265          }
266          return ShellResponse.error(errorType, result, throwable);
267        } else {
268          if (msg == null) {
269            msg = throwable.getClass().getSimpleName();
270          }
271          if (throwable instanceof RuntimeException) {
272            result = name + ": exception: " + msg;
273          } else if (throwable instanceof Exception) {
274            result = name + ": exception: " + msg;
275          } else if (throwable instanceof java.lang.Error) {
276            result = name + ": error: " + msg;
277          } else {
278            result = name + ": unexpected throwable: " + msg;
279          }
280          return ShellResponse.error(errorType, result, throwable);
281        }
282      }
283    }