001/*
002 * Copyright 2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.item.support;
017
018import org.springframework.scripting.support.StaticScriptSource;
019import org.springframework.util.StringUtils;
020import org.springframework.batch.item.ItemProcessor;
021import org.springframework.beans.factory.InitializingBean;
022import org.springframework.core.io.Resource;
023import org.springframework.scripting.ScriptEvaluator;
024import org.springframework.scripting.ScriptSource;
025import org.springframework.scripting.support.ResourceScriptSource;
026import org.springframework.scripting.support.StandardScriptEvaluator;
027import org.springframework.util.Assert;
028
029import java.util.HashMap;
030import java.util.Map;
031
032/**
033 * <p>
034 * {@link org.springframework.batch.item.ItemProcessor} implementation that passes the current
035 * item to process to the provided script. Exposes the current item for processing via the
036 * {@link org.springframework.batch.item.support.ScriptItemProcessor#ITEM_BINDING_VARIABLE_NAME}
037 * key name ("item"). A custom key name can be set by invoking:
038 * {@link org.springframework.batch.item.support.ScriptItemProcessor#setItemBindingVariableName}
039 * with the desired key name. The thread safety of this {@link org.springframework.batch.item.ItemProcessor}
040 * depends on the implementation of the {@link org.springframework.scripting.ScriptEvaluator} used.
041 * </p>
042 *
043 *
044 * @author Chris Schaefer
045 * @since 3.0
046 */
047public class ScriptItemProcessor<I, O> implements ItemProcessor<I, O>, InitializingBean {
048        private static final String ITEM_BINDING_VARIABLE_NAME = "item";
049
050        private String language;
051        private ScriptSource script;
052        private ScriptSource scriptSource;
053        private ScriptEvaluator scriptEvaluator;
054        private String itemBindingVariableName = ITEM_BINDING_VARIABLE_NAME;
055
056        @Override
057        @SuppressWarnings("unchecked")
058        public O process(I item) throws Exception {
059                Map<String, Object> arguments = new HashMap<String, Object>();
060                arguments.put(itemBindingVariableName, item);
061
062                return (O) scriptEvaluator.evaluate(getScriptSource(), arguments);
063        }
064
065        /**
066         * <p>
067         * Sets the {@link org.springframework.core.io.Resource} location of the script to use.
068         * The script language will be deduced from the filename extension.
069         * </p>
070         *
071         * @param resource the {@link org.springframework.core.io.Resource} location of the script to use.
072         */
073        public void setScript(Resource resource) {
074                Assert.notNull(resource, "The script resource cannot be null");
075
076                this.script = new ResourceScriptSource(resource);
077        }
078
079        /**
080         * <p>
081         * Sets the provided {@link String} as the script source code to use.
082         * </p>
083         *
084         * @param scriptSource the {@link String} form of the script source code to use.
085         * @param language the language of the script.
086         */
087        public void setScriptSource(String scriptSource, String language) {
088                Assert.hasText(language, "Language must contain the script language");
089                Assert.hasText(scriptSource, "Script source must contain the script source to evaluate");
090
091                this.language = language;
092                this.scriptSource = new StaticScriptSource(scriptSource);
093        }
094
095        /**
096         * <p>
097         * Provides the ability to change the key name that scripts use to obtain the current
098         * item to process if the variable represented by:
099         * {@link org.springframework.batch.item.support.ScriptItemProcessor#ITEM_BINDING_VARIABLE_NAME}
100         * is not suitable ("item").
101         * </p>
102         *
103         * @param itemBindingVariableName the desired binding variable name
104         */
105        public void setItemBindingVariableName(String itemBindingVariableName) {
106                this.itemBindingVariableName = itemBindingVariableName;
107        }
108
109        /**
110         * <p>
111         * Provides the ability to set a custom {@link org.springframework.scripting.ScriptEvaluator}
112         * implementation. If not set, a {@link org.springframework.scripting.support.StandardScriptEvaluator}
113         * will be used by default.
114         * </p>
115         *
116         * @param scriptEvaluator the {@link org.springframework.scripting.ScriptEvaluator} to use
117         */
118        public void setScriptEvaluator(ScriptEvaluator scriptEvaluator) {
119                this.scriptEvaluator = scriptEvaluator;
120        }
121
122        @Override
123        public void afterPropertiesSet() throws Exception {
124                if(scriptEvaluator == null) {
125                        scriptEvaluator = new StandardScriptEvaluator();
126                }
127
128                Assert.state(scriptSource != null || script != null,
129                                "Either the script source or script file must be provided");
130
131                Assert.state(scriptSource == null || script == null,
132                                "Either a script source or script file must be provided, not both");
133
134                if (scriptSource != null && scriptEvaluator instanceof StandardScriptEvaluator) {
135                        Assert.isTrue(!StringUtils.isEmpty(language),
136                                        "Language must be provided when using the default ScriptEvaluator and raw source code");
137
138                        ((StandardScriptEvaluator) scriptEvaluator).setLanguage(language);
139                }
140        }
141
142        private ScriptSource getScriptSource() {
143                if (script != null) {
144                        return script;
145                }
146
147                if (scriptSource != null) {
148                        return scriptSource;
149                }
150
151                throw new IllegalStateException("Either a script source or script needs to be provided.");
152        }
153}