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