001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.env.groovy;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.ObjectInputStream;
023import java.io.ObjectOutputStream;
024import java.io.ObjectStreamClass;
025import java.util.Iterator;
026import java.util.Map;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.apache.commons.scxml2.Context;
031import org.apache.commons.scxml2.env.SimpleContext;
032
033import groovy.lang.Closure;
034
035/**
036 * Groovy Context implementation for Commons SCXML.
037 */
038public class GroovyContext extends SimpleContext {
039
040    private static final long serialVersionUID = 1L;
041
042    private static final Log log = LogFactory.getLog(GroovyContext.class);
043
044    private String scriptBaseClass;
045    private GroovyEvaluator evaluator;
046    private GroovyContextBinding binding;
047    private Map<String, Object> vars;
048
049    GroovyContextBinding getBinding() {
050        if (binding == null) {
051            binding = new GroovyContextBinding(this);
052        }
053        return  binding;
054    }
055
056    /**
057     * Constructor.
058     */
059    public GroovyContext() {
060        super();
061    }
062
063    /**
064     * Constructor with initial vars.
065     *
066     * @param initialVars The initial set of variables.
067     */
068    public GroovyContext(final Context parent, final Map<String, Object> initialVars, GroovyEvaluator evaluator) {
069        super(parent, initialVars);
070        this.evaluator = evaluator;
071    }
072
073    /**
074     * Constructor with parent context.
075     *
076     * @param parent The parent context.
077     */
078    public GroovyContext(final Context parent, GroovyEvaluator evaluator) {
079        super(parent);
080        this.evaluator = evaluator;
081    }
082
083    protected GroovyEvaluator getGroovyEvaluator() {
084        return evaluator;
085    }
086
087    protected void setGroovyEvaluator(GroovyEvaluator evaluator) {
088        this.evaluator = evaluator;
089    }
090
091    @Override
092    public Map<String, Object> getVars() {
093        return vars;
094    }
095
096    @Override
097    protected void setVars(final Map<String, Object> vars) {
098        this.vars = vars;
099    }
100
101    protected void setScriptBaseClass(String scriptBaseClass) {
102        this.scriptBaseClass = scriptBaseClass;
103    }
104
105    protected String getScriptBaseClass() {
106        if (scriptBaseClass != null) {
107            return scriptBaseClass;
108        }
109        if (getParent() instanceof GroovyContext) {
110            return ((GroovyContext)getParent()).getScriptBaseClass();
111        }
112        return null;
113    }
114
115    private void writeObject(ObjectOutputStream out) throws IOException {
116        boolean closureErased = false;
117        if (vars != null) {
118            Iterator<Map.Entry<String, Object>> iterator = getVars().entrySet().iterator();
119            while (iterator.hasNext()) {
120                Map.Entry<String, Object> entry = iterator.next();
121                if (entry.getValue() != null && entry.getValue() instanceof Closure) {
122                    iterator.remove();
123                    closureErased = true;
124                }
125            }
126            if (closureErased) {
127                log.warn("Encountered and removed Groovy Closure(s) in the GroovyContext during serialization: these are not supported for (de)serialization");
128            }
129        }
130        out.writeObject(this.scriptBaseClass);
131        out.writeObject(this.evaluator);
132        out.writeObject(this.binding);
133        ByteArrayOutputStream bout = new ByteArrayOutputStream();
134        new ObjectOutputStream(bout).writeObject(this.vars);
135        out.writeObject(bout.toByteArray());
136    }
137
138    @SuppressWarnings("unchecked")
139    private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
140        this.scriptBaseClass = (String)in.readObject();
141        this.evaluator = (GroovyEvaluator)in.readObject();
142        this.binding = (GroovyContextBinding)in.readObject();
143        byte[] bytes  = (byte[])in.readObject();
144        if (evaluator != null) {
145            this.vars = (Map<String, Object>)
146                    new ObjectInputStream(new ByteArrayInputStream(bytes)) {
147                        protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
148                            return Class.forName(osc.getName(), true, evaluator.getGroovyClassLoader());
149                        }
150                    }.readObject();
151        }
152        else {
153            this.vars = (Map<String, Object>)new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
154        }
155    }
156}