Merge pull request #5562 from psychsane/master

[BAEL-1995] Add new module for restx-demo
This commit is contained in:
Tom Hombergs 2018-11-28 20:44:45 +01:00 committed by GitHub
commit 4fe2f0866f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 597 additions and 0 deletions

View File

@ -1714,6 +1714,7 @@
<module>persistence-modules/spring-data-elasticsearch</module> <module>persistence-modules/spring-data-elasticsearch</module>
<module>core-java-concurrency</module> <module>core-java-concurrency</module>
<module>core-java-concurrency-collections</module> <module>core-java-concurrency-collections</module>
<module>restx</module>
</modules> </modules>
</profile> </profile>

View File

@ -0,0 +1,12 @@
{
"//": "lines with // keys are just comments (we don't have real comments in json)",
"//": "this file stores password passed through md5+bcrypt hash",
"//": "you can use `restx hash md5+bcrypt {password}` shell command to get hashed passwords to put here",
"//": "to help startup with restx, there are comments with clear text passwords,",
"//": "which should obviously not be stored here.",
"user1": "$2a$10$iZluFUJShbjb1ue68bLrDuGCeJL9EMLHelVIf8u0SUbCseDOvKnoe",
"//": "user 1 password is 'user1-pwd'",
"user2": "$2a$10$oym3SYMFXf/9gGfDKKHO4eM1vWNqAZMsRZCL.BORCaP4yp5cdiCXu",
"//": "user 2 password is 'user2-pwd'"
}

4
restx/data/users.json Normal file
View File

@ -0,0 +1,4 @@
[
{"name":"user1", "roles": ["hello"]},
{"name":"user2", "roles": []}
]

38
restx/md.restx.json Normal file
View File

@ -0,0 +1,38 @@
{
"module": "restx-demo:restx-demo:0.1-SNAPSHOT",
"packaging": "war",
"properties": {
"java.version": "1.8",
"restx.version": "0.35-rc4"
},
"fragments": {
"maven": [
"classpath:///restx/build/fragments/maven/javadoc-apidoclet.xml" ]
},
"dependencies": {
"compile": [
"io.restx:restx-core:${restx.version}",
"io.restx:restx-security-basic:${restx.version}",
"io.restx:restx-core-annotation-processor:${restx.version}",
"io.restx:restx-factory:${restx.version}",
"io.restx:restx-factory-admin:${restx.version}",
"io.restx:restx-validation:${restx.version}",
"io.restx:restx-monitor-codahale:${restx.version}",
"io.restx:restx-monitor-admin:${restx.version}",
"io.restx:restx-log-admin:${restx.version}",
"io.restx:restx-i18n-admin:${restx.version}",
"io.restx:restx-stats-admin:${restx.version}",
"io.restx:restx-servlet:${restx.version}",
"io.restx:restx-server-jetty8:${restx.version}!optional",
"io.restx:restx-apidocs:${restx.version}",
"io.restx:restx-specs-admin:${restx.version}",
"io.restx:restx-admin:${restx.version}",
"ch.qos.logback:logback-classic:1.0.13"
],
"test": [
"io.restx:restx-specs-tests:${restx.version}",
"junit:junit:4.11"
]
}
}

