Skip to content

Instantly share code, notes, and snippets.

@agrison
Last active September 29, 2016 06:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agrison/ed88896d5d7b4466f0934a3aa013ab19 to your computer and use it in GitHub Desktop.
Save agrison/ed88896d5d7b4466f0934a3aa013ab19 to your computer and use it in GitHub Desktop.
Monster Component in Java & Spring
package foo;
// imports skipped
/**
* @author @algrison
*/
@Getter // generate getters
@Setter // generate setters
@Aspect // we are an aspect
@ToString // generate toString()
@EnableWs // SOAP is so enterprisy, we definitely need it
@Endpoint // Seriously, just read above
@EnableWebMvc // we want MVC
@EnableCaching // and we want to cache stuff
@Configuration // this class can configure itself
@RestController // we want some REST
@XmlRootElement // this component is marshallable
@EnableWebSocket // we want web socket, it's so new-generation
@RedisHash("cat") // this class is an entity saved in redis
@EnableScheduling // we want scheduled tasks
@EnableWebSecurity // and some built-in security
@NoArgsConstructor // generate no args constructor
@ContextConfiguration // we want context configuration for unit testing
@SpringBootApplication // this is a Sprint Boot application
@Accessors(chain = true) // getters/setters are chained (ala jQuery)
@EnableAspectJAutoProxy // we want AspectJ auto proxy
@EnableAutoConfiguration // and auto configuration
@EnableRedisRepositories // since it is an entity we want to enable spring data repositories for redis
@EnableWebSocketMessageBroker // we want a broker for web socket messages
@ComponentScan(basePackages = "foo") // we may scan for additional components in package "foo"
@EqualsAndHashCode(callSuper = false) // generate equals() and hashCode()
@Scope(proxyMode = ScopedProxyMode.NO) // Nope
@RunWith(SpringJUnit4ClassRunner.class) // we are also a unit test, but we need specific bootstrapping for Spring
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) // the Spring context could get dirty please clean up
public class Cat extends AbstractWebSocketMessageBrokerConfigurer {
// ---------- model stuff ----------
@Id
private String id;
@Indexed
private String url;
@Autowired
@JsonIgnore
KeyValueRepository<Cat, String> repository;
// ---------- redis ----------
@Configuration
@EnableCaching
public static class Redis extends CachingConfigurerSupport {
@Bean(initMethod = "start", destroyMethod = "stop")
public RedisServer redisServer() throws IOException {
return new RedisServer();
}
@Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> tpl = new RedisTemplate<>();
tpl.setConnectionFactory(connectionFactory());
return tpl;
}
@Bean
public KeyValueRepository<Cat, String> crazyRepository() {
// this is some ugly stuff to create some kind of Spring Data repository with no interface
return new SimpleKeyValueRepository<>(new ReflectionEntityInformation<Cat, String>(Cat.class), //
new KeyValueTemplate(new RedisKeyValueAdapter(redisTemplate())));
}
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(300);
return cacheManager;
}
}
// ---------- security ----------
@Configuration
@EnableWebSecurity
public static class Security extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/ws");
http.authorizeRequests().antMatchers("/ws").anonymous();
http.authorizeRequests().antMatchers("/*").authenticated().and().formLogin();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
// ---------- SOAP ----------
@EnableWs
@Configuration
public static class Soap extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name = "cats")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CatThingPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://ws.foo");
wsdl11Definition.setSchema(schema);
return wsdl11Definition;
}
@Bean
public XsdSchema schema() {
return new SimpleXsdSchema(new ClassPathResource("cat.xsd"));
}
}
// ---------- SOAP endpoint ----------
@ResponsePayload
@PayloadRoot(namespace = "http://ws.foo", localPart = "getRandomCatRequest")
public GetRandomCatResponse getRandomCat(@RequestPayload GetRandomCatRequest request) {
GetRandomCatResponse response = new GetRandomCatResponse();
List<Cat> cats = IteratorUtils.toList(listCats().iterator());
WsCat cat = new WsCat();
Cat random = cats.get(new Random().nextInt(cats.size()));
BeanUtils.copyProperties(random, cat);
response.setCat(cat);
return response;
}
// ---------- AOP ----------
@Around("execution(* org.springframework.data.repository.CrudRepository.*(..))")
public Object aroundRepository(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Someone's calling a repository " + //
pjp.getSignature().getName() + "(" + Arrays.toString(pjp.getArgs()) + ")");
return pjp.proceed();
}
// ---------- internal stuff ----------
@Cacheable("cats")
public Iterable<Cat> listCats() {
return repository.findAll();
}
@CacheEvict("cats")
public Cat addCat(String url) {
Cat cat = new Cat().setId(UUID.randomUUID().toString()).setUrl(url);
return repository.save(cat);
}
// ---------- scheduling ----------
@Scheduled(fixedRate = 30000)
public void generateSomeCat() throws IOException {
Map<String, String> map = new HashMap<String, String>();
map = new Gson().fromJson(IOUtils.toString( //
new URL("http://random.cat/meow").openStream()), map.getClass());
addCat(map.get("file"));
}
// ---------- MVC ----------
@RequestMapping("/")
public String homeView() {
return view("Listing cats", //
"<table class=table><thead><tr><th>ID<th>Cat<tbody>" + //
StreamSupport.stream(listCats().spliterator(), false) //
.map(c -> "<tr><td>" + c.id + "<td><img src=\"" + c.url + "\" width=\"350\"/>") //
.collect(Collectors.joining()) + "</table>", true);
}
@RequestMapping("/new")
public String newCatView(HttpServletRequest request) {
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
return view("Add a cat", //
"<form method=post name=cat action=\"/new\">" + //
"<label for=url>URL:</label><input id=url name=url class=\"form-control\"/>" + //
"<input type=submit class=\"btn btn-info\"/> <input type=hidden name=\"" + csrfToken.getParameterName() + "\" value=\"" + csrfToken.getToken() + "\"/>",
false);
}
@RequestMapping(value = "/new", method = RequestMethod.POST)
public String newCat(@ModelAttribute("cat") Cat cat) {
addCat(cat.url);
return homeView();
}
// ---------- REST ----------
@RequestMapping(value = "/api/cats", method = RequestMethod.GET)
public Iterable<Cat> restList() {
return listCats();
}
@RequestMapping(value = "/api/cats/{id}", method = RequestMethod.GET)
public Cat restFind(@PathVariable String id) {
return repository.findOne(id);
}
@RequestMapping(value = "/api/cats", method = RequestMethod.POST)
public Cat restAdd(@RequestBody Cat cat) {
return addCat(cat.getUrl());
}
// ---------- HTML view ----------
public String view(String title, String content, boolean websocket) {
String bootstrap = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css";
return String.format("<!DOCTYPE html><title>%s</title><link href=\"%s\" rel=\"stylesheet\">" +
"<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.1/sockjs.min.js\"></script>" +
"<script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js\"></script>" +
"<nav class=\"navbar navbar-inverse navbar-fixed-top\">" +
" <div class=\"container\">" +
" <div class=\"navbar-header\">" +
" <button type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#navbar\">" +
" <span class=\"sr-only\">Toggle navigation</span>" +
" <span class=\"icon-bar\"></span>" +
" <span class=\"icon-bar\"></span>" +
" <span class=\"icon-bar\"></span>" +
" </button>" +
" <a class=\"navbar-brand\" href=\"#\"><b>Cat</b>astrophic</a>" +
" </div>" +
" <div id=\"navbar\" class=\"collapse navbar-collapse\">" +
" <ul class=\"nav navbar-nav\">" +
" <li class=\"active\"><a href=\"/\">Home</a></li>" +
" <li><a href=\"/new\">New cat</a></li>" +
" <li><a href=\"https://twitter.com/algrison\">@algrison</a></li>" +
" </ul>" +
" </div>" +
" </div>" +
"</nav>" +
"<div class=\"container\" style=\"margin-top: 80px\">" +
" <div class=\"starter-template\">" +
" <p class=\"lead\">%s</p>" +
" </div>" +
"%s\n" +
(websocket ? ("<hr/>" +
"<h2>WebSocket</h2>" +
"<div class=\"row\">\n" +
" <button id=\"connect\" class=\"btn btn-success\" onclick=\"connect();\">Connect</button>\n" +
" <button id=\"disconnect\" class=\"btn btn-danger\" disabled=\"disabled\" onclick=\"disconnect();\">Disconnect</button><br/><br/>\n" +
" <button id=\"sendNum\" class=\"btn btn-info\" onclick=\"sendList();\">List cats</button>" +
" </div>" +
"<br/><br/><br/><pre id=\"ws\"></pre>" +
"<script type=\"text/javascript\">\n" +
" var stompClient = null; \n" +
" function setConnected(connected) {\n" +
" document.getElementById('connect').disabled = connected;\n" +
" document.getElementById('disconnect').disabled = !connected;\n" +
" document.getElementById('ws').innerHTML = '';\n" +
" }\n" +
" function connect() {\n" +
" var socket = new SockJS('/list');\n" +
" stompClient = Stomp.over(socket);\n" +
" stompClient.connect({}, function(frame) {\n" +
" setConnected(true);\n" +
" showResult('Connected! - ' + frame);" +
" stompClient.subscribe('/topic/cats', function(result){\n" +
" showResult(result.body);\n" +
" });\n" +
" });\n" +
" }\n" +
" function disconnect() {\n" +
" stompClient.disconnect();\n" +
" setConnected(false);\n" +
" console.log(\"Disconnected\");\n" +
" }\n" +
" function sendList() {\n" +
" stompClient.send(\"/cats/list\", {}, JSON.stringify({'hello': 'world'}));\n" +
" }\n" +
" function showResult(message) {\n" +
" var response = document.getElementById('ws');\n" +
" var p = document.createElement('p');\n" +
" p.style.wordWrap = 'break-word';\n" +
" p.appendChild(document.createTextNode(message));\n" +
" response.appendChild(p);\n" +
" }\n" +
" </script>") : ""), //
title, bootstrap, title, content);
}
// ---------- PostContruct/PreDestroy ----------
@PostConstruct
private void prepare() {
System.out.println("Hello");
}
@PreDestroy
private void release() {
System.out.println("Bye");
}
// ---------- WebSocket ----------
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/cats");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/list").withSockJS();
}
@MessageMapping("/list")
@SendTo("/topic/cats")
public Iterable<Cat> wsList() throws Exception {
return listCats();
}
// ---------- Main entry point ----------
public static void main(String[] args) {
SpringApplication.run(Cat.class, args); // run the whole stuff
}
// ---------- Testing ----------
@Test
public void testInsert() {
Cat cat = addCat("foo");
Assert.assertThat(repository.findOne(cat.id), CoreMatchers.notNullValue());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment