Repository: metamodel-membrane Updated Branches: refs/heads/master 8d6636a89 -> d5419cb34
http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java new file mode 100644 index 0000000..82f568d --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.ws.rs.core.UriBuilder; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.membrane.app.DataContextTraverser; +import org.apache.metamodel.membrane.app.TenantContext; +import org.apache.metamodel.membrane.app.TenantRegistry; +import org.apache.metamodel.membrane.controllers.model.RestLink; +import org.apache.metamodel.schema.Table; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +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; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}", + "/{tenant}/{dataContext}/s/{schema}/t/{table}" }, produces = MediaType.APPLICATION_JSON_VALUE) +public class TableController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public TableController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map<String, Object> get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Table table = traverser.getTable(schemaId, tableId); + + final String tenantName = tenantContext.getTenantName(); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}"); + + final String tableName = table.getName(); + final String schemaName = table.getSchema().getName(); + final List<RestLink> columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf( + c), uriBuilder.build(tenantName, dataSourceName, schemaName, tableName, c))).collect(Collectors + .toList()); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "table"); + map.put("name", tableName); + map.put("schema", schemaName); + map.put("datasource", dataSourceName); + map.put("tenant", tenantName); + map.put("columns", columnsLinks); + return map; + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java new file mode 100644 index 0000000..e906556 --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateCallback; +import org.apache.metamodel.UpdateScript; +import org.apache.metamodel.UpdateSummary; +import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.insert.RowInsertionBuilder; +import org.apache.metamodel.membrane.app.DataContextTraverser; +import org.apache.metamodel.membrane.app.TenantContext; +import org.apache.metamodel.membrane.app.TenantRegistry; +import org.apache.metamodel.query.Query; +import org.apache.metamodel.schema.Table; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/data", + "/{tenant}/{dataContext}/s/{schema}/t/{table}/d" }, produces = MediaType.APPLICATION_JSON_VALUE) +public class TableDataController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public TableDataController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map<String, Object> get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId, @RequestParam(value = "offset", required = false) Integer offset, + @RequestParam(value = "limit", required = false) Integer limit) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Table table = traverser.getTable(schemaId, tableId); + + final Query query = dataContext.query().from(table).selectAll().toQuery(); + + return QueryController.executeQuery(dataContext, query, offset, limit); + } + + @RequestMapping(method = RequestMethod.POST) + @ResponseBody + public Map<String, Object> post(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId, @RequestBody final List<Map<String, Object>> inputRecords) { + + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName); + + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Table table = traverser.getTable(schemaId, tableId); + + final UpdateSummary result = dataContext.executeUpdate(new UpdateScript() { + @Override + public void run(UpdateCallback callback) { + for (Map<String, Object> inputMap : inputRecords) { + final RowInsertionBuilder insert = callback.insertInto(table); + for (Entry<String, Object> entry : inputMap.entrySet()) { + insert.value(entry.getKey(), entry.getValue()); + } + insert.execute(); + } + } + }); + + final Map<String, Object> response = new LinkedHashMap<>(); + response.put("status", "ok"); + + if (result.getInsertedRows().isPresent()) { + response.put("inserted-rows", result.getInsertedRows().get()); + } + if (result.getGeneratedKeys().isPresent()) { + response.put("generated-keys", result.getGeneratedKeys().get()); + } + + return response; + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java new file mode 100644 index 0000000..c4af271 --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.ws.rs.core.UriBuilder; + +import org.apache.metamodel.membrane.app.TenantContext; +import org.apache.metamodel.membrane.app.TenantRegistry; +import org.apache.metamodel.membrane.controllers.model.RestLink; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +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; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/{tenant}", produces = MediaType.APPLICATION_JSON_VALUE) +public class TenantController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public TenantController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map<String, Object> getTenant(@PathVariable("tenant") String tenantName) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName); + final String tenantNameNormalized = tenantContext.getTenantName(); + + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}"); + + final List<String> dataContextIdentifiers = tenantContext.getDataSourceRegistry().getDataSourceNames(); + final List<RestLink> dataSourceLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder + .build(tenantNameNormalized, s))).collect(Collectors.toList()); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("name", tenantNameNormalized); + map.put("datasources", dataSourceLinks); + return map; + } + + @RequestMapping(method = RequestMethod.PUT) + @ResponseBody + public Map<String, Object> putTenant(@PathVariable("tenant") String tenantName) { + final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName); + final String tenantIdentifier = tenantContext.getTenantName(); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("name", tenantIdentifier); + + return map; + } + + @RequestMapping(method = RequestMethod.DELETE) + @ResponseBody + public Map<String, Object> deleteTenant(@PathVariable("tenant") String tenantName) { + tenantRegistry.deleteTenantContext(tenantName); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("name", tenantName); + map.put("deleted", true); + + return map; + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java new file mode 100644 index 0000000..500427e --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers.model; + +import java.util.HashMap; +import java.util.Map; + +import javax.validation.constraints.NotNull; + +import org.apache.metamodel.membrane.app.DataSourceDefinition; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class RestDataSourceDefinition implements DataSourceDefinition { + + private final Map<String, Object> properties = new HashMap<>(); + + @JsonProperty(value = "type", required = true) + @NotNull + private String type; + + @Override + public String getType() { + return type; + } + + @JsonAnyGetter + @Override + public Map<String, Object> getProperties() { + return properties; + } + + @JsonAnySetter + public void set(String name, Object value) { + properties.put(name, value); + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java new file mode 100644 index 0000000..ad2ce2a --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers.model; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents the JSON object that is returned when an error occurs + */ +public class RestErrorResponse { + + @JsonProperty("code") + private int code; + + @JsonProperty("message") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String message; + + @JsonProperty("additional_details") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Map<String, Object> additionalDetails; + + public RestErrorResponse(int code, String message) { + this(code, message, null); + } + + public RestErrorResponse(int code, String message, Map<String, Object> additionalDetails) { + this.code = code; + this.message = message; + this.additionalDetails = additionalDetails; + } + + public RestErrorResponse() { + this(-1, null, null); + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setAdditionalDetails(Map<String, Object> additionalDetails) { + this.additionalDetails = additionalDetails; + } + + public Map<String, Object> getAdditionalDetails() { + return additionalDetails; + } + +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java new file mode 100644 index 0000000..01fb76d --- /dev/null +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers.model; + +import java.io.Serializable; +import java.net.URI; + +/** + * Represents a hyper-link to a service (typically provided in the REST + * responses) + */ +public class RestLink implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + + private URI uri; + + public RestLink() { + } + + public RestLink(String name, URI uri) { + this(); + this.name = name; + this.uri = uri; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/context/application-context.xml ---------------------------------------------------------------------- diff --git a/core/src/main/resources/context/application-context.xml b/core/src/main/resources/context/application-context.xml new file mode 100644 index 0000000..e28e2b0 --- /dev/null +++ b/core/src/main/resources/context/application-context.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:mvc="http://www.springframework.org/schema/mvc" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + <context:component-scan base-package="org.apache.metamodel.membrane.app" /> + + <bean id="tenantRegistry" class="org.apache.metamodel.membrane.app.InMemoryTenantRegistry" /> + +</beans> http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/context/dispatcher-servlet.xml ---------------------------------------------------------------------- diff --git a/core/src/main/resources/context/dispatcher-servlet.xml b/core/src/main/resources/context/dispatcher-servlet.xml new file mode 100644 index 0000000..17a4be7 --- /dev/null +++ b/core/src/main/resources/context/dispatcher-servlet.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:mvc="http://www.springframework.org/schema/mvc" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + <context:component-scan base-package="org.apache.metamodel.membrane.controllers" /> + + <mvc:annotation-driven> + <mvc:message-converters> + <ref bean="jsonMessageConverter" /> + </mvc:message-converters> + </mvc:annotation-driven> + + <mvc:resources location="/swagger-ui/" mapping="/swagger-ui/**" order="-1001" /> + + <!-- Message converters --> + <bean id="jsonMessageConverter" + class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> + <property name="supportedMediaTypes"> + <list> + <value>application/json;charset=UTF-8</value> + <value>application/json</value> + </list> + </property> + </bean> + + <bean + class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> + <property name="messageConverters"> + <list> + <ref bean="jsonMessageConverter" /> + </list> + </property> + </bean> +</beans> http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/logback.xml ---------------------------------------------------------------------- diff --git a/core/src/main/resources/logback.xml b/core/src/main/resources/logback.xml new file mode 100644 index 0000000..8ba8596 --- /dev/null +++ b/core/src/main/resources/logback.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<configuration> + + <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <logger name="org.apache.metamodel" level="info" /> + + <root level="warn"> + <appender-ref ref="consoleAppender" /> + </root> +</configuration> http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/swagger.yaml ---------------------------------------------------------------------- diff --git a/core/src/main/resources/swagger.yaml b/core/src/main/resources/swagger.yaml new file mode 100644 index 0000000..dc5ef93 --- /dev/null +++ b/core/src/main/resources/swagger.yaml @@ -0,0 +1,538 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +swagger: '2.0' +info: + title: Apache MetaModel Membrane + description: Data Federation as a RESTful service. + version: "1.0" + contact: + name: Apache MetaModel + url: http://metamodel.apache.org + license: + name: Apache License, version 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0 +consumes: + - application/json +produces: + - application/json +paths: + /: + get: + summary: Hello Membrane + description: An endpoint that provides a confirmation that the system is operational + responses: + 200: + description: The system is operational + schema: + type: object + properties: + ping: + type: string + description: Should return 'pong!' when the system is operational + application: + type: string + description: The name of the application running (Apache MetaModel Membrane) + version: + type: string + description: The version of the application running + server-time: + type: object + properties: + timestamp: + type: integer + format: int64 + description: The server-time in timestamp format (millis since 1st of January 1970) + iso8601: + type: string + description: The server-time in ISO-8601 format + canonical-hostname: + type: string + description: The canonical hostname of the server + /{tenant}: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + put: + summary: Create new tenant + responses: + 200: + description: Tenant created + schema: + properties: + type: + type: string + description: The type of entity (tenant) + name: + type: string + description: The tenant name/identifier + 409: + description: Tenant already exist + schema: + $ref: "#/definitions/error" + get: + summary: Get tenant information + description: Provides basic information about a tenant of the system + responses: + 404: + description: Tenant not found + schema: + $ref: "#/definitions/error" + 200: + description: Tenant found + schema: + type: object + properties: + type: + type: string + description: The type of entity (tenant) + name: + type: string + description: The tenant name/identifier + datasources: + type: array + items: + type: object + properties: + name: + type: string + description: The name of the datasource + uri: + type: string + format: uri + description: A link to the datasource information + delete: + summary: Delete tenant + description: Deletes a tenant from the system + responses: + 200: + description: Tenant deleted + schema: + type: object + properties: + type: + type: string + description: The type of entity (tenant) + name: + type: string + description: The tenant name/identifier + 404: + description: Tenant not found + schema: + $ref: "#/definitions/error" + /{tenant}/{datasource}: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + - name: datasource + in: path + type: string + description: The datasource name + required: true + get: + responses: + 200: + description: Datasource found + schema: + type: object + properties: + type: + type: string + description: The type of entity (datasource) + name: + type: string + description: The datasource name + tenant: + type: string + description: The tenant name + updateable: + type: boolean + description: Is this datasource updateable? + query_uri: + type: string + description: A link to the query endpoint for this datasource + format: uri + schemas: + type: array + description: The schemas of this datasource + items: + type: object + properties: + name: + type: string + description: The schema name + uri: + type: string + description: A link to the schema information + format: uri + 404: + description: Datasource not found + schema: + $ref: "#/definitions/error" + put: + parameters: + - name: inputData + in: body + description: The definition of the datasource using properties. The same properties as normally applied in MetaModel factories (e.g. 'type', 'resource', 'url', 'driver-class' ,'hostname', 'port', 'catalog', 'database', 'username', 'port', 'table-defs') are used here. + required: true + schema: + type: object + responses: + 200: + description: Datasource created + /{tenant}/{datasource}/q: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + - name: datasource + in: path + type: string + description: The datasource name + required: true + get: + description: Executes a query on the datasource + parameters: + - name: sql + in: query + type: string + description: The MetaModel-flavoured SQL query to execute + required: true + - name: offset + in: query + type: string + description: An offset / first-row flag to set on the query + required: false + - name: limit + in: query + type: string + description: A limit / max-rows flag to set on the query + required: false + responses: + 200: + description: Query executed + schema: + $ref: "#/definitions/queryResponse" + 400: + description: Failure while parsing query + schema: + $ref: "#/definitions/error" + 404: + description: Datasource not found + schema: + $ref: "#/definitions/error" + 500: + description: Failure while executing query + schema: + $ref: "#/definitions/error" + /{tenant}/{datasource}/s/{schema}: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + - name: datasource + in: path + type: string + description: The datasource name + required: true + - name: schema + in: path + type: string + description: The schema name + required: true + get: + responses: + 200: + description: Schema found + schema: + type: object + properties: + type: + type: string + description: The type of entity (schema) + name: + type: string + description: The schema name + datasource: + type: string + description: The datasource name + tables: + type: array + description: The names of the schema's tables + items: + type: object + properties: + name: + type: string + description: The table name + uri: + type: string + description: A link to the table information + format: uri + 404: + description: Schema not found + schema: + $ref: "#/definitions/error" + /{tenant}/{datasource}/s/{schema}/t/{table}: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + - name: datasource + in: path + type: string + description: The datasource name + required: true + - name: schema + in: path + type: string + description: The schema name + required: true + - name: table + in: path + type: string + description: The table name + required: true + get: + responses: + 200: + description: Table found + schema: + type: object + properties: + type: + type: string + description: The type of entity (table) + name: + type: string + description: The table name + schema: + type: string + description: The schema name + datasource: + type: string + description: The datasource name + tenant: + type: string + description: The tenant name + columns: + type: array + description: The names of the table's columns + items: + type: object + properties: + name: + type: string + description: The column name + uri: + type: string + description: A link to the column information + format: uri + 404: + description: Table not found + schema: + $ref: "#/definitions/error" + /{tenant}/{datasource}/s/{schema}/t/{table}/d: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + - name: datasource + in: path + type: string + description: The datasource name + required: true + - name: schema + in: path + type: string + description: The schema name + required: true + - name: table + in: path + type: string + description: The table name + required: true + get: + description: Gets the data of the table + responses: + 200: + description: Query executed + schema: + $ref: "#/definitions/queryResponse" + 400: + description: Failure while parsing query + schema: + $ref: "#/definitions/error" + 404: + description: Table not found + schema: + $ref: "#/definitions/error" + 500: + description: Failure while executing query + schema: + $ref: "#/definitions/error" + post: + description: Inserts data to the table + parameters: + - name: inputData + in: body + description: The data to insert + required: true + schema: + type: array + items: + description: A record to insert where each key is expected to match a column name and each value is the value to put. + type: object + responses: + 200: + description: Data inserted + #TODO + 404: + description: Table not found + schema: + $ref: "#/definitions/error" + /{tenant}/{datasource}/s/{schema}/t/{table}/c/{column}: + parameters: + - name: tenant + in: path + type: string + description: The tenant name + required: true + - name: datasource + in: path + type: string + description: The datasource name + required: true + - name: schema + in: path + type: string + description: The schema name + required: true + - name: table + in: path + type: string + description: The table name + required: true + - name: column + in: path + type: string + description: The column name + required: true + get: + description: Gets information about a column + responses: + 200: + description: Query executed + schema: + type: object + properties: + type: + type: string + description: The type of entity (column) + name: + type: string + description: The column name + table: + type: string + description: The table name + schema: + type: string + description: The schema name + datasource: + type: string + description: The datasource name + tenant: + type: string + description: The tenant name + metadata: + type: object + description: Metadata about the column + properties: + number: + type: integer + description: The column number (0-based) + size: + type: integer + description: The column size + nullable: + type: boolean + description: Is the column nullable? + primary-key: + type: boolean + description: Is the column a primary key? + indexed: + type: boolean + description: Is the column indexed? + column-type: + type: string + description: The column type (as interpreted/adapted by Apache MetaModel) + native-type: + type: string + description: The native column type (as defined by the datasource itself) + remarks: + type: string + description: Any remarks on the column + 404: + description: Column not found + schema: + $ref: "#/definitions/error" +definitions: + queryResponse: + description: Represents the result of a query - a dataset + type: object + properties: + type: + type: string + description: The type of entity (dataset) + headers: + type: array + description: The dataset header names + items: + type: string + data: + type: array + description: The actual data returned by the query + items: + type: array + items: + type: object + error: + description: Elaborates the error that occurred + type: object + properties: + code: + type: integer + description: The HTTP status code + message: + type: string + description: A humanly readable error message + additional_details: + type: object + description: Any auxilary details to further elaborate the error http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java b/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java new file mode 100644 index 0000000..1b5fbbf --- /dev/null +++ b/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers; + +import static org.junit.Assert.assertEquals; + +import java.util.Map; + +import org.apache.metamodel.membrane.controllers.RootInformationController; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RootInformationControllerTest { + + private MockMvc mockMvc; + + @Before + public void init() { + final RootInformationController controller = new RootInformationController(); + controller.servletContext = new MockServletContext(); + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + public void testGet() throws Exception { + final MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/").contentType( + MediaType.APPLICATION_JSON); + + final MvcResult result = mockMvc.perform(request).andExpect(MockMvcResultMatchers.status().is(200)).andReturn(); + + final String content = result.getResponse().getContentAsString(); + + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("pong!", map.get("ping")); + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java b/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java new file mode 100644 index 0000000..4c01e53 --- /dev/null +++ b/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java @@ -0,0 +1,202 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.controllers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Map; + +import org.apache.metamodel.membrane.app.InMemoryTenantRegistry; +import org.apache.metamodel.membrane.app.TenantRegistry; +import org.apache.metamodel.membrane.controllers.ColumnController; +import org.apache.metamodel.membrane.controllers.DataSourceController; +import org.apache.metamodel.membrane.controllers.QueryController; +import org.apache.metamodel.membrane.controllers.SchemaController; +import org.apache.metamodel.membrane.controllers.TableController; +import org.apache.metamodel.membrane.controllers.TableDataController; +import org.apache.metamodel.membrane.controllers.TenantController; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class TenantInteractionScenarioTest { + + private MockMvc mockMvc; + + @Before + public void init() { + TenantRegistry tenantRegistry = new InMemoryTenantRegistry(); + TenantController tenantController = new TenantController(tenantRegistry); + DataSourceController dataContextController = new DataSourceController(tenantRegistry); + SchemaController schemaController = new SchemaController(tenantRegistry); + TableController tableController = new TableController(tenantRegistry); + ColumnController columnController = new ColumnController(tenantRegistry); + QueryController queryController = new QueryController(tenantRegistry); + TableDataController tableDataController = new TableDataController(tenantRegistry); + + mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController, + tableController, columnController, queryController, tableDataController).build(); + } + + @Test + public void testScenario() throws Exception { + // create tenant + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1").contentType( + MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("tenant", map.get("type")); + assertEquals("tenant1", map.get("name")); + assertNull(map.get("datasources")); + } + + // create datasource + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content( + "{'type':'pojo','table-defs':'hello_world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}" + .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( + MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("datasource", map.get("type")); + assertEquals("mydata", map.get("name")); + assertEquals( + "[{name=information_schema, uri=/tenant1/mydata/s/information_schema}, {name=mydata, uri=/tenant1/mydata/s/mydata}]", + map.get("schemas").toString()); + } + + // explore tenant - now with a datasource + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1")).andExpect( + MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("tenant", map.get("type")); + assertEquals("tenant1", map.get("name")); + assertEquals("[{name=mydata, uri=/tenant1/mydata}]", map.get("datasources").toString()); + } + + // explore schema + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata")).andExpect( + MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("schema", map.get("type")); + assertEquals("mydata", map.get("name")); + + assertEquals( + "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello_world, uri=/tenant1/mydata/s/mydata/t/hello_world}]", + map.get("tables").toString()); + } + + // explore table + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata/t/foo")) + .andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("table", map.get("type")); + assertEquals("foo", map.get("name")); + + assertEquals("[{name=bar, uri=/tenant1/mydata/s/mydata/t/foo/c/bar}, " + + "{name=baz, uri=/tenant1/mydata/s/mydata/t/foo/c/baz}]", map.get("columns").toString()); + } + + // explore column + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata/t/foo/c/bar")) + .andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("column", map.get("type")); + assertEquals("bar", map.get("name")); + + assertEquals( + "{number=0, size=null, nullable=true, primary-key=false, indexed=false, column-type=INTEGER, native-type=null, remarks=null}", + map.get("metadata").toString()); + } + + // query metadata from information_schema + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/q?sql={sql}", + "SELECT name, table FROM information_schema.columns")).andExpect(MockMvcResultMatchers.status() + .isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("dataset", map.get("type")); + assertEquals("[columns.name, columns.table]", map.get("header").toString()); + assertEquals("[[bar, foo], [baz, foo], [greeting, hello_world], [who, hello_world]]", map.get("data") + .toString()); + } + + // insert into table (x2) + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post( + "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Howdy','who':'MetaModel'}]" + .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( + MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("{status=ok}", map.toString()); + } + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post( + "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Hi','who':'Apache'}]" + .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( + MockMvcResultMatchers.status().isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("{status=ok}", map.toString()); + } + + // query the actual data + // query metadata from information_schema + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/q?sql={sql}", + "SELECT greeting, who AS who_is_it FROM hello_world")).andExpect(MockMvcResultMatchers.status() + .isOk()).andReturn(); + + final String content = result.getResponse().getContentAsString(); + final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class); + assertEquals("dataset", map.get("type")); + assertEquals("[hello_world.greeting, hello_world.who AS who_is_it]", map.get("header").toString()); + assertEquals("[[Howdy, MetaModel], [Hi, Apache]]", map.get("data") + .toString()); + } + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/docker-compose.yml ---------------------------------------------------------------------- diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c87a2bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +version: '2' +services: + metamodel-membrane: + container_name: metamodel-membrane + image: metamodel-membrane + build: undertow + ports: + - "8080:8080" + environment: + - MEMBRANE_HTTP_PORT=8080 http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index b8dc59f..b5d51d5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,25 +1,32 @@ <?xml version="1.0" encoding="UTF-8" ?> -<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor - license agreements. See the NOTICE file distributed with this work for additional - information regarding copyright ownership. The ASF licenses this file to - you under the Apache License, Version 2.0 (the "License"); you may not use - this file except in compliance with the License. You may obtain a copy of - the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required - by applicable law or agreed to in writing, software distributed under the - License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS - OF ANY KIND, either express or implied. See the License for the specific - language governing permissions and limitations under the License. --> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <metamodel.version>5.0-RC2</metamodel.version> + <metamodel.version>5.0-SNAPSHOT</metamodel.version> </properties> <parent> <groupId>org.apache.metamodel</groupId> <artifactId>MetaModel</artifactId> - <version>5.0-RC2</version> + <version>5.0-SNAPSHOT</version> </parent> <scm> <url>https://git-wip-us.apache.org/repos/asf?p=metamodel-membrane.git</url> @@ -36,25 +43,34 @@ <inceptionYear>2017</inceptionYear> <packaging>pom</packaging> <modules> + <module>core</module> + <module>war</module> + <module>undertow</module> </modules> <dependencyManagement> <dependencies> <dependency> - <groupId>org.apache.metanmodel</groupId> + <groupId>org.apache.metamodel</groupId> <artifactId>MetaModel-core</artifactId> <version>${metamodel.version}</version> </dependency> <dependency> - <groupId>org.apache.metanmodel</groupId> + <groupId>org.apache.metamodel</groupId> <artifactId>MetaModel-spring</artifactId> <version>${metamodel.version}</version> </dependency> <dependency> - <groupId>org.apache.metanmodel</groupId> + <groupId>org.apache.metamodel</groupId> <artifactId>MetaModel-full</artifactId> <version>${metamodel.version}</version> </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>3.1.0</version> + <scope>provided</scope> + </dependency> </dependencies> </dependencyManagement> </project> http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/Dockerfile ---------------------------------------------------------------------- diff --git a/undertow/Dockerfile b/undertow/Dockerfile new file mode 100644 index 0000000..3e14546 --- /dev/null +++ b/undertow/Dockerfile @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +FROM openjdk:8-jre-alpine + +COPY target/membrane-undertow-server.jar membrane-undertow-server.jar + +CMD java -server -jar membrane-undertow-server.jar http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/pom.xml ---------------------------------------------------------------------- diff --git a/undertow/pom.xml b/undertow/pom.xml new file mode 100644 index 0000000..81c2600 --- /dev/null +++ b/undertow/pom.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<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"> + <parent> + <groupId>org.apache.metamodel.membrane</groupId> + <artifactId>Membrane-parent</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>Membrane-undertow</artifactId> + <packaging>jar</packaging> + + <build> + <finalName>membrane-undertow-server</finalName> + + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.4.3</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <transformers> + <transformer + implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>org.apache.metamodel.membrane.server.WebServer</mainClass> + </transformer> + <transformer + implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/spring.handlers</resource> + </transformer> + <transformer + implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/spring.schemas</resource> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.metamodel.membrane</groupId> + <artifactId>Membrane-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>io.undertow</groupId> + <artifactId>undertow-servlet</artifactId> + <version>1.3.23.Final</version> + </dependency> + </dependencies> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/src/main/docker/swagger-ui/index.html ---------------------------------------------------------------------- diff --git a/undertow/src/main/docker/swagger-ui/index.html b/undertow/src/main/docker/swagger-ui/index.html new file mode 100644 index 0000000..fae3de9 --- /dev/null +++ b/undertow/src/main/docker/swagger-ui/index.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<html> +<head> + <meta charset="UTF-8"> + <title>Swagger UI</title> + <link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" /> + <link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" /> + <link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/> + <link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/> + <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/> + <link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/> + <link href='css/print.css' media='print' rel='stylesheet' type='text/css'/> + + <script src='lib/object-assign-pollyfill.js' type='text/javascript'></script> + <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script> + <script src='lib/jquery.slideto.min.js' type='text/javascript'></script> + <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script> + <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script> + <script src='lib/handlebars-2.0.0.js' type='text/javascript'></script> + <script src='lib/lodash.min.js' type='text/javascript'></script> + <script src='lib/backbone-min.js' type='text/javascript'></script> + <script src='swagger-ui.js' type='text/javascript'></script> + <script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script> + <script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script> + <script src='lib/jsoneditor.min.js' type='text/javascript'></script> + <script src='lib/marked.js' type='text/javascript'></script> + <script src='lib/swagger-oauth.js' type='text/javascript'></script> + + <!-- Some basic translations --> + <!-- <script src='lang/translator.js' type='text/javascript'></script> --> + <!-- <script src='lang/ru.js' type='text/javascript'></script> --> + <!-- <script src='lang/en.js' type='text/javascript'></script> --> + + <script type="text/javascript"> + $(function () { + var url = window.location.search.match(/url=([^&]+)/); + if (url && url.length > 1) { + url = decodeURIComponent(url[1]); + } else { + url = "../../swagger.json"; + } + + hljs.configure({ + highlightSizeThreshold: 5000 + }); + + // Pre load translate... + if(window.SwaggerTranslator) { + window.SwaggerTranslator.translate(); + } + window.swaggerUi = new SwaggerUi({ + url: url, + dom_id: "swagger-ui-container", + supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'], + onComplete: function(swaggerApi, swaggerUi){ + if(typeof initOAuth == "function") { + initOAuth({ + clientId: "your-client-id", + clientSecret: "your-client-secret-if-required", + realm: "your-realms", + appName: "your-app-name", + scopeSeparator: ",", + additionalQueryStringParams: {} + }); + } + + if(window.SwaggerTranslator) { + window.SwaggerTranslator.translate(); + } + }, + onFailure: function(data) { + log("Unable to Load SwaggerUI"); + }, + docExpansion: "none", + jsonEditor: false, + defaultModelRendering: 'schema', + showRequestHeaders: false + }); + + window.swaggerUi.load(); + + function log() { + if ('console' in window) { + console.log.apply(console, arguments); + } + } + }); + </script> +</head> + +<body class="swagger-section"> +<div id="swagger-ui-container" class="swagger-ui-wrap"></div> +</body> +</html> http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java ---------------------------------------------------------------------- diff --git a/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java b/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java new file mode 100644 index 0000000..1643bd8 --- /dev/null +++ b/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.membrane.server; + +import java.io.File; + +import javax.servlet.ServletException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.request.RequestContextListener; +import org.springframework.web.filter.CharacterEncodingFilter; +import org.springframework.web.servlet.DispatcherServlet; + +import com.google.common.base.Strings; + +import io.undertow.Undertow; +import io.undertow.server.handlers.resource.FileResourceManager; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; + +/** + * Defines the main class that starts the Undertow-based web server. + */ +public class WebServer { + + private static final Logger logger = LoggerFactory.getLogger(WebServer.class); + + private static final int DEFAULT_PORT = 8080; + + public static void main(final String[] args) throws Exception { + final String portEnv = System.getenv("MEMBRANE_HTTP_PORT"); + final int port = Strings.isNullOrEmpty(portEnv) ? DEFAULT_PORT : Integer.parseInt(portEnv); + + logger.info("Apache MetaModel Membrane server initiating on port {}", port); + + startServer(port); + + logger.info("Apache MetaModel Membrane server started on port {}", port); + } + + public static void startServer(int port) throws Exception { + final DeploymentInfo deployment = Servlets.deployment().setClassLoader(WebServer.class.getClassLoader()); + deployment.setContextPath(""); + deployment.setDeploymentName("membrane"); + deployment.addInitParameter("contextConfigLocation", "classpath:context/application-context.xml"); + deployment.setResourceManager(new FileResourceManager(new File("."), 0)); + deployment.addListener(Servlets.listener(ContextLoaderListener.class)); + deployment.addListener(Servlets.listener(RequestContextListener.class)); + deployment.addServlet(Servlets.servlet("dispatcher", DispatcherServlet.class).addMapping("/*") + .addInitParam("contextConfigLocation", "classpath:context/dispatcher-servlet.xml")); + deployment.addFilter(Servlets.filter(CharacterEncodingFilter.class).addInitParam("forceEncoding", "true") + .addInitParam("encoding", "UTF-8")); + + final DeploymentManager manager = Servlets.defaultContainer().addDeployment(deployment); + manager.deploy(); + + final Undertow server = Undertow.builder().addHttpListener(port, "0.0.0.0").setHandler(manager.start()).build(); + server.start(); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // graceful shutdown of everything + server.stop(); + try { + manager.stop(); + } catch (ServletException e) { + } + manager.undeploy(); + } + }); + } +} http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/war/pom.xml ---------------------------------------------------------------------- diff --git a/war/pom.xml b/war/pom.xml new file mode 100644 index 0000000..0c35c43 --- /dev/null +++ b/war/pom.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<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"> + <parent> + <groupId>org.apache.metamodel.membrane</groupId> + <artifactId>Membrane-parent</artifactId> + <version>0.1-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>Membrane-war</artifactId> + <packaging>war</packaging> + + <build> + <finalName>Membrane</finalName> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.metamodel.membrane</groupId> + <artifactId>Membrane-core</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/war/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/war/src/main/webapp/WEB-INF/web.xml b/war/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..5a313ca --- /dev/null +++ b/war/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + version="3.1" xmlns="http://java.sun.com/xml/ns/javaee"> + + <!-- Spring listener --> + <listener> + <listener-class>org.springframework.web.context.ContextLoaderListener + </listener-class> + </listener> + <listener> + <listener-class>org.springframework.web.context.request.RequestContextListener + </listener-class> + </listener> + + <!-- Spring config --> + <context-param> + <param-name>contextConfigLocation</param-name> + <param-value>classpath:context/application-context.xml</param-value> + </context-param> + + <!-- Spring servlet --> + <servlet> + <servlet-name>dispatcher</servlet-name> + <servlet-class>org.springframework.web.servlet.DispatcherServlet + </servlet-class> + <load-on-startup>1</load-on-startup> + <init-param> + <param-name>contextConfigLocation</param-name> + <param-value>classpath:context/dispatcher-servlet.xml</param-value> + </init-param> + </servlet> + <servlet-mapping> + <servlet-name>dispatcher</servlet-name> + <url-pattern>/*</url-pattern> + </servlet-mapping> + + <filter> + <filter-name>CharacterEncodingFilter</filter-name> + <filter-class>org.springframework.web.filter.CharacterEncodingFilter + </filter-class> + <init-param> + <param-name>encoding</param-name> + <param-value>UTF-8</param-value> + </init-param> + <init-param> + <param-name>forceEncoding</param-name> + <param-value>true</param-value> + </init-param> + </filter> + + <filter-mapping> + <filter-name>CharacterEncodingFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + +</web-app>