Skip to content

Commit

Permalink
Dynamic javascript library
Browse files Browse the repository at this point in the history
Visiting /jsclient/{library} (currently supports jquery) will yield a dynamically generated javascript library for interacting with the REST endpoints.
  • Loading branch information
gregturn committed Mar 20, 2014
1 parent 063cebe commit 4e5ecdb
Show file tree
Hide file tree
Showing 20 changed files with 371 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public void merge(Object from, Object target, final NullHandlingPolicy nullPolic
return;
}

final BeanWrapper<?, Object> fromWrapper = BeanWrapper.create(from, conversionService);
final BeanWrapper<?, Object> targetWrapper = BeanWrapper.create(target, conversionService);
final BeanWrapper<Object> fromWrapper = BeanWrapper.create(from, conversionService);
final BeanWrapper<Object> targetWrapper = BeanWrapper.create(target, conversionService);
final PersistentEntity<?, ?> entity = repositories.getPersistentEntity(target.getClass());

entity.doWithProperties(new SimplePropertyHandler() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class AbstractRepositoryRestController implements MessageSourceAware {

public AbstractRepositoryRestController(PagedResourcesAssembler<Object> pagedResourcesAssembler) {
this.pagedResourcesAssembler = pagedResourcesAssembler;
LOG.error("Wiring up " + this.getClass().getSimpleName());
}

/*
Expand All @@ -83,6 +84,8 @@ public void setMessageSource(MessageSource messageSource) {
@ExceptionHandler({ NullPointerException.class })
@ResponseBody
public ResponseEntity<?> handleNPE(NullPointerException npe) {
LOG.error("7) Couldn't find it! => " + npe.getMessage());

return errorResponse(npe, HttpStatus.INTERNAL_SERVER_ERROR);
}

Expand All @@ -108,6 +111,8 @@ public ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadable
ConversionFailedException.class })
@ResponseBody
public ResponseEntity handleMiscFailures(Throwable t) {
LOG.error("5) Couldn't find it! => " + t.getMessage());

if (null != t.getCause() && t.getCause() instanceof ResourceNotFoundException) {
return notFound();
}
Expand All @@ -132,6 +137,7 @@ public ResponseEntity handleRepositoryConstraintViolationException(Locale locale
@ExceptionHandler({ OptimisticLockingFailureException.class, DataIntegrityViolationException.class })
@ResponseBody
public ResponseEntity handleConflict(Exception ex) {
LOG.error("6) Couldn't find it! => " + ex);
return errorResponse(null, ex, HttpStatus.CONFLICT);
}

Expand All @@ -152,14 +158,17 @@ public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
}

protected <T> ResponseEntity<T> notFound() {
LOG.error("1) Couldn't find it!");
return notFound(null, null);
}

protected <T> ResponseEntity<T> notFound(HttpHeaders headers, T body) {
LOG.error("2) Couldn't find it!");
return response(headers, body, HttpStatus.NOT_FOUND);
}

protected <T extends Throwable> ResponseEntity<ExceptionMessage> badRequest(T throwable) {
LOG.error("3) Couldn't find it!");
return badRequest(null, throwable);
}

Expand All @@ -168,6 +177,7 @@ protected <T extends Throwable> ResponseEntity<ExceptionMessage> badRequest(Http
}

public <T extends Throwable> ResponseEntity<ExceptionMessage> errorResponse(T throwable, HttpStatus status) {
LOG.error("4) Couldn't find it!");
return errorResponse(null, throwable, status);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package org.springframework.data.rest.webmvc;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@RepositoryRestController
public class JavascriptClientController extends AbstractRepositoryRestController {

public static final MediaType APPLICATION_JAVASCRIPT = MediaType.valueOf("application/javascript");
public static final String BASE_MAPPING = "/jsclient";
private static final Logger LOG = LoggerFactory.getLogger(JavascriptClientController.class);

private final Repositories repositories;
private final EntityLinks entityLinks;
private final RepositoryResourceMappings mappings;

@Autowired
public JavascriptClientController(PagedResourcesAssembler<Object> assembler, Repositories repositories,
EntityLinks entityLinks, RepositoryResourceMappings mappings) {

super(assembler);

this.repositories = repositories;
this.entityLinks = entityLinks;
this.mappings = mappings;
}

@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
public ResourceSupport listSdks() {

LOG.error("Trying to look something up...");

ResourceSupport resourceSupport = new ResourceSupport();
resourceSupport.add(new Link(BASE_MAPPING + "/{library}", "library"));
return resourceSupport;
}

@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{library}", method = RequestMethod.GET, produces = "application/javascript")
public String createJavascriptRepository(
@PathVariable String library) throws IOException {

List<String> modules = new ArrayList<String>();

for (Class<?> domainType : repositories) {
if (mappings.exportsMappingFor(domainType)) {
LOG.error("Building " + library + " for " + domainType.getName() + "...");

List<String> functions = new ArrayList<String>();

final String url = mappings.getMappingFor(domainType).getPath().toString();

for (String file : new String[]{"/findAll.js", "/post.js", "/put.js", "/patch.js", "/delete.js"}) {
final String code = loadJavascriptFunction(library + file, url);
functions.add(StringUtils.trimTrailingWhitespace(indent(code)));
}

for (MethodResourceMapping method :
mappings.getSearchResourceMappings(domainType)) {
if (method.getParameterNames().size() > 0) {
final List<String> javascriptParams = new ArrayList<String>();
for (String paramName : method.getParameterNames()) {
javascriptParams.add(paramName + ": " + paramName);
}

final String code = loadJavascriptFunction(library + "/findBy.js", url)
.replace("__ARGS__", StringUtils.
collectionToCommaDelimitedString(method.getParameterNames()))
.replace("__METHOD__", method.getMethod().getName())
.replace("__INPUTS__", StringUtils.
collectionToCommaDelimitedString(javascriptParams));

functions.add(StringUtils.trimTrailingWhitespace(indent(code)));
} else {
throw new RuntimeException(method.getMethod().getName() + " doesn't have any args!");
}
}

String module = StringUtils.trimTrailingWhitespace(indent(StringUtils.uncapitalize(domainType.getSimpleName()) + "Repository: {\n" +
StringUtils.collectionToDelimitedString(functions, ",\n") +
"\n}"));
modules.add(module);
}
}

String javascript = "var sdr = {\n" +
StringUtils.collectionToDelimitedString(modules, ",\n")
+ "\n};";

return javascript;
}

/**
* Embed four spaces in front of each line. This can be run more than once if need be.
* @param code
* @return
*/
private String indent(String code) {

String indentedCode = "";
for (String line : code.split("\n")) {
indentedCode += " " + line + "\n";
}
return indentedCode;
}

/**
* Load a javascript code template and plug in the URL
* @param filename
* @param url
* @return
* @throws IOException
*/
private String loadJavascriptFunction(String filename, String url) throws IOException {

ClassPathResource resource = new ClassPathResource(filename, JavascriptClientController.class);
final String code = StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
return code.replace("__URL__", url);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class PersistentEntityResourceAssembler implements ResourceAssembler<Obje
*
* @param repositories must not be {@literal null}.
* @param entityLinks must not be {@literal null}.
* @param projections must not be {@literal null}.
* @param projector must not be {@literal null}.
*/
public PersistentEntityResourceAssembler(Repositories repositories, EntityLinks entityLinks, Projector projector) {

Expand Down Expand Up @@ -82,7 +82,7 @@ public Link getSelfLinkFor(Object instance) {
instanceType));
}

BeanWrapper<?, Object> wrapper = BeanWrapper.create(instance, null);
BeanWrapper<Object> wrapper = BeanWrapper.create(instance, null);
Object id = wrapper.getProperty(entity.getIdProperty());

Link resourceLink = entityLinks.linkToSingleResource(entity.getType(), id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package org.springframework.data.rest.webmvc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
Expand All @@ -32,6 +35,8 @@
@RepositoryRestController
public class RepositoryController extends AbstractRepositoryRestController {

private static final Logger LOG = LoggerFactory.getLogger(RepositoryController.class);

private final Repositories repositories;
private final EntityLinks entityLinks;
private final ResourceMappings mappings;
Expand All @@ -54,7 +59,9 @@ public RepositoryController(PagedResourcesAssembler<Object> assembler, Repositor
*/
@ResponseBody
@RequestMapping(value = "/", method = RequestMethod.GET)
public RepositoryLinksResource listRepositories() {
public RepositoryLinksResource listRepositories() throws NoSuchMethodException {

LOG.error("Trying to look something up...");

RepositoryLinksResource resource = new RepositoryLinksResource();

Expand All @@ -66,6 +73,10 @@ public RepositoryLinksResource listRepositories() {
}
}

//resource.add(JavascriptClientController.class.getMethod("listSdks"));


return resource;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -71,6 +73,8 @@
class RepositoryEntityController extends AbstractRepositoryRestController implements ApplicationEventPublisherAware {

private static final String BASE_MAPPING = "/{repository}";
private static final Logger LOG = LoggerFactory.getLogger(RepositoryEntityController.class);


private final EntityLinks entityLinks;
private final RepositoryRestConfiguration config;
Expand Down Expand Up @@ -107,6 +111,8 @@ public Resources<?> getCollectionResource(final RootResourceInformation resource
Sort sort, PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
HttpRequestMethodNotSupportedException {

LOG.error("Trying to look something up...");

resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION);

RepositoryInvoker invoker = resourceInformation.getInvoker();
Expand Down Expand Up @@ -145,6 +151,8 @@ public Resources<?> getCollectionResourceCompact(RootResourceInformation repoReq
PersistentEntityResourceAssembler assembler) throws ResourceNotFoundException,
HttpRequestMethodNotSupportedException {

LOG.error("Trying to look something up...");

Resources<?> resources = getCollectionResource(repoRequest, pageable, sort, assembler);
List<Link> links = new ArrayList<Link>(resources.getLinks());

Expand Down Expand Up @@ -229,7 +237,7 @@ public ResponseEntity<? extends ResourceSupport> putItemResource(RootResourceInf

if (domainObject == null) {

BeanWrapper<?, Object> incomingWrapper = BeanWrapper.create(payload.getContent(), conversionService);
BeanWrapper<Object> incomingWrapper = BeanWrapper.create(payload.getContent(), conversionService);
incomingWrapper.setProperty(payload.getPersistentEntity().getIdProperty(), id);

return createAndReturn(incomingWrapper.getBean(), invoker, assembler);
Expand Down
Loading

0 comments on commit 4e5ecdb

Please sign in to comment.