001/*
002 * Copyright 2002-2018 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 */
016
017package org.springframework.scripting.support;
018
019import java.io.IOException;
020import java.io.Reader;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.core.io.Resource;
026import org.springframework.core.io.support.EncodedResource;
027import org.springframework.lang.Nullable;
028import org.springframework.scripting.ScriptSource;
029import org.springframework.util.Assert;
030import org.springframework.util.FileCopyUtils;
031import org.springframework.util.StringUtils;
032
033/**
034 * {@link org.springframework.scripting.ScriptSource} implementation
035 * based on Spring's {@link org.springframework.core.io.Resource}
036 * abstraction. Loads the script text from the underlying Resource's
037 * {@link org.springframework.core.io.Resource#getFile() File} or
038 * {@link org.springframework.core.io.Resource#getInputStream() InputStream},
039 * and tracks the last-modified timestamp of the file (if possible).
040 *
041 * @author Rob Harrop
042 * @author Juergen Hoeller
043 * @since 2.0
044 * @see org.springframework.core.io.Resource#getInputStream()
045 * @see org.springframework.core.io.Resource#getFile()
046 * @see org.springframework.core.io.ResourceLoader
047 */
048public class ResourceScriptSource implements ScriptSource {
049
050        /** Logger available to subclasses. */
051        protected final Log logger = LogFactory.getLog(getClass());
052
053        private EncodedResource resource;
054
055        private long lastModified = -1;
056
057        private final Object lastModifiedMonitor = new Object();
058
059
060        /**
061         * Create a new ResourceScriptSource for the given resource.
062         * @param resource the EncodedResource to load the script from
063         */
064        public ResourceScriptSource(EncodedResource resource) {
065                Assert.notNull(resource, "Resource must not be null");
066                this.resource = resource;
067        }
068
069        /**
070         * Create a new ResourceScriptSource for the given resource.
071         * @param resource the Resource to load the script from (using UTF-8 encoding)
072         */
073        public ResourceScriptSource(Resource resource) {
074                Assert.notNull(resource, "Resource must not be null");
075                this.resource = new EncodedResource(resource, "UTF-8");
076        }
077
078
079        /**
080         * Return the {@link org.springframework.core.io.Resource} to load the
081         * script from.
082         */
083        public final Resource getResource() {
084                return this.resource.getResource();
085        }
086
087        /**
088         * Set the encoding used for reading the script resource.
089         * <p>The default value for regular Resources is "UTF-8".
090         * A {@code null} value implies the platform default.
091         */
092        public void setEncoding(@Nullable String encoding) {
093                this.resource = new EncodedResource(this.resource.getResource(), encoding);
094        }
095
096
097        @Override
098        public String getScriptAsString() throws IOException {
099                synchronized (this.lastModifiedMonitor) {
100                        this.lastModified = retrieveLastModifiedTime();
101                }
102                Reader reader = this.resource.getReader();
103                return FileCopyUtils.copyToString(reader);
104        }
105
106        @Override
107        public boolean isModified() {
108                synchronized (this.lastModifiedMonitor) {
109                        return (this.lastModified < 0 || retrieveLastModifiedTime() > this.lastModified);
110                }
111        }
112
113        /**
114         * Retrieve the current last-modified timestamp of the underlying resource.
115         * @return the current timestamp, or 0 if not determinable
116         */
117        protected long retrieveLastModifiedTime() {
118                try {
119                        return getResource().lastModified();
120                }
121                catch (IOException ex) {
122                        if (logger.isDebugEnabled()) {
123                                logger.debug(getResource() + " could not be resolved in the file system - " +
124                                                "current timestamp not available for script modification check", ex);
125                        }
126                        return 0;
127                }
128        }
129
130        @Override
131        @Nullable
132        public String suggestedClassName() {
133                String filename = getResource().getFilename();
134                return (filename != null ? StringUtils.stripFilenameExtension(filename) : null);
135        }
136
137        @Override
138        public String toString() {
139                return this.resource.toString();
140        }
141
142}