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 }