Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

$RestxSession special header, more samples in AppModule generated file (app.name, security) & test harness for restx-core-shell #40

Merged
merged 20 commits into from
Sep 1, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
62aeec2
Added "app.name" component to defaultly generated AppModule
fcamblor Aug 27, 2013
3972e82
Adding @PermitAll on HelloResource, otherwise, HelloResourceSpecTest …
fcamblor Aug 27, 2013
daff9f0
Added UserRepository.java class,
fcamblor Aug 28, 2013
cfb97d7
Provided implementation for basicPrincipalAuthenticator() in AppModule
fcamblor Aug 28, 2013
867eda2
Provided spec tests trying to call hello route
fcamblor Aug 28, 2013
4006d87
fix missing import in HelloResourceSpecTest after AL on e69f9ccdd2f79…
fcamblor Aug 28, 2013
bfed533
Created SignatureKey.DEFAULT
fcamblor Aug 28, 2013
dd7a592
Added Query.findOneAsComponent(), same as SatisfiedBOM.getOneAsCompon…
fcamblor Aug 28, 2013
09aa537
Introduced special "$RestxSession:" header for `when` clauses
fcamblor Aug 28, 2013
1e64106
using new $RestxSession special header in spec tests
fcamblor Aug 28, 2013
5ef649f
Cleaned a bit the header cookies code, introducing a WhenHeaders enum
fcamblor Aug 28, 2013
992fc61
removed useless `generateSignatureFor()` mustache template block
fcamblor Aug 28, 2013
b9cd78c
refactored NewAppCommandRunner in a way which is more testable
fcamblor Aug 29, 2013
c1b38e0
Provided integration test for NewApp command
fcamblor Aug 29, 2013
cf2e31e
Extracted RestxSpec inner classes to upper classes
fcamblor Aug 29, 2013
d6d460c
Transforming cookies map into an immutable map inside
fcamblor Aug 29, 2013
47bf2b2
Refactored WhenHttpRequest instanciation in
fcamblor Aug 29, 2013
0963bdb
Considering shell-dependent modules as module to build "in the end"
fcamblor Aug 30, 2013
5b744fc
Transformed RestxSpecLoader.WhenHeaders enum into a @Component
fcamblor Aug 30, 2013
055edd3
Improved the way to resolve current module version
fcamblor Aug 30, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 11 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,9 @@
<module>restx-barbarywatch</module>
<module>restx-classloader</module>
<module>restx-security-basic</module>
<module>restx-shell</module>
<module>restx-shell-manager</module>
<module>restx-package</module>
<module>restx-build</module>
<module>restx-build-shell</module>
<module>restx-core</module>
<module>restx-core-shell</module>
<module>restx-core-annotation-processor</module>
<module>restx-annotation-processors-package</module>
<module>restx-admin</module>
Expand All @@ -125,13 +121,23 @@
<module>restx-specs-tests</module>
<module>restx-specs-admin</module>
<module>restx-specs-server</module>
<module>restx-specs-shell</module>
<module>restx-jongo</module>
<module>restx-jongo-specs-tests</module>
<module>restx-servlet</module>
<module>restx-server-jetty</module>
<module>restx-server-tomcat</module>
<module>restx-server-simple</module>
<module>restx-server-testing</module>
<!--
Shell modules should be built in the end because
they will have to generate some restx apps during tests
(every restx modules should have been installed when testing
these generated aps)
-->
<module>restx-core-shell</module>
<module>restx-shell</module>
<module>restx-shell-manager</module>
<module>restx-build-shell</module>
<module>restx-specs-shell</module>
</modules>
</project>
18 changes: 18 additions & 0 deletions restx-core-shell/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,23 @@
<artifactId>ivy</artifactId>
<version>${ivy.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-verifier</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
132 changes: 79 additions & 53 deletions restx-core-shell/src/main/java/restx/core/shell/AppShellCommand.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package restx.core.shell;

import com.github.mustachejava.Mustache;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.base.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
Expand All @@ -19,6 +17,7 @@
import restx.shell.ShellCommandRunner;
import restx.shell.StdShellCommand;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
Expand Down Expand Up @@ -63,13 +62,29 @@ public Iterable<Completer> getCompleters() {
new StringsCompleter("app"), new StringsCompleter("new", "run")));
}

