001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
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 *     http://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 * Contributors:
017 *     Stephane Lacoin
018 */
019package org.nuxeo.ecm.webengine.gwt;
020
021import static java.nio.file.FileVisitResult.CONTINUE;
022import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
023import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
024
025import java.io.File;
026import java.io.IOException;
027import java.net.URI;
028import java.nio.file.FileSystem;
029import java.nio.file.FileSystems;
030import java.nio.file.FileVisitResult;
031import java.nio.file.FileVisitor;
032import java.nio.file.Files;
033import java.nio.file.Path;
034import java.nio.file.Paths;
035import java.nio.file.attribute.BasicFileAttributes;
036import java.util.ArrayList;
037import java.util.Collections;
038import java.util.HashMap;
039import java.util.List;
040import java.util.ListIterator;
041import java.util.Map;
042
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.nuxeo.common.Environment;
046
047public class GwtResolver {
048
049    private final Log log = LogFactory.getLog(GwtResolver.class);
050
051    public interface Strategy {
052
053        URI source();
054
055        File resolve(String path);
056    }
057
058    public final File GWT_ROOT = locateRoot();
059
060    private static File locateRoot() {
061                File dir = new File(Environment.getDefault().getWeb(), "root.war/gwt");
062                dir.mkdirs();
063                return dir;
064        }
065
066    protected final Map<String, CompositeStrategy> strategies = new HashMap<String, CompositeStrategy>();
067
068    protected final Strategy ROOT_RESOLVER_STRATEGY = new Strategy() {
069
070        @Override
071        public URI source() {
072            return GWT_ROOT.toURI();
073        }
074
075        @Override
076        public File resolve(String path) {
077            return new File(GWT_ROOT, path);
078        }
079    };
080
081    class CompositeStrategy {
082        final Map<URI, Strategy> strategiesByKey = new HashMap<URI, Strategy>();
083
084        final List<Strategy> strategies = new ArrayList<Strategy>();
085
086        void install(Strategy strategy) {
087            strategiesByKey.put(strategy.source(), strategy);
088            strategies.add(strategy);
089        }
090
091        void uninstall(URI source) {
092            Strategy strategy = strategiesByKey.remove(source);
093            if (strategy == null) {
094                return;
095            }
096            strategies.remove(strategy);
097        }
098
099        public File resolve(String path) {
100            ListIterator<Strategy> it = strategies.listIterator(strategies.size());
101            while (it.hasPrevious()) {
102                File file = it.previous().resolve(path);
103                if (file.exists()) {
104                    return file;
105                }
106            }
107            return null;
108        }
109    }
110
111    public Strategy newStrategy(final URI location) throws IOException {
112        final File root = install(location);
113        return new Strategy() {
114
115
116            @Override
117            public URI source() {
118                return location;
119            }
120
121            @Override
122            public File resolve(String path) {
123                return new File(root, path);
124            }
125        };
126    }
127
128    protected File install(URI location) throws IOException {
129        if ("jar".equals(location.getScheme())) {
130            Map<String, Object> env = Collections.emptyMap();
131            try (FileSystem fileSystem = FileSystems.newFileSystem(location, env, this.getClass().getClassLoader());) {
132                Path path = Paths.get(location);
133                try {
134                    // it's a directory
135                    return path.toFile();
136                } catch (UnsupportedOperationException cause) {
137                    // it's a jar, we should install content
138                }
139                Files.walkFileTree(path, new TreeImporter(path, GWT_ROOT.toPath()));
140                return GWT_ROOT;
141            }
142        }
143        return Paths.get(location).toFile();
144    }
145
146    public void install(String name, Strategy strategy) {
147        if (!strategies.containsKey(name)) {
148            strategies.put(name, new CompositeStrategy());
149        }
150        strategies.get(name).install(strategy);
151    }
152
153    public void install(String name, URI location) throws IOException {
154        install(name, newStrategy(location));
155    }
156
157    public void uninstall(String name) {
158        strategies.remove(name);
159    }
160
161    public File resolve(String path) {
162        int indexOf = path.indexOf('/');
163        if (indexOf == -1) {
164            if (strategies.containsKey(path)) {
165                return strategies.get(path).resolve("/");
166            }
167            return ROOT_RESOLVER_STRATEGY.resolve(path);
168        }
169        String name = path.substring(0, indexOf);
170        if (strategies.containsKey(name)) {
171            return strategies.get(name).resolve(path);
172        }
173        return ROOT_RESOLVER_STRATEGY.resolve(path);
174    }
175
176    public class TreeImporter implements FileVisitor<Path> {
177        final Path source;
178
179        final Path sink;
180
181        public TreeImporter(Path source, Path sink) {
182            this.source = source;
183            this.sink = sink;
184        }
185
186        @Override
187        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
188            if (dir == source) {
189                return CONTINUE;
190            }
191            Path sinkPath = toSinkPath(dir);
192            if (!Files.exists(sinkPath)) {
193                                Files.createDirectory(sinkPath);
194                        }
195            return CONTINUE;
196        }
197
198        Path toSinkPath(Path path) {
199            if (path == source) {
200                return sink;
201            }
202            path = source.relativize(path);
203            path = sink.resolve(path.toString());
204            return path;
205        }
206
207        @Override
208        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
209            Files.copy(file, toSinkPath(file), COPY_ATTRIBUTES, REPLACE_EXISTING);
210            return CONTINUE;
211        }
212
213        @Override
214        public FileVisitResult postVisitDirectory(Path dir, IOException error) {
215            if (error != null) {
216                return FileVisitResult.TERMINATE;
217            }
218            return CONTINUE;
219        }
220
221        @Override
222        public FileVisitResult visitFileFailed(Path file, IOException error) {
223            if (error != null) {
224                return FileVisitResult.TERMINATE;
225            }
226
227            return CONTINUE;
228        }
229
230    }
231}