View Javadoc

1   /**
2    * Copyright (c) 2002-2011 "Neo Technology,"
3    * Network Engine for Objects in Lund AB [http://neotechnology.com]
4    *
5    * This file is part of Neo4j.
6    *
7    * Neo4j is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation, either version 3 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.neo4j.server.rest.domain;
21  
22  import java.util.Map;
23  
24  import javax.script.Compilable;
25  import javax.script.CompiledScript;
26  import javax.script.ScriptContext;
27  import javax.script.ScriptEngine;
28  import javax.script.ScriptEngineManager;
29  import javax.script.ScriptException;
30  import javax.script.SimpleScriptContext;
31  
32  import org.neo4j.graphdb.Path;
33  import org.neo4j.graphdb.traversal.PruneEvaluator;
34  import org.neo4j.helpers.Predicate;
35  import org.neo4j.kernel.Traversal;
36  
37  /**
38   * This factory can instantiate or get {@link PruneEvaluator}s and
39   * {@link ReturnFilter}s from a description. Either it returns built-in
40   * evaluators, or instantiates wrappers around user-supplied scripts,
41   * f.ex. javascript.
42   */
43  abstract class EvaluatorFactory
44  {
45      private static final String BUILTIN = "builtin";
46      private static final String KEY_LANGUAGE = "language";
47      private static final String KEY_BODY = "body";
48      private static final String KEY_NAME = "name";
49  
50      public static PruneEvaluator pruneEvaluator( Map<String, Object> description )
51      {
52          if ( refersToBuiltInEvaluator( description ) )
53          {
54              return builtInPruneEvaluator( description );
55          }
56          else
57          {
58              return new ScriptedPruneEvaluator( scriptEngine( description ),
59                      (String) description.get( KEY_BODY ) );
60          }
61      }
62  
63      public static Predicate<Path> returnFilter( Map<String, Object> description )
64      {
65          if ( refersToBuiltInEvaluator( description ) )
66          {
67              return builtInReturnFilter( description );
68          }
69          else
70          {
71              return new ScriptedReturnEvaluator( scriptEngine( description ),
72                      (String) description.get( KEY_BODY ) );
73          }
74      }
75  
76      private static boolean refersToBuiltInEvaluator( Map<String, Object> description )
77      {
78          String language = (String) description.get( KEY_LANGUAGE );
79          return language.equals( BUILTIN );
80      }
81  
82      private static PruneEvaluator builtInPruneEvaluator(
83              Map<String, Object> description )
84      {
85          String name = (String) description.get( KEY_NAME );
86          // FIXME I don't like these hardcoded strings
87          if ( name.equalsIgnoreCase( "none" ) )
88          {
89              return PruneEvaluator.NONE;
90          }
91          else
92          {
93              throw new EvaluationException( "Unrecognized prune evaluator name '" + name + "'" );
94          }
95      }
96  
97      private static Predicate<Path> builtInReturnFilter( Map<String, Object> description )
98      {
99          String name = (String) description.get( KEY_NAME );
100         // FIXME I don't like these hardcoded strings
101         if ( name.equalsIgnoreCase( "all" ) )
102         {
103             return Traversal.returnAll();
104         }
105         else if ( name.equalsIgnoreCase( "all but start node" ) )
106         {
107             return Traversal.returnAllButStartNode();
108         }
109         else
110         {
111             throw new EvaluationException( "Unrecognized return evaluator name '" + name + "'" );
112         }
113     }
114 
115     private static ScriptEngine scriptEngine( Map<String, Object> description )
116     {
117         String language = (String) description.get( KEY_LANGUAGE );
118         ScriptEngine engine = new ScriptEngineManager().getEngineByName( language );
119         if ( engine == null )
120         {
121             throw new EvaluationException( "Unknown script language '" + language + "'" );
122         }
123         return engine;
124     }
125 
126     /**
127      * An abstract for {@link ScriptEngine} and {@link CompiledScript}. They
128      * have no common interface... other than this one.
129      */
130     private static abstract class ScriptExecutor
131     {
132         abstract Object eval( Path position );
133     }
134 
135     private static class EvalScriptExecutor extends ScriptExecutor
136     {
137         private final ScriptEngine script;
138         private final String body;
139 
140         EvalScriptExecutor( ScriptEngine script, String body )
141         {
142             this.script = script;
143             this.body = body;
144         }
145 
146         @Override
147         Object eval( Path position )
148         {
149             try
150             {
151                 this.script.getContext().setAttribute( "position", position,
152                         ScriptContext.ENGINE_SCOPE );
153                 return this.script.eval( body );
154             }
155             catch ( ScriptException e )
156             {
157                 throw new EvaluationException( e );
158             }
159         }
160     }
161 
162     private static class CompiledScriptExecutor extends ScriptExecutor
163     {
164         private final CompiledScript script;
165         private final ScriptContext context;
166 
167         CompiledScriptExecutor( CompiledScript script, ScriptContext context )
168         {
169             this.script = script;
170             this.context = context;
171         }
172 
173         @Override
174         Object eval( Path position )
175         {
176             try
177             {
178                 this.context.setAttribute( "position", position, ScriptContext.ENGINE_SCOPE );
179                 return this.script.eval( this.context );
180             }
181             catch ( ScriptException e )
182             {
183                 throw new EvaluationException( e );
184             }
185         }
186     }
187 
188     private static abstract class ScriptedEvaluator
189     {
190         private final ScriptEngine engine;
191         private final String body;
192         private ScriptExecutor executor;
193 
194         ScriptedEvaluator( ScriptEngine engine, String body )
195         {
196             this.engine = engine;
197             this.body = body;
198         }
199 
200         protected ScriptExecutor executor( Path position )
201         {
202             // We'll have to decide between evaluated script or compiled script
203             // the first time we execute it, else the compiled script can't be
204             // compiled (since position must be a valid object).
205             if ( this.executor == null )
206             {
207                 try
208                 {
209                     ScriptContext context = new SimpleScriptContext();
210                     context.setAttribute( "position", position, ScriptContext.ENGINE_SCOPE );
211                     this.engine.setContext( context );
212                     if ( this.engine instanceof Compilable )
213                     {
214                         this.executor = new CompiledScriptExecutor(
215                                 ((Compilable) engine).compile( body ), context );
216                     }
217                     else
218                     {
219                         this.executor = new EvalScriptExecutor( engine, body );
220                     }
221                     return executor;
222                 }
223                 catch ( ScriptException e )
224                 {
225                     throw new EvaluationException( e );
226                 }
227             }
228             return this.executor;
229         }
230     }
231 
232     private static class ScriptedPruneEvaluator extends ScriptedEvaluator implements PruneEvaluator
233     {
234         ScriptedPruneEvaluator( ScriptEngine engine, String body )
235         {
236             super( engine, body );
237         }
238 
239         public boolean pruneAfter( Path position )
240         {
241             return (Boolean) executor( position ).eval( position );
242         }
243     }
244 
245     private static class ScriptedReturnEvaluator extends ScriptedEvaluator implements
246             Predicate<Path>
247     {
248         ScriptedReturnEvaluator( ScriptEngine engine, String body )
249         {
250             super( engine, body );
251         }
252 
253         public boolean accept( Path position )
254         {
255             return (Boolean) this.executor( position ).eval( position );
256         }
257     }
258 }