private class NewAppCommandRunner implements ShellCommandRunner {
static class NewAppDescriptor {
String appName;
String groupId;
String artifactId;
String mainPackage;
String version;
String buildFile;
String signatureKey;
String adminPassword;
String defaultPort;
String basePath;
String restxVersion;
boolean generateHelloResource;
}

class NewAppCommandRunner implements ShellCommandRunner {

private ImmutableMap<Mustache, String> mainTemplates = buildTemplates(
AppShellCommand.class, ImmutableMap.<String, String>builder()
.put("md.restx.json.mustache", "md.restx.json")
.put("AppModule.java.mustache", "src/main/java/{{packagePath}}/AppModule.java")
.put("AppServer.java.mustache", "src/main/java/{{packagePath}}/AppServer.java")
.put("UserRepository.java.mustache", "src/main/java/{{packagePath}}/persistence/UserRepository.java")
.put("web.xml.mustache", "src/main/webapp/WEB-INF/web.xml")
.put("logback.xml.mustache", "src/main/resources/logback.xml")
.build()
Expand All @@ -79,7 +94,9 @@ private class NewAppCommandRunner implements ShellCommandRunner {
.put("Message.java.mustache", "src/main/java/{{packagePath}}/domain/Message.java")
.put("HelloResource.java.mustache", "src/main/java/{{packagePath}}/rest/HelloResource.java")
.put("HelloResourceSpecTest.java.mustache", "src/test/java/{{packagePath}}/rest/HelloResourceSpecTest.java")
.put("should_say_hello.spec.yaml.mustache", "src/test/resources/specs/hello/should_say_hello.spec.yaml")
.put("should_admin_not_say_hello.spec.yaml.mustache", "src/test/resources/specs/hello/should_admin_not_say_hello.spec.yaml")
.put("should_user1_say_hello.spec.yaml.mustache", "src/test/resources/specs/hello/should_user1_say_hello.spec.yaml")
.put("should_user2_not_say_hello.spec.yaml.mustache", "src/test/resources/specs/hello/should_user2_not_say_hello.spec.yaml")
.build()
);

Expand All @@ -91,39 +108,41 @@ public void run(RestxShell shell) throws Exception {
shell.println("For any question you can get help by answering '??' (without the quotes).");
shell.println("");

String appName = "";
while (Strings.isNullOrEmpty(appName)) {
appName = shell.ask("App name? ", "",
NewAppDescriptor descriptor = new NewAppDescriptor();
descriptor.appName = "";
while (Strings.isNullOrEmpty(descriptor.appName)) {
descriptor.appName = shell.ask("App name? ", "",
"This is the name of the application you are creating.\n" +
"It can contain spaces, it's used mainly for documentation and to provide default for other values.\n" +
"Examples: Todo, Foo Bar, ...");
}
String groupId = shell.ask("group id [%s]? ",
appName.replaceAll("\\s+", "-").toLowerCase(Locale.ENGLISH),

descriptor.groupId = shell.ask("group id [%s]? ",
descriptor.appName.replaceAll("\\s+", "-").toLowerCase(Locale.ENGLISH),
"This is the identifier of the group or organization producing the application.\n" +
"In the Maven world this is called a groupId, in Ivy it's called organization.\n" +
"It MUST NOT contain spaces nor columns (':'), and is usually a reversed domain name.\n" +
"Examples: io.restx, com.example, ...");
String artifactId = shell.ask("artifact id [%s]? ",
appName.replaceAll("\\s+", "-").toLowerCase(Locale.ENGLISH),
descriptor.artifactId = shell.ask("artifact id [%s]? ",
descriptor.appName.replaceAll("\\s+", "-").toLowerCase(Locale.ENGLISH),
"This is the identifier of the app module.\n" +
"In the Maven world this is called an artifactId, in Ivy it's called module.\n" +
"It MUST NOT contain spaces nor columns (':'), and is usually a dash separated lower case word.\n" +
"Examples: myapp, todo, foo-app, ...")
.replaceAll("\\s+", "-");
String mainPackage = shell.ask("main package [%s]? ",
artifactId.replaceAll("\\-", ".").toLowerCase(Locale.ENGLISH),
descriptor.mainPackage = shell.ask("main package [%s]? ",
descriptor.artifactId.replaceAll("\\-", ".").toLowerCase(Locale.ENGLISH),
"This is the main package in which you will develop your application.\n" +
"In Java convention it should start with a reversed domain name followed by the app name\n" +
"but for applications (as opposed to APIs) we prefer to use a short name, like that app name.\n" +
"It MUST follow Java package names restrictions, so MUST NOT contain spaces\n" +
"Examples: myapp, com.example.todoapp, ...");
String version = shell.ask("version [%s]? ", "0.1-SNAPSHOT",
descriptor.version = shell.ask("version [%s]? ", "0.1-SNAPSHOT",
"This is the name of the first version of the app you are targetting.\n" +
"It's recommended to use Maven convention to suffix it with -SNAPSHOT if you plan to use Maven for your app\n" +
"Examples: 0.1-SNAPSHOT, 1.0, ...");

String buildFile = shell.ask("generate module descriptor (ivy/pom/none/all) [%s]? ", "all",
descriptor.buildFile = shell.ask("generate module descriptor (ivy/pom/none/all) [%s]? ", "all",
"This allows to generate a module descriptor for your app.\n" +
"Options:\n" +
"\t- 'ivy': get an Easyant compatible Ivy file generated for you.\n" +
Expand All @@ -132,32 +151,30 @@ public void run(RestxShell shell) throws Exception {
"\t- 'none': get no module descriptor generated. WARNING: this will make it harder to build your app.\n" +
"If you don't know these tools, use default answer.\n"
);
boolean generateIvy = "ivy".equalsIgnoreCase(buildFile) || "all".equalsIgnoreCase(buildFile);
boolean generatePom = "pom".equalsIgnoreCase(buildFile) || "all".equalsIgnoreCase(buildFile);

String restxVersion = shell.ask("restx version [%s]? ", Version.getVersion("io.restx", "restx-core"));
descriptor.restxVersion = shell.ask("restx version [%s]? ", Version.getVersion("io.restx", "restx-core"));

List<String> list = Lists.newArrayList(UUIDGenerator.DEFAULT.doGenerate(),
String.valueOf(new Random().nextLong()), appName, artifactId);
String.valueOf(new Random().nextLong()), descriptor.appName, descriptor.artifactId);
Collections.shuffle(list);
String signatureKey = shell.ask("signature key (to sign cookies) [%s]? ",
descriptor.signatureKey = shell.ask("signature key (to sign cookies) [%s]? ",
Joiner.on(" ").join(list),
"This is used as salt for signing stuff exchanged with the client.\n" +
"Use something fancy or keep what is proposed by default, but make sure to not share that publicly.");

String adminPassword = shell.ask("admin password (to authenticate on restx console) [%s]? ",
descriptor.adminPassword = shell.ask("admin password (to authenticate on restx console) [%s]? ",
String.valueOf(new Random().nextInt(10000)),
"This is used as password for the admin user to authenticate on restx console.\n" +
"This is only a default way to authenticate out of the box, restx security is very flexible.");

String defaultPort = shell.ask("default port [%s]? ", "8080",
descriptor.defaultPort = shell.ask("default port [%s]? ", "8080",
"This is the default port used when using embedded version.\n" +
"Usually Java web containers use 8080, it may be a good idea to use a different port to avoid \n" +
"conflicts with another servlet container.\n" +
"You can also use port 80 if you want to serve your API directly with the embedded server\n" +
"and no reverse proxy in front of it. But beware that you may need admin privileges for that.\n" +
"Examples: 8080, 8086, 8000, 80");
String basePath = shell.ask("base path [%s]? ", "/api",
descriptor.basePath = shell.ask("base path [%s]? ", "/api",
"This is the base API path on which RESTX will handle requests.\n" +
"Being focused on REST API only, RESTX is usually shared with either static or dynamic \n" +
"resources serving (HTML, CSS, JS, images, ...) and therefore is used to handle requests on\n" +
Expand All @@ -166,28 +183,48 @@ public void run(RestxShell shell) throws Exception {
"you can use '' (empty string) for this path.\n" +
"Examples: /api, /api/v2, /restx, ...");

ImmutableMap scope = ImmutableMap.builder()
.put("appName", appName)
.put("groupId", groupId)
.put("artifactId", artifactId)
.put("mainPackage", mainPackage)
.put("packagePath", mainPackage.replace('.', '/'))
.put("version", version)
.put("signatureKey", signatureKey)
.put("adminPassword", adminPassword)
.put("defaultPort", defaultPort)
.put("basePath", basePath)
.put("restxVersion", restxVersion)
.build();

boolean generateHelloResource = shell.askBoolean("generate hello resource example [Y/n]? ", "y",
descriptor.generateHelloResource = shell.askBoolean("generate hello resource example [Y/n]? ", "y",
"This will generate an example resource with an associated spec test so that your boostrapped\n" +
"application can be used as soon as it has been generated.\n" +
"If this is the first app you generate with RESTX, it's probably a good idea to generate\n" +
"this example resource.\n" +
"If you already know RESTX by heart you shouldn't be reading this message anyway :)");

Path appPath = shell.currentLocation().resolve(artifactId);
Path appPath = generateApp(descriptor, shell);

shell.cd(appPath);

if (shell.askBoolean("Do you want to install its deps and run it now? [Y/n]", "Y",
"By answering yes restx will resolve and install the dependencies of the app and run it.\n" +
"You can always install the deps later by using the `deps install` command\n" +
"and run the app with the `app run` command")) {

shell.println("restx> deps install");
new DepsShellCommand().new InstallDepsCommandRunner().run(shell);
shell.println("restx> app run");
new RunAppCommandRunner(Collections.<String>emptyList()).run(shell);
}
}

public Path generateApp(NewAppDescriptor descriptor, RestxShell shell) throws IOException {
boolean generateIvy = "ivy".equalsIgnoreCase(descriptor.buildFile) || "all".equalsIgnoreCase(descriptor.buildFile);
boolean generatePom = "pom".equalsIgnoreCase(descriptor.buildFile) || "all".equalsIgnoreCase(descriptor.buildFile);

ImmutableMap scope = ImmutableMap.builder()
.put("appName", descriptor.appName)
.put("groupId", descriptor.groupId)
.put("artifactId", descriptor.artifactId)
.put("mainPackage", descriptor.mainPackage)
.put("packagePath", descriptor.mainPackage.replace('.', '/'))
.put("version", descriptor.version)
.put("signatureKey", descriptor.signatureKey)
.put("adminPassword", descriptor.adminPassword)
.put("defaultPort", descriptor.defaultPort)
.put("basePath", descriptor.basePath)
.put("restxVersion", descriptor.restxVersion)
.build();

Path appPath = shell.currentLocation().resolve(descriptor.artifactId);

shell.println("scaffolding app to `" + appPath.toAbsolutePath() + "` ...");
generate(mainTemplates, appPath, scope);
Expand All @@ -201,26 +238,15 @@ public void run(RestxShell shell) throws Exception {
RestxBuild.convert(appPath.toAbsolutePath() + "/md.restx.json", appPath.toAbsolutePath() + "/pom.xml");
}

if (generateHelloResource) {
if (descriptor.generateHelloResource) {
shell.println("generating hello resource ...");
generate(helloResourceTemplates, appPath, scope);
}
shell.printIn("Congratulations! - Your app is now ready in " + appPath.toAbsolutePath(), RestxShell.AnsiCodes.ANSI_GREEN);
shell.println("");
shell.println("");

shell.cd(appPath);

if (shell.askBoolean("Do you want to install its deps and run it now? [Y/n]", "Y",
"By answering yes restx will resolve and install the dependencies of the app and run it.\n" +
"You can always install the deps later by using the `deps install` command\n" +
"and run the app with the `app run` command")) {

shell.println("restx> deps install");
new DepsShellCommand().new InstallDepsCommandRunner().run(shell);
shell.println("restx> app run");
new RunAppCommandRunner(Collections.<String>emptyList()).run(shell);
}
return appPath;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,56 @@
package {{mainPackage}};

import {{mainPackage}}.persistence.UserRepository;

import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import restx.SignatureKey;
import restx.factory.Module;
import restx.factory.Provides;
import restx.security.BasicPrincipalAuthenticator;
import restx.security.RestxPrincipal;
import restx.security.RestxSession;
import org.joda.time.Duration;
import javax.inject.Named;

@Module
public class AppModule {
@Provides
public SignatureKey signatureKey() {
return new SignatureKey("{{signatureKey}}".getBytes(Charsets.UTF_8));
}

@Provides
@Named("restx.admin.password")
public String restxAdminPassword() {
return "{{adminPassword}}";
}

@Provides
public SignatureKey signatureKey() {
return new SignatureKey("{{signatureKey}}".getBytes(Charsets.UTF_8));
@Named("app.name")
public String appName(){
return "{{appName}}";
}

@Provides
public BasicPrincipalAuthenticator basicPrincipalAuthenticator(final UserRepository userRepository) {
return new BasicPrincipalAuthenticator() {
@Override
public Optional<? extends RestxPrincipal> findByName(String name) {
return userRepository.findUserByName(name);
}

@Override
public Optional<? extends RestxPrincipal> authenticate(String name, String passwordHash, ImmutableMap<String, ?> principalData) {
boolean rememberMe = Boolean.valueOf((String) principalData.get("rememberMe"));
Optional<? extends RestxPrincipal> user = userRepository.findUserByNameAndPasswordHash(name, passwordHash);
if (user.isPresent()) {
RestxSession.current().expires(rememberMe ? Duration.standardDays(30) : Duration.ZERO);
}

return user;
}
};
}
}