package io.github.infolis.datastore; import io.github.infolis.InfolisConfig; import io.github.infolis.model.BaseModel; import io.github.infolis.model.ErrorResponse; import io.github.infolis.util.SerializationUtils; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLEncoder; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.BadRequestException; import javax.ws.rs.ProcessingException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.CollectionLikeType; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; /** * Client to access the Linked Data frontend web services. * * @author kba * */ class CentralClient extends AbstractClient { private Logger log = LoggerFactory.getLogger(CentralClient.class); /* * <pre> * Add mappings from class name to uri fragment here, e.g. * http://infolis-frontend/api/infolisFile/124 * \__/ * map this * </pre> */ private static Map<Class<?>, String> uriForClass = new ImmutableMap.Builder<Class<?>, String>() // .put(InfolisFile.class, "file") .build(); public static String getEndpointForClass(Class<?> clazz) { // if explicitly mapped if (uriForClass.containsKey(clazz)) { return uriForClass.get(clazz); } // otherwise use lcfirst-version of simplename ("QueryService" -> "queryService") String name = clazz.getSimpleName(); char c[] = name.toCharArray(); c[0] = Character.toLowerCase(c[0]); return new String(c); } private static <T extends BaseModel> String getUriForClass(Class<T> clazz) { if (uriForClass.containsKey(clazz)) { return uriForClass.get(clazz); } else { String name = clazz.getSimpleName(); return name.substring(0,1).toLowerCase() + name.substring(1); } } private static Client jerseyClient = ClientBuilder.newBuilder() .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) .register(JacksonFeature.class) .register(MultiPartFeature.class) .register(JacksonJsonProvider.class) .build(); @Override public <T extends BaseModel> void post(Class<T> clazz, T thing) throws BadRequestException { WebTarget target = jerseyClient .target(InfolisConfig.getFrontendURI()) .path(CentralClient.getUriForClass(clazz)); Entity<T> entity = Entity .entity(thing, MediaType.APPLICATION_JSON_TYPE); Response resp = target .request(MediaType.APPLICATION_JSON_TYPE) .post(entity); log.debug("-> {}", target); log.debug("<- HTTP {}", resp.getStatus()); if (resp.getStatus() >= 400) { String err = resp.readEntity(String.class); log.error(err); throw new RuntimeException(err); } else { thing.setUri(resp.getHeaderString("Location")); log.debug("URI of Posted {}: {}", clazz.getSimpleName(), thing.getUri()); } } public <T extends BaseModel> void put(Class<T> clazz, T thing) { String thingURI = thing.getUri(); if (null == thingURI) { throw new IllegalArgumentException("PUT requires a URI: " + thing); } WebTarget target = jerseyClient.target(URI.create(thingURI)); Entity<T> entity = Entity .entity(thing, MediaType.APPLICATION_JSON_TYPE); Response resp = target .request(MediaType.APPLICATION_JSON_TYPE) .put(entity); if (resp.getStatus() >= 400) { // TODO check whether resp actually succeeded // ErrorResponse err = resp.readEntity(ErrorResponse.class); // log.error(err.getMessage()); // log.error(Arrays.toString(err.getCause().entrySet().toArray())); throw new BadRequestException(resp); } } public <T extends BaseModel> void put(Class<T> clazz, T thing, String uri) { String id = uri; WebTarget target = jerseyClient.target(id); Entity<T> entity = Entity .entity(thing, MediaType.APPLICATION_JSON_TYPE); Response resp = target .request(MediaType.APPLICATION_JSON_TYPE) .put(entity); if (resp.getStatus() >= 400) { // TODO check whether resp actually succeeded // ErrorResponse err = resp.readEntity(ErrorResponse.class); // log.error(err.getMessage()); // log.error(Arrays.toString(err.getCause().entrySet().toArray())); throw new BadRequestException(resp); } } @Override public <T extends BaseModel> T get(Class<T> clazz, String uriStr) { URI uri = URI.create(uriStr); log.debug("GET from {}", uri); if (!uri.isAbsolute()) { String msg = "URI must be absolute, " + uri + " is NOT."; log.error(msg); throw new ProcessingException(msg); } WebTarget target = jerseyClient.target(uri); Response resp = target.request(MediaType.APPLICATION_JSON_TYPE).get(); log.debug("-> HTTP {}", resp.getStatus()); if (resp.getStatus() == 404) { throw new RuntimeException(String.format("No such '%s': '%s'", clazz.getSimpleName(), target.getUri())); } else if (resp.getStatus() >= 400) { throw new WebApplicationException(resp); } T thing; try { thing = resp.readEntity(clazz); } catch (Exception e) { throw new ProcessingException(e); } thing.setUri(target.getUri().toString()); return thing; } @Override public void clear() { log.error("clear not supported"); return; } @Override public void dump(Path directory, String basename) { log.error("dump not supported"); return; } @Override public <T extends BaseModel> List<T> search(Class<T> clazz, Multimap<String, String> query) { StringBuilder qParamSB = new StringBuilder(); for (Entry<String, String> entry : query.entries()) { qParamSB.append(entry.getKey()); qParamSB.append(":"); try { qParamSB.append(URLEncoder.encode(entry.getValue(), "UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } qParamSB.append(","); } String baseURI = InfolisConfig.getFrontendURI() + "/" + getUriForClass(clazz); String uri = baseURI; if (qParamSB.length() > 0) uri += "?q=" + qParamSB.toString(); uri += "&max=" + Integer.MAX_VALUE; log.debug("Search for {}", uri); WebTarget target = jerseyClient.target(uri); Response resp = target.request(MediaType.APPLICATION_JSON_TYPE).get(); log.debug("-> HTTP {}", resp.getStatus()); if (resp.getStatus() == 404) { throw new RuntimeException(String.format("No such '%s': '%s'", clazz.getSimpleName(), target.getUri())); } else if (resp.getStatus() >= 400) { throw new WebApplicationException(resp); } try { ObjectMapper mapper = SerializationUtils.jacksonMapper; CollectionType listOfTType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz); CollectionType listOfMapType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, HashMap.class); String readEntity = resp.readEntity(String.class); // log.debug("JSON before: {}", readEntity); // readEntity = readEntity.replaceAll("\"_id\":\"", "\"uri\":\""+baseURI+"/"); // log.debug("JSON after: {}", readEntity); List<T> listOfTs = mapper.<List<T>>readValue(readEntity, listOfTType); List<Map> flatList = mapper.readValue(readEntity, listOfMapType); for (int i = 0; i < flatList.size(); i++) { listOfTs.get(i).setUri(baseURI + "/" + flatList.get(i).get("_id")); } return listOfTs; } catch (Exception e) { throw new ProcessingException(e); } } }