155
restx/pom.xml Normal file
View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>restx</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>restx-demo</name>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<restx.version>0.35-rc4</restx.version>
</properties>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-core</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-security-basic</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-core-annotation-processor</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-factory</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-factory-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-validation</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-monitor-codahale</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-monitor-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-log-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-i18n-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-stats-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-servlet</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-server-jetty8</artifactId>
<version>${restx.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-apidocs</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-specs-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-admin</artifactId>
<version>${restx.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>io.restx</groupId>
<artifactId>restx-specs-tests</artifactId>
<version>${restx.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-docs</id>
<!--
we generate javadoc before packaging the jar to let a chance to apidocs doclet
to generate comments dictionary to be packaged inside the jar as a resource
-->
<phase>prepare-package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<source>${maven.compiler.source}</source>
<doclet>restx.apidocs.doclet.ApidocsDoclet</doclet>
<docletArtifact>
<groupId>io.restx</groupId>
<artifactId>restx-apidocs-doclet</artifactId>
<version>${restx.version}</version>
</docletArtifact>
<additionalparam>-restx-target-dir ${project.basedir}/target/classes</additionalparam>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,74 @@
package restx.demo;
import restx.config.ConfigLoader;
import restx.config.ConfigSupplier;
import restx.factory.Provides;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
import restx.security.*;
import restx.factory.Module;
import restx.factory.Provides;
import javax.inject.Named;
import java.nio.file.Paths;
@Module
public class AppModule {
@Provides
public SignatureKey signatureKey() {
return new SignatureKey("restx-demo -447494532235718370 restx-demo 801c9eaf-4116-48f2-906b-e979fba72757".getBytes(Charsets.UTF_8));
}
@Provides
@Named("restx.admin.password")
public String restxAdminPassword() {
return "4780";
}
@Provides
public ConfigSupplier appConfigSupplier(ConfigLoader configLoader) {
// Load settings.properties in restx.demo package as a set of config entries
return configLoader.fromResource("restx/demo/settings");
}
@Provides
public CredentialsStrategy credentialsStrategy() {
return new BCryptCredentialsStrategy();
}
@Provides
public BasicPrincipalAuthenticator basicPrincipalAuthenticator(
SecuritySettings securitySettings, CredentialsStrategy credentialsStrategy,
@Named("restx.admin.passwordHash") String defaultAdminPasswordHash, ObjectMapper mapper) {
return new StdBasicPrincipalAuthenticator(new StdUserService<>(
// use file based users repository.
// Developer's note: prefer another storage mechanism for your users if you need real user management
// and better perf
new FileBasedUserRepository<>(
StdUser.class, // this is the class for the User objects, that you can get in your app code
// with RestxSession.current().getPrincipal().get()
// it can be a custom user class, it just need to be json deserializable
mapper,
// this is the default restx admin, useful to access the restx admin console.
// if one user with restx-admin role is defined in the repository, this default user won't be
// available anymore
new StdUser("admin", ImmutableSet.<String>of("*")),
// the path where users are stored
Paths.get("data/users.json"),
// the path where credentials are stored. isolating both is a good practice in terms of security
// it is strongly recommended to follow this approach even if you use your own repository
Paths.get("data/credentials.json"),
// tells that we want to reload the files dynamically if they are touched.
// this has a performance impact, if you know your users / credentials never change without a
// restart you can disable this to get better perfs
true),
credentialsStrategy, defaultAdminPasswordHash),
securitySettings);
}
}

View File

@ -0,0 +1,32 @@
package restx.demo;
import com.google.common.base.Optional;
import restx.server.WebServer;
import restx.server.Jetty8WebServer;
/**
* This class can be used to run the app.
*
* Alternatively, you can deploy the app as a war in a regular container like tomcat or jetty.
*
* Reading the port from system env PORT makes it compatible with heroku.
*/
public class AppServer {
public static final String WEB_INF_LOCATION = "src/main/webapp/WEB-INF/web.xml";
public static final String WEB_APP_LOCATION = "src/main/webapp";
public static void main(String[] args) throws Exception {
int port = Integer.valueOf(Optional.fromNullable(System.getenv("PORT")).or("8080"));
WebServer server = new Jetty8WebServer(WEB_INF_LOCATION, WEB_APP_LOCATION, port, "0.0.0.0");
/*
* load mode from system property if defined, or default to dev
* be careful with that setting, if you use this class to launch your server in production, make sure to launch
* it with -Drestx.mode=prod or change the default here
*/
System.setProperty("restx.mode", System.getProperty("restx.mode", "dev"));
System.setProperty("restx.app.package", "restx.demo");
server.startAndAwait();
}
}

View File

@ -0,0 +1,10 @@
package restx.demo;
/**
* A list of roles for the application.
*
* We don't use an enum here because it must be used inside an annotation.
*/
public final class Roles {
public static final String HELLO_ROLE = "hello";
}

View File

@ -0,0 +1,21 @@
package restx.demo.domain;
public class Message {
private String message;
public String getMessage() {
return message;
}
public Message setMessage(final String message) {
this.message = message;
return this;
}
@Override
public String toString() {
return "Message{" +
"message='" + message + '\'' +
'}';
}
}

View File

@ -0,0 +1,62 @@
package restx.demo.rest;
import restx.demo.domain.Message;
import restx.demo.Roles;
import org.joda.time.DateTime;
import restx.annotations.GET;
import restx.annotations.POST;
import restx.annotations.RestxResource;
import restx.factory.Component;
import restx.security.PermitAll;
import restx.security.RolesAllowed;
import restx.security.RestxSession;
import javax.validation.constraints.NotNull;
@Component @RestxResource
public class HelloResource {
/**
* Say hello to currently logged in user.
*
* Authorized only for principals with Roles.HELLO_ROLE role.
*
* @return a Message to say hello
*/
@GET("/message")
@RolesAllowed(Roles.HELLO_ROLE)
public Message sayHello() {
return new Message().setMessage(String.format(
"hello %s, it's %s",
RestxSession.current().getPrincipal().get().getName(),
DateTime.now().toString("HH:mm:ss")));
}
/**
* Say hello to anybody.
*
* Does not require authentication.
*
* @return a Message to say hello
*/
@GET("/hello")
@PermitAll
public Message helloPublic(String who) {
return new Message().setMessage(String.format(
"hello %s, it's %s",
who, DateTime.now().toString("HH:mm:ss")));
}
public static class MyPOJO {
@NotNull
String value;
public String getValue(){ return value; }
public void setValue(String value){ this.value = value; }
}
@POST("/mypojo")
@PermitAll
public MyPOJO helloPojo(MyPOJO pojo){
pojo.setValue("hello "+pojo.getValue());
return pojo;
}
}

View File

@ -0,0 +1,94 @@
<configuration>
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>true</resetJUL>
</contextListener>
<property name="LOGS_FOLDER" value="${logs.base:-logs}" />
<appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOGS_FOLDER}/errors.log</File>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOGS_FOLDER}/errors.%d.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<if condition='p("restx.mode").equals("prod")'>
<then>
<!-- production mode -->
<appender name="appLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOGS_FOLDER}/app.log</File>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOGS_FOLDER}/app.%d.log</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
</appender>
<appender name="debugFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOGS_FOLDER}/debug.log</File>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${LOGS_FOLDER}/debug.%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>50MB</maxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="debugFile" />
<appender-ref ref="appLog" />
</root>
</then>
<else>
<!-- not production mode -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="appLog" class="ch.qos.logback.core.FileAppender">
<File>${LOGS_FOLDER}/app.log</File>
<encoder>
<pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="appLog" />
</root>
</else>
</if>
<!-- clean up container logs -->
<logger name="org.eclipse.jetty.server.AbstractConnector" level="WARN" />
<logger name="org.eclipse.jetty.server.handler.ContextHandler" level="WARN" />
<logger name="org.eclipse.jetty.webapp.StandardDescriptorProcessor" level="WARN" />
<logger name="org.hibernate.validator.internal.engine.ConfigurationImpl" level="WARN" />
<logger name="org.reflections.Reflections" level="WARN" />
<logger name="restx.factory.Factory" level="WARN" />
<!-- app logs - set DEBUG level, in prod it will go to a dedicated file -->
<logger name="restx.demo" level="DEBUG" />
<root level="INFO">
<appender-ref ref="errorFile" />
</root>
</configuration>

View File

@ -0,0 +1 @@
app.name=restx-demo

View File

@ -0,0 +1,15 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0" metadata-complete="true">
<servlet>
<servlet-name>restx</servlet-name>
<servlet-class>restx.servlet.RestxMainRouterServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restx</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -0,0 +1,23 @@
package restx.demo.rest;
import org.junit.runner.RunWith;
import restx.tests.FindSpecsIn;
import restx.tests.RestxSpecTestsRunner;
@RunWith(RestxSpecTestsRunner.class)
@FindSpecsIn("specs/hello")
public class HelloResourceSpecUnitTest {
/**
* Useless, thanks to both @RunWith(RestxSpecTestsRunner.class) & @FindSpecsIn()
*
* @Rule
* public RestxSpecRule rule = new RestxSpecRule();
*
* @Test
* public void test_spec() throws Exception {
* rule.runTest(specTestPath);
* }
*/
}

View File

@ -0,0 +1,10 @@
title: should admin say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
- uuids: [ "e2b4430f-9541-4602-9a3a-413d17c56a6b" ]
wts:
- when: |
GET message
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"admin","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
then: |
{"message":"hello admin, it's 01:18:00"}

View File

@ -0,0 +1,8 @@
title: should admin say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
wts:
- when: |
GET hello?who=xavier
then: |
{"message":"hello xavier, it's 01:18:00"}

View File

@ -0,0 +1,17 @@
title: should missing post value triggers a validation error
given:
- time: 2013-08-28T01:18:00.822+02:00
- uuids: [ "e2b4430f-9541-4602-9a3a-413d17c56a6b" ]
wts:
- when: |
POST mypojo
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"user1","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
{}
then: |
400
- when: |
POST mypojo
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"user1","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
{"value":"world"}
then: |
{"value":"hello world"}

View File

@ -0,0 +1,10 @@
title: should user1 say hello
given:
- time: 2013-08-28T01:18:00.822+02:00
- uuids: [ "e2b4430f-9541-4602-9a3a-413d17c56a6b" ]
wts:
- when: |
GET message
$RestxSession: {"_expires":"2013-09-27T01:18:00.822+02:00","principal":"user1","sessionKey":"e2b4430f-9541-4602-9a3a-413d17c56a6b"}
then: |
{"message":"hello user1, it's 01:18:00"}

View File

@ -0,0 +1,10 @@
title: should user2 not say hello
given:
- time: 2013-08-28T01:19:44.770+02:00
- uuids: [ "56f71fcc-42d3-422f-9458-8ad37fc4a0b5" ]
wts:
- when: |
GET message
$RestxSession: {"_expires":"2013-09-27T01:19:44.770+02:00","principal":"user2","sessionKey":"56f71fcc-42d3-422f-9458-8ad37fc4a0b5"}
then: |
403