Super Dev Mode: rename to Outbox and OutboxTable
External visible change: better error checking in SourceHandler
when a module isn't found.
Renamed ModuleState to Outbox and Modules to OutboxTable, and fixed
usages. This is in preparation for implementing fast switching between
browsers. Outboxes won't be one-to-one with modules, so "module"
shouldn't be in the class name anymore.
Bug: issue 8882
Change-Id: I6a0090d99096ff3defed09f75220f5d14b07d360
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
index c490ee4..6c9d808 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
@@ -58,10 +58,10 @@
PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(options.getLogLevel());
- Modules modules;
+ OutboxTable outboxes;
try {
- modules = makeModules(options, logger);
+ outboxes = makeOutboxes(options, logger);
} catch (Throwable t) {
t.printStackTrace();
System.out.println("FAIL");
@@ -75,7 +75,7 @@
try {
// TODO: actually test recompiling here.
// (This is just running precompiles repeatedly.)
- modules.defaultCompileAll(options.getNoPrecompile(), logger);
+ outboxes.defaultCompileAll(options.getNoPrecompile(), logger);
} catch (Throwable t) {
t.printStackTrace();
System.out.println("FAIL");
@@ -116,13 +116,13 @@
topLogger.setMaxDetail(options.getLogLevel());
TreeLogger startupLogger = topLogger.branch(Type.INFO, "Super Dev Mode starting up");
- Modules modules = makeModules(options, startupLogger);
+ OutboxTable outboxes = makeOutboxes(options, startupLogger);
- SourceHandler sourceHandler = new SourceHandler(modules);
+ SourceHandler sourceHandler = new SourceHandler(outboxes);
ProgressTable progressTable = new ProgressTable();
- JobRunner runner = new JobRunner(progressTable, modules);
+ JobRunner runner = new JobRunner(progressTable, outboxes);
- WebServer webServer = new WebServer(sourceHandler, modules,
+ WebServer webServer = new WebServer(sourceHandler, outboxes,
runner, progressTable, options.getBindAddress(), options.getPort());
webServer.start(topLogger);
@@ -132,20 +132,20 @@
/**
* Configures and compiles all the modules (unless {@link Options#getNoPrecompile} is false).
*/
- private static Modules makeModules(Options options, TreeLogger logger)
+ private static OutboxTable makeOutboxes(Options options, TreeLogger logger)
throws IOException, UnableToCompleteException {
File workDir = ensureWorkDir(options);
logger.log(Type.INFO, "workDir: " + workDir);
- Modules modules = new Modules(options);
+ OutboxTable outboxes = new OutboxTable(options);
for (String moduleName : options.getModuleNames()) {
AppSpace appSpace = AppSpace.create(new File(workDir, moduleName));
Recompiler recompiler = new Recompiler(appSpace, moduleName, options);
- modules.addModuleState(new ModuleState(recompiler, options.getNoPrecompile(), logger));
+ outboxes.addOutbox(new Outbox(recompiler, options.getNoPrecompile(), logger));
}
- return modules;
+ return outboxes;
}
/**
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
index 482d10d..4da9f3f 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
@@ -60,8 +60,7 @@
/**
* Creates a job to recompile a module.
- * @param moduleName The name of the module to recompile, suitable for
- * passing to {@link Modules#get}.
+ * @param moduleName The client-side name of the module to recompile (after renaming).
* @param bindingProperties Properties that uniquely identify a permutation.
* (Otherwise, more than one permutation will be compiled.)
* @param parentLogger The parent of the logger that will be used for this job.
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
index f0507a3..abdf13c 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
@@ -31,12 +31,12 @@
*/
public class JobRunner {
private final ProgressTable table;
- private final Modules modules;
+ private final OutboxTable outboxes;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
- JobRunner(ProgressTable table, Modules modules) {
+ JobRunner(ProgressTable table, OutboxTable outboxes) {
this.table = table;
- this.modules = modules;
+ this.outboxes = outboxes;
}
/**
@@ -50,22 +50,22 @@
executor.submit(new Runnable() {
@Override
public void run() {
- recompile(job, modules);
+ recompile(job, outboxes);
}
});
job.getLogger().log(Type.TRACE, "added job to queue");
}
- private static void recompile(Job job, Modules modules) {
+ private static void recompile(Job job, OutboxTable outboxes) {
job.getLogger().log(Type.INFO, "starting job: " + job.getId());
- ModuleState m = modules.get(job.getModuleName());
- if (m == null) {
+ Outbox box = outboxes.findOutbox(job);
+ if (box == null) {
String msg = "skipped a compile job with an unknown module: " + job.getModuleName();
job.getLogger().log(Type.WARN, msg);
job.onFinished(new Result(job, null, new RuntimeException(msg)));
return;
}
- m.recompile(job);
+ box.recompile(job);
}
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Modules.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Modules.java
deleted file mode 100644
index 48cfcbf..0000000
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Modules.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.dev.codeserver;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.json.JsonArray;
-import com.google.gwt.dev.json.JsonObject;
-import com.google.gwt.thirdparty.guava.common.collect.Maps;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * An in-memory directory of all the modules available on this code server. The {@link WebServer}
- * uses this directory to find the {@link ModuleState} associated with a URL and to list all the
- * modules on the front page.
- */
-class Modules implements Iterable<String> {
- private final Options options;
- private final Map<String, ModuleState> moduleStateMap = Maps.newHashMap();
-
- public Modules(Options options) {
- this.options = options;
- }
-
- /**
- * Adds a {@link ModuleState} to the map.
- * @param state the module state to map
- */
- public void addModuleState(ModuleState state) {
- moduleStateMap.put(state.getModuleName(), state);
- }
-
- /**
- * Retrieves a {@link ModuleState} corresponding to a given module name.
- * This should be the module name after renaming.
- */
- public ModuleState get(String moduleName) {
- // TODO: maybe this lookup should also succeed if passed the module name before renaming?
- // (I believe currently everything breaks if you change how a module is renamed, requiring
- // a restart.)
- return moduleStateMap.get(moduleName);
- }
-
- /**
- * Iterates over the list of modules.
- */
- @Override
- public Iterator<String> iterator() {
- return moduleStateMap.keySet().iterator();
- }
-
- void defaultCompileAll(boolean noPrecompile, TreeLogger logger) throws UnableToCompleteException {
- for (ModuleState m: moduleStateMap.values()) {
- m.maybePrecompile(noPrecompile, logger);
- }
- }
-
- /**
- * Returns a configuration object containing the names of all the modules
- * and warnings to display to the user.
- */
- JsonObject getConfig() {
- JsonObject config = JsonObject.create();
- JsonArray moduleNames = new JsonArray();
- for (String module : this) {
- moduleNames.add(module);
- }
- config.put("moduleNames", moduleNames);
- config.put("warnings", options.getWarningsAsJson());
- return config;
- }
-}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/ModuleState.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
similarity index 96%
rename from dev/codeserver/java/com/google/gwt/dev/codeserver/ModuleState.java
rename to dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
index c52098a..289e8d5 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/ModuleState.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
@@ -33,10 +33,10 @@
import java.util.concurrent.atomic.AtomicReference;
/**
- * Contains everything that the code server knows about a GWT app (module), including how
- * to recompile it and where the compiler output is.
+ * Holds the compiler output for one module.
+ * TODO(skybrian) there will later be a separate Outbox for each set of binding properties.
*/
-class ModuleState {
+class Outbox {
/**
* The suffix that the GWT compiler uses when writing a sourcemap file.
@@ -46,8 +46,7 @@
private final AtomicReference<Job.Result> published = new AtomicReference<Job.Result>();
private final Recompiler recompiler;
- ModuleState(Recompiler recompiler, boolean noPrecompile,
- TreeLogger logger)
+ Outbox(Recompiler recompiler, boolean noPrecompile, TreeLogger logger)
throws UnableToCompleteException {
this.recompiler = recompiler;
maybePrecompile(noPrecompile, logger);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/OutboxTable.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/OutboxTable.java
new file mode 100644
index 0000000..9f3e718
--- /dev/null
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/OutboxTable.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.dev.codeserver;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.json.JsonArray;
+import com.google.gwt.dev.json.JsonObject;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * An in-memory table of all the outboxes available on this code server. The {@link WebServer}
+ * uses this directory to find the {@link Outbox} associated with a URL and to list all the
+ * modules on the front page.
+ */
+class OutboxTable {
+ private final Options options;
+
+ /**
+ * A map from client-side module names (after renaming) to its outbox.
+ */
+ private final Map<String, Outbox> outboxes = Maps.newHashMap();
+
+ OutboxTable(Options options) {
+ this.options = options;
+ }
+
+ /**
+ * Adds a {@link Outbox} to the table.
+ */
+ void addOutbox(Outbox outbox) {
+ outboxes.put(outbox.getModuleName(), outbox);
+ }
+
+ /**
+ * Returns the outbox where the output of a compile job will be published,
+ * or null if it can't be found.
+ */
+ Outbox findOutbox(Job job) {
+ return findByModuleName(job.getModuleName());
+ }
+
+ /**
+ * Retrieves a {@link Outbox} corresponding to a given module name.
+ * This should be the module name after renaming.
+ * TODO: remove and use an Outbox id instead.
+ */
+ Outbox findByModuleName(String moduleName) {
+ return outboxes.get(moduleName);
+ }
+
+ /**
+ * Returns the list of known module names (after renaming).
+ */
+ Collection<String> getModuleNames() {
+ return outboxes.keySet();
+ }
+
+ void defaultCompileAll(boolean noPrecompile, TreeLogger logger) throws UnableToCompleteException {
+ for (Outbox box: outboxes.values()) {
+ box.maybePrecompile(noPrecompile, logger);
+ }
+ }
+
+ /**
+ * Returns a configuration object containing the names of all the modules
+ * and warnings to display to the user.
+ */
+ JsonObject getConfig() {
+ JsonObject config = JsonObject.create();
+ JsonArray moduleNames = new JsonArray();
+ for (String module : getModuleNames()) {
+ moduleNames.add(module);
+ }
+ config.put("moduleNames", moduleNames);
+ config.put("warnings", options.getWarningsAsJson());
+ return config;
+ }
+}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/ReverseSourceMap.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/ReverseSourceMap.java
index 2b81781..d737012 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/ReverseSourceMap.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/ReverseSourceMap.java
@@ -21,6 +21,8 @@
import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapConsumerV3;
import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapParseException;
+import java.io.File;
+
/**
* A mapping from Java lines to JavaScript.
*/
@@ -35,9 +37,9 @@
* Reads a source map from disk and parses it into an in-memory representation.
* If it can't be loaded, logs a warning and returns an empty source map.
*/
- static ReverseSourceMap load(TreeLogger logger, ModuleState moduleState) {
+ static ReverseSourceMap load(TreeLogger logger, File sourceMapFile) {
SourceMapConsumerV3 consumer = new SourceMapConsumerV3();
- String unparsed = Util.readFileAsString(moduleState.findSourceMapForOnePermutation());
+ String unparsed = Util.readFileAsString(sourceMapFile);
try {
consumer.parse(unparsed);
return new ReverseSourceMap(consumer);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/SourceHandler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/SourceHandler.java
index bc57320..d841530 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/SourceHandler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/SourceHandler.java
@@ -74,10 +74,10 @@
static final String SOURCEROOT_TEMPLATE_VARIABLE = "$sourceroot_goes_here$";
- private Modules modules;
+ private OutboxTable outboxes;
- SourceHandler(Modules modules) {
- this.modules = modules;
+ SourceHandler(OutboxTable outboxes) {
+ this.outboxes = outboxes;
}
static boolean isSourceMapRequest(String target) {
@@ -100,6 +100,13 @@
throw new RuntimeException("invalid request (shouldn't happen): " + target);
}
+ Outbox box = outboxes.findByModuleName(moduleName);
+ if (box == null) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ logger.log(TreeLogger.WARN, "unknown module; returned not found for request: " + target);
+ return;
+ }
+
String rootDir = SOURCEMAP_PATH + moduleName + "/";
String rest = target.substring(rootDir.length());
@@ -109,18 +116,15 @@
// This URL is no longer used by debuggers (we use the strong name) but is used for testing.
// It's useful not to need the strong name to download the sourcemap.
// (But this only works when there is one permutation.)
- ModuleState moduleState = modules.get(moduleName);
- sendSourceMap(moduleName, moduleState.findSourceMapForOnePermutation(), request, response,
- logger);
+ sendSourceMap(moduleName, box.findSourceMapForOnePermutation(), request, response, logger);
} else if (rest.endsWith("/")) {
sendFileListPage(moduleName, rest, response, logger);
} else if (rest.endsWith(".java")) {
- sendSourceFile(moduleName, rest, request.getQueryString(), response, logger);
+ sendSourceFile(box, rest, request.getQueryString(), response, logger);
} else {
String strongName = getStrongNameFromSourcemapFilename(rest);
if (strongName != null) {
- ModuleState moduleState = modules.get(moduleName);
- File sourceMap = moduleState.findSourceMap(strongName).getAbsoluteFile();
+ File sourceMap = box.findSourceMap(strongName).getAbsoluteFile();
sendSourceMap(moduleName, sourceMap, request, response, logger);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
@@ -205,10 +209,9 @@
* Sends an HTTP response containing a Java source. It will be sent as plain text by default,
* or as HTML if the query string is equal to "html".
*/
- private void sendSourceFile(String moduleName, String sourcePath, String query,
+ private void sendSourceFile(Outbox box, String sourcePath, String query,
HttpServletResponse response, TreeLogger logger) throws IOException {
- ModuleState moduleState = modules.get(moduleName);
- InputStream pageBytes = moduleState.openSourceFile(sourcePath);
+ InputStream pageBytes = box.openSourceFile(sourcePath);
if (pageBytes == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
@@ -218,7 +221,7 @@
if (query != null && query.equals("html")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(pageBytes));
- sendSourceFileAsHtml(moduleName, sourcePath, reader, response, logger);
+ sendSourceFileAsHtml(box, sourcePath, reader, response, logger);
} else {
PageUtil.sendStream("text/plain", pageBytes, response);
}
@@ -229,10 +232,11 @@
* that have corresponding JavaScript will be highlighted (as determined by reading the
* source map).
*/
- private void sendSourceFileAsHtml(String moduleName, String sourcePath, BufferedReader lines,
+ private void sendSourceFileAsHtml(Outbox box, String sourcePath, BufferedReader lines,
HttpServletResponse response, TreeLogger logger) throws IOException {
- ReverseSourceMap sourceMap = ReverseSourceMap.load(logger, modules.get(moduleName));
+ ReverseSourceMap sourceMap = ReverseSourceMap.load(logger,
+ box.findSourceMapForOnePermutation());
File sourceFile = new File(sourcePath);
@@ -276,7 +280,7 @@
}
private SourceMap loadSourceMap(String moduleName) {
- ModuleState moduleState = modules.get(moduleName);
- return SourceMap.load(moduleState.findSourceMapForOnePermutation());
+ Outbox box = outboxes.findByModuleName(moduleName);
+ return SourceMap.load(box.findSourceMapForOnePermutation());
}
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
index 29d8b6a..9500126 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
@@ -90,7 +90,7 @@
private static final MimeTypes MIME_TYPES = new MimeTypes();
private final SourceHandler handler;
- private final Modules modules;
+ private final OutboxTable outboxes;
private final JobRunner runner;
private final ProgressTable progressTable;
@@ -99,10 +99,10 @@
private Server server;
- WebServer(SourceHandler handler, Modules modules, JobRunner runner,
+ WebServer(SourceHandler handler, OutboxTable outboxes, JobRunner runner,
ProgressTable progressTable, String bindAddress, int port) {
this.handler = handler;
- this.modules = modules;
+ this.outboxes = outboxes;
this.runner = runner;
this.progressTable = progressTable;
this.bindAddress = bindAddress;
@@ -153,7 +153,7 @@
* Returns the location of the compiler output. (Changes after every recompile.)
*/
public File getCurrentWarDir(String moduleName) {
- return modules.get(moduleName).getWarDir();
+ return outboxes.findByModuleName(moduleName).getWarDir();
}
private void handleRequest(String target, HttpServletRequest request,
@@ -179,14 +179,14 @@
if (target.equals("/")) {
setHandled(request);
- JsonObject config = modules.getConfig();
+ JsonObject config = outboxes.getConfig();
PageUtil.sendJsonAndHtml("config", config, "frontpage.html", response, logger);
return;
}
if (target.equals("/dev_mode_on.js")) {
setHandled(request);
- JsonObject config = modules.getConfig();
+ JsonObject config = outboxes.getConfig();
PageUtil
.sendJsonAndJavaScript("__gwt_codeserver_config", config, "dev_mode_on.js", response,
logger);
@@ -198,8 +198,8 @@
if (target.startsWith("/recompile/")) {
setHandled(request);
String moduleName = target.substring("/recompile/".length());
- ModuleState moduleState = modules.get(moduleName);
- if (moduleState == null) {
+ Outbox outbox = outboxes.findByModuleName(moduleName);
+ if (outbox == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
logger.log(TreeLogger.WARN, "not found: " + target);
return;
@@ -211,11 +211,11 @@
// cause a spurious recompile, resulting in an unexpected permutation being loaded later.
//
// It would be unsafe to allow a configuration property to be changed.
- Job job = new Job(moduleState.getModuleName(), getBindingProperties(request), logger);
+ Job job = new Job(outbox.getModuleName(), getBindingProperties(request), logger);
runner.submit(job);
boolean ok = job.waitForResult().isOk();
- JsonObject config = modules.getConfig();
+ JsonObject config = outboxes.getConfig();
config.put("status", ok ? "ok" : "failed");
sendJsonResult(config, request, response, logger);
return;
@@ -224,7 +224,7 @@
if (target.startsWith("/log/")) {
setHandled(request);
String moduleName = target.substring("/log/".length());
- File file = modules.get(moduleName).getCompileLog();
+ File file = outboxes.findByModuleName(moduleName).getCompileLog();
sendLogPage(moduleName, file, response);
return;
}
@@ -299,12 +299,12 @@
int secondSlash = target.indexOf('/', 1);
String moduleName = target.substring(1, secondSlash);
- ModuleState moduleState = modules.get(moduleName);
+ Outbox outbox = outboxes.findByModuleName(moduleName);
- File file = moduleState.getOutputFile(target);
+ File file = outbox.getOutputFile(target);
if (!file.isFile()) {
// perhaps it's compressed
- file = moduleState.getOutputFile(target + ".gz");
+ file = outbox.getOutputFile(target + ".gz");
if (!file.isFile()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
logger.log(TreeLogger.WARN, "not found: " + file.toString());
@@ -332,7 +332,7 @@
private void sendModulePage(String moduleName, HttpServletResponse response, TreeLogger logger)
throws IOException {
- ModuleState module = modules.get(moduleName);
+ Outbox module = outboxes.findByModuleName(moduleName);
if (module == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
logger.log(TreeLogger.WARN, "module not found: " + moduleName);
@@ -357,8 +357,8 @@
out.startTag("h1").text("Policy Files").endTag("h1").nl();
- for (String moduleName : modules) {
- ModuleState module = modules.get(moduleName);
+ for (String moduleName : outboxes.getModuleNames()) {
+ Outbox module = outboxes.findByModuleName(moduleName);
File manifest = module.getExtraFile("rpcPolicyManifest/manifest.txt");
if (manifest.isFile()) {
out.startTag("h2").text(moduleName).endTag("h2").nl();
@@ -415,8 +415,8 @@
return;
}
- for (String moduleName : modules) {
- ModuleState module = modules.get(moduleName);
+ for (String moduleName : outboxes.getModuleNames()) {
+ Outbox module = outboxes.findByModuleName(moduleName);
File policy = module.getOutputFile(moduleName + "/" + rest);
if (policy.isFile()) {
PageUtil.sendFile("text/plain", policy, response);