class ScriptedProcessor extends Processor 
implements ApplicationContextAware, InitializingBean {

    @SuppressWarnings("unused")
    private static final long serialVersionUID = 3L;

    private static final Logger logger =
        Logger.getLogger(ScriptedProcessor.class.getName());

    /** engine name; default "beanshell" */
    protected String engineName = "beanshell";
    public String getEngineName() {
        return this.engineName;
    }
    public void setEngineName(String name) {
        this.engineName = name;
    }
    
    protected ReadSource scriptSource = null;
    public ReadSource getScriptSource() {
        return this.scriptSource;
    }
    @Required
    public void setScriptSource(ReadSource source) {
        this.scriptSource = source; 
    }

    /**
     * Whether each ToeThread should get its own independent script 
     * engine, or they should share synchronized access to one 
     * engine. Default is true, meaning each thread gets its own 
     * isolated engine.
     */
    protected boolean isolateThreads = true; 
    public boolean getIsolateThreads() {
        return isolateThreads;
    }
    public void setIsolateThreads(boolean isolateThreads) {
        this.isolateThreads = isolateThreads;
    }

    protected ApplicationContext appCtx;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.appCtx = applicationContext;
    }
    
    transient protected ThreadLocal<ScriptEngine> threadEngine = 
        new ThreadLocal<ScriptEngine>();
    protected ScriptEngine sharedEngine;

    /**
     * Constructor.
     */
    public ScriptedProcessor() {
        super();
    }

    public void afterPropertiesSet() throws Exception {
        // fail at build-time if script engine not available
        if(null == new ScriptEngineManager().getEngineByName(engineName)) {
            throw new BeanInitializationException("named ScriptEngine not available");
        }
    }
    protected boolean shouldProcess(CrawlURI curi) {
        return true;
    }
    
    @Override
    protected void innerProcess(CrawlURI curi) {<FILL_FUNCTION_BODY>}

    /**
     * Get the proper ScriptEngine instance -- either shared or local 
     * to this thread. 
     * @return ScriptEngine to use
     */
    protected synchronized ScriptEngine getEngine() {
        if (getIsolateThreads()) {
            if (threadEngine.get() == null) {
                threadEngine.set(newEngine());
            }
            return threadEngine.get();
        } else {
            if (sharedEngine == null) {
                sharedEngine = newEngine();
            }
            return sharedEngine;
        }
    }

    /**
     * Create a new {@link ScriptEngine} instance, preloaded with any supplied
     * source file and the variables 'self' (this {@link ScriptedProcessor}) and
     * 'context' (the {@link ApplicationContext}).
     * 
     * @return the new ScriptEngine instance
     */
    protected ScriptEngine newEngine() {
        ScriptEngine interpreter = new ScriptEngineManager().getEngineByName(engineName);

        interpreter.put("self", this);
        interpreter.put("context", appCtx);
        
        Reader reader = null;
        try {
            reader = getScriptSource().obtainReader();
            interpreter.eval(reader);
        } catch (ScriptException e) {
            logger.log(Level.SEVERE,"script problem",e);
        } finally {
            IOUtils.closeQuietly(reader);
        }

        return interpreter; 
    }
}

        // depending on previous configuration, engine may 
        // be local to this thread or shared
        ScriptEngine engine = getEngine(); 
        synchronized(engine) {
            // synchronization is harmless for local thread engine,
            // necessary for shared engine
            engine.put("curi",curi);
            engine.put("appCtx", appCtx);
            try {
                engine.eval("process(curi)");
            } catch (ScriptException e) {
                logger.log(Level.WARNING,e.getMessage(),e);
            } finally { 
                engine.put("curi", null);
                engine.put("appCtx", null);
            }
        }
    
<methods>public non-sealed void <init>() ,public void doCheckpoint(org.archive.checkpointing.Checkpoint) throws java.io.IOException,public void finishCheckpoint(org.archive.checkpointing.Checkpoint) ,public static java.lang.String flattenVia(org.archive.modules.CrawlURI) ,public java.lang.String getBeanName() ,public boolean getEnabled() ,public org.archive.spring.KeyedProperties getKeyedProperties() ,public static long getRecordedSize(org.archive.modules.CrawlURI) ,public org.archive.modules.deciderules.DecideRule getShouldProcessRule() ,public long getURICount() ,public static boolean hasHttpAuthenticationCredential(org.archive.modules.CrawlURI) ,public boolean isRunning() ,public static boolean isSuccess(org.archive.modules.CrawlURI) ,public org.archive.modules.ProcessResult process(org.archive.modules.CrawlURI) throws java.lang.InterruptedException,public java.lang.String report() ,public void setBeanName(java.lang.String) ,public void setEnabled(boolean) ,public void setRecoveryCheckpoint(org.archive.checkpointing.Checkpoint) ,public void setShouldProcessRule(org.archive.modules.deciderules.DecideRule) ,public void start() ,public void startCheckpoint(org.archive.checkpointing.Checkpoint) ,public void stop() <variables>protected java.lang.String beanName,protected boolean isRunning,protected org.archive.spring.KeyedProperties kp,protected org.archive.checkpointing.Checkpoint recoveryCheckpoint,protected java.util.concurrent.atomic.AtomicLong uriCount