View Javadoc

1   /**
2    * This file Copyright (c) 2009-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia.info/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.groovy.console;
35  
36  import groovy.lang.Binding;
37  import groovy.lang.GroovySystem;
38  import groovy.lang.MissingPropertyException;
39  import info.magnolia.cms.i18n.MessagesUtil;
40  import info.magnolia.cms.security.User;
41  import info.magnolia.cms.util.NodeDataUtil;
42  import info.magnolia.context.Context;
43  import info.magnolia.context.MgnlContext;
44  import info.magnolia.module.admininterface.TemplatedMVCHandler;
45  
46  import java.io.ByteArrayInputStream;
47  import java.io.FileInputStream;
48  import java.io.FileNotFoundException;
49  import java.io.InputStream;
50  import java.io.PrintWriter;
51  import java.io.Serializable;
52  
53  import javax.jcr.RepositoryException;
54  import javax.servlet.http.HttpServletRequest;
55  import javax.servlet.http.HttpServletResponse;
56  
57  import org.apache.commons.io.IOUtils;
58  import org.apache.commons.lang.StringUtils;
59  import org.codehaus.groovy.control.CompilationFailedException;
60  import org.codehaus.groovy.runtime.InvokerInvocationException;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  
65  /**
66   * The server-side implementation of the *nix-like Magnolia Groovy console.
67   * @author fgrilli
68   * @version $Id$
69   */
70  public class MgnlGroovyInteractiveConsole extends TemplatedMVCHandler {
71  
72      public MgnlGroovyInteractiveConsole(String name, HttpServletRequest request, HttpServletResponse response) {
73          super(name, request, response);
74      }
75  
76      private static final Logger log = LoggerFactory.getLogger(MgnlGroovyInteractiveConsole.class);
77  
78      private static final String MSG_BASENAME = "info.magnolia.module.groovy.messages";
79  
80      private static final String BINDING_SESSION_ATTRIBUTE = "info.magnolia.module.groovy.console.binding.session.attribute";
81  
82      private static final String GROOVY_VERSION = GroovySystem.getVersion();
83  
84      public String clean() throws Exception {
85          log.debug("calling command {} and cleaning groovy context", getCommand());
86          request.getSession().setAttribute(BINDING_SESSION_ATTRIBUTE, new SerializableBinding());
87          return VIEW_SHOW;
88      }
89  
90      public String evaluateGroovy() throws Exception {
91          User currentUser = MgnlContext.getUser();
92          if(!isAuthorized(currentUser)){
93              String msg = "User " + currentUser.getName() + " is trying to use the Magnolia Groovy Interactive Console but is not authorized.";
94              log.warn(msg);
95              response.getWriter().println(msg);
96              return StringUtils.EMPTY;
97          }
98          String code = request.getParameter("code");
99          log.debug("calling command {} with code {}", getCommand(), code);
100         response.setContentType("text/xml");
101 
102         PrintWriter out = response.getWriter();
103 
104         if (handleCommand(code, out)) {
105             return StringUtils.EMPTY;
106         }
107 
108         evaluate(new ByteArrayInputStream(code.getBytes()), out);
109 
110         return StringUtils.EMPTY;
111     }
112 
113     public String getGroovyVersion() {
114         return GROOVY_VERSION;
115     }
116 
117     /**
118      * @param code
119      * @param out
120      * @return
121      */
122     private void evaluate(final InputStream is, final PrintWriter out) {
123         Binding binding = (Binding) request.getSession().getAttribute(BINDING_SESSION_ATTRIBUTE);
124         MgnlGroovyConsole console = new MgnlGroovyConsole(binding != null ? binding : new SerializableBinding());
125 
126         Object lastResult = null;
127         Context originalCtx = MgnlContext.getInstance();
128         MgnlGroovyConsoleContext groovyCtx = new MgnlGroovyConsoleContext(originalCtx);
129         MgnlContext.setInstance(groovyCtx);
130         try {
131             lastResult = console.evaluate(is, console.generateScriptName(), out);
132             // Shows the result of the evaluated code
133             out.print("===> \n");
134             out.println(lastResult);
135         }
136         catch (CompilationFailedException e) {
137             out.println(e);
138         }
139         catch (Throwable e) {
140             // Unroll invoker exceptions
141             if (e instanceof InvokerInvocationException) {
142                 e = e.getCause();
143             }
144             if(e instanceof MissingPropertyException){
145                 out.println(e.getMessage());
146             } else {
147                 log.error("Error while evaluating script: ", e);
148                 out.println(e.getClass().getSimpleName() + ": " +e.getMessage());
149             }
150         } finally {
151             MgnlContext.setInstance(originalCtx);
152         }
153     }
154 
155     // need this otherwise adding Binding to the session fails with:
156     // java.lang.IllegalArgumentException: setAttribute: Non-serializable attribute
157     // at org.apache.catalina.session.StandardSession.setAttribute(..)
158     private static final class SerializableBinding extends Binding implements Serializable {
159 
160         public static final long serialVersionUID = 42L;
161     }
162 
163     /**
164      * @param command
165      * @param out
166      * @return <code>true</code> if the command could be processed, <code>false</code> otherwise
167      * @throws RepositoryException
168      */
169     private boolean handleCommand(final String command, final PrintWriter out) throws RepositoryException {
170         if (StringUtils.isEmpty(command)) {
171             // just ignore it
172             return true;
173         }
174         String[] tokens = command.trim().split("\\s+");
175         if ("help".equals(tokens[0]) || "?".equals(tokens[0])) {
176             out.println(MessagesUtil.get("console.help", MSG_BASENAME));
177             return true;
178         }
179         else if ("clear".equals(tokens[0])) {
180             return true;
181         }
182         else if ("clean".equals(tokens[0])) {
183             request.getSession().setAttribute(BINDING_SESSION_ATTRIBUTE, new SerializableBinding());
184             return true;
185         }
186         /*
187          * TODO should we use something like apache
188          * commons cli to parse commands and arguments?
189          */
190         else if ("run".equals(tokens[0])) {
191             if (tokens.length != 2) {
192                 out.println(MessagesUtil.get("console.commands.run.usage", MSG_BASENAME));
193                 return true;
194             }
195             InputStream is = null;
196             String path = tokens[1];
197             // first try to locate the script in the scripts workspace
198             String source = NodeDataUtil.getString("scripts", path.endsWith("/") ? path + "text" : path + "/text");
199             try {
200                 if (StringUtils.isNotEmpty(source)) {
201                     is = new ByteArrayInputStream(source.getBytes());
202                 }
203                 else {
204                     // if not found in the workspace, try with the file system
205                     is = new FileInputStream(path);
206                 }
207                 evaluate(is, out);
208                 return true;
209             }
210             catch (FileNotFoundException e) {
211                 out.println(e.getMessage());
212                 return true;
213             }
214             finally {
215                 IOUtils.closeQuietly(is);
216             }
217         }
218         /*else if ("cd".equals(tokens[0])) {
219             if (tokens.length != 2) {
220                 out.println("Please, specify valid a path in the repository.");
221                 return true;
222             }
223             Binding binding = (Binding) request.getSession().getAttribute(BINDING_SESSION_ATTRIBUTE);
224             Object hm;
225             try {
226                 hm = binding.getVariable("hm");
227             }
228             catch (MissingPropertyException e) {
229                 hm = MgnlContext.getHierarchyManager("website");
230                 binding.setVariable("hm", hm);
231             }
232 
233             MgnlGroovyNode node = MgnlGroovySupport.getContent((HierarchyManager) hm, tokens[1]);
234             binding.setVariable("pwd", node);
235             request.getSession().setAttribute(BINDING_SESSION_ATTRIBUTE, binding);
236             return true;
237         }
238         else if ("cdtab".equals(tokens[0])) {
239             Binding binding = (Binding) request.getSession().getAttribute(BINDING_SESSION_ATTRIBUTE);
240             Object hm;
241             try {
242                 hm = binding.getVariable("hm");
243             }
244             catch (MissingPropertyException e) {
245                 hm = MgnlContext.getHierarchyManager("website");
246                 binding.setVariable("hm", hm);
247             }
248             Object pwd;
249             try {
250                 pwd = binding.getVariable("pwd");
251             }
252             catch (MissingPropertyException e) {
253                 pwd = MgnlGroovySupport.getContent((HierarchyManager) hm, "/");
254                 binding.setVariable("pwd", pwd);
255             }
256             out.println(ContentUtil.collectAllChildren((MgnlGroovyNode) pwd, ItemType.CONTENT));
257             return true;
258         }*/
259         return false;
260     }
261 
262     private boolean isAuthorized(User currentUser) {
263         return currentUser.hasRole("superuser") || currentUser.hasRole("scripter");
264     }
265 }