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 }