From c9f56aa753334b571ab868912c47d0e629e351ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 23 May 2016 21:36:33 -0700 Subject: [PATCH 01/19] Added scaffolding for 'metamodel-as-a-service' module --- pom.xml | 30 ++++++ service-webapp/pom.xml | 84 ++++++++++++++++ .../RootInformationController.java | 95 +++++++++++++++++++ .../resources/context/application-context.xml | 32 +++++++ service-webapp/src/main/resources/logback.xml | 33 +++++++ .../webapp/WEB-INF/dispatcher-servlet.xml | 57 +++++++++++ .../src/main/webapp/WEB-INF/web.xml | 66 +++++++++++++ .../RootInformationControllerTest.java | 61 ++++++++++++ spring/pom.xml | 12 --- 9 files changed, 458 insertions(+), 12 deletions(-) create mode 100644 service-webapp/pom.xml create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java create mode 100644 service-webapp/src/main/resources/context/application-context.xml create mode 100644 service-webapp/src/main/resources/logback.xml create mode 100644 service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml create mode 100644 service-webapp/src/main/webapp/WEB-INF/web.xml create mode 100644 service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java diff --git a/pom.xml b/pom.xml index 6b4b2b31f..0e9d696bf 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ under the License. 2.6.0 2.6.3 3.2 + 4.2.6.RELEASE 4.4.1 1.2 false @@ -77,6 +78,7 @@ under the License. full spring neo4j + service-webapp Jira @@ -534,6 +536,34 @@ under the License. hsqldb 1.8.0.10 + + + + org.springframework + spring-core + ${spring.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-test + ${spring.version} + + + org.springframework + spring-webmvc + ${spring.version} + diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml new file mode 100644 index 000000000..f3836dd46 --- /dev/null +++ b/service-webapp/pom.xml @@ -0,0 +1,84 @@ + + + + + MetaModel + org.apache.metamodel + 5.0-SNAPSHOT + + 4.0.0 + MetaModel-service-webapp + MetaModel-as–a-service web application + war + + + MetaModel + + + + + org.springframework + spring-webmvc + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.guava + guava + + + ch.qos.logback + logback-classic + 1.1.7 + + + org.hibernate + hibernate-validator + 5.2.4.Final + + + org.slf4j + jcl-over-slf4j + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + junit + junit + test + + + org.springframework + spring-test + test + + + \ No newline at end of file diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java new file mode 100644 index 000000000..794f85a88 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java @@ -0,0 +1,95 @@ +/** + * 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.service.controllers; + +import java.io.InputStream; +import java.net.InetAddress; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +import javax.servlet.ServletContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +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 +public class RootInformationController { + + private static final Logger logger = LoggerFactory.getLogger(RootInformationController.class); + + @Autowired + ServletContext servletContext; + + @RequestMapping(method = RequestMethod.GET, value = "/", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Map index() { + final Map map = new LinkedHashMap<>(); + map.put("ping", "pong!"); + map.put("application", "Apache MetaModel"); + map.put("version", getVersion()); + map.put("server-time", getServerTime()); + try { + map.put("canonical-hostname", InetAddress.getLocalHost().getCanonicalHostName()); + } catch (Exception e) { + logger.info("Failed to get canonical-hostname", e); + } + return map; + } + + private Map getServerTime() { + final ZonedDateTime now = ZonedDateTime.now(); + final String dateFormatted = now.format( DateTimeFormatter.ISO_INSTANT); + + final Map map = new LinkedHashMap<>(); + map.put("timestamp", new Date().getTime()); + map.put("iso8601", dateFormatted); + return map; + } + + /** + * Does the slightly tedious task of reading the software version from + * META-INF based on maven metadata. + * + * @return + */ + private String getVersion() { + final String groupId = "org.apache.metamodel"; + final String artifactId = "MetaModel-service-webapp"; + final String resourcePath = "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties"; + final Properties properties = new Properties(); + try (final InputStream inputStream = servletContext.getResourceAsStream(resourcePath)) { + properties.load(inputStream); + } catch (Exception e) { + logger.error("Failed to load version from manifest: " + e.getMessage()); + } + + final String version = properties.getProperty("version", "UNKNOWN"); + return version; + } +} diff --git a/service-webapp/src/main/resources/context/application-context.xml b/service-webapp/src/main/resources/context/application-context.xml new file mode 100644 index 000000000..b0c945d67 --- /dev/null +++ b/service-webapp/src/main/resources/context/application-context.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/service-webapp/src/main/resources/logback.xml b/service-webapp/src/main/resources/logback.xml new file mode 100644 index 000000000..de862de4e --- /dev/null +++ b/service-webapp/src/main/resources/logback.xml @@ -0,0 +1,33 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml new file mode 100644 index 000000000..12af16fca --- /dev/null +++ b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + application/json;charset=UTF-8 + application/json + + + + + + + + + + + + \ No newline at end of file diff --git a/service-webapp/src/main/webapp/WEB-INF/web.xml b/service-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..eb3ef51d7 --- /dev/null +++ b/service-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,66 @@ + + + + + + + org.springframework.web.context.ContextLoaderListener + + + org.springframework.web.context.request.RequestContextListener + + + + + contextConfigLocation + classpath:context/application-context.xml + + + + + dispatcher + org.springframework.web.servlet.DispatcherServlet + 1 + + + dispatcher + /* + + + + CharacterEncodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + + CharacterEncodingFilter + /* + + + diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java new file mode 100644 index 000000000..58a5ca558 --- /dev/null +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java @@ -0,0 +1,61 @@ +/** + * 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.service.controllers; + +import static org.junit.Assert.assertEquals; + +import java.util.Map; + +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 testGenericMessageSuccess() 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")); + } +} diff --git a/spring/pom.xml b/spring/pom.xml index 67925822f..b6d09b411 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -27,10 +27,6 @@ under the License. MetaModel-spring MetaModel module for Spring enabled configuration - - 3.0.7.RELEASE - - org.apache.metamodel @@ -46,14 +42,7 @@ under the License. org.springframework spring-context - ${spring.version} provided - - - commons-logging - commons-logging - - @@ -70,7 +59,6 @@ under the License. org.springframework spring-test - ${spring.version} test From 8e2ff7167eeae456a1e307426407e262dd7297f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 23 May 2016 23:05:21 -0700 Subject: [PATCH 02/19] Made a very simple tenant controller --- service-webapp/pom.xml | 10 ++ .../service/app/DataContextRegistry.java | 33 +++++ .../app/InMemoryDataContextRegistry.java | 51 ++++++++ .../service/app/InMemoryTenantContext.java | 41 +++++++ .../service/app/InMemoryTenantRegistry.java | 64 ++++++++++ .../metamodel/service/app/TenantContext.java | 31 +++++ .../metamodel/service/app/TenantRegistry.java | 35 ++++++ .../service/controllers/TenantController.java | 113 ++++++++++++++++++ .../service/controllers/model/Link.java | 59 +++++++++ .../resources/context/application-context.xml | 2 + 10 files changed, 439 insertions(+) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml index f3836dd46..b452f55c6 100644 --- a/service-webapp/pom.xml +++ b/service-webapp/pom.xml @@ -34,6 +34,16 @@ under the License. + + org.apache.metamodel + MetaModel-spring + ${project.version} + + + org.apache.metamodel + MetaModel-full + ${project.version} + org.springframework spring-webmvc diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java new file mode 100644 index 000000000..dd18a04d7 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java @@ -0,0 +1,33 @@ +/** + * 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.service.app; + +import java.util.List; + +import org.apache.metamodel.DataContext; + +/** + * Represents a user's/tenant's registry of {@link DataContext}s. + */ +public interface DataContextRegistry { + + public List getDataContextIdentifiers(); + + public DataContext openDataContext(String dataContextIdentifier) throws IllegalArgumentException; +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java new file mode 100644 index 000000000..89d835dfa --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java @@ -0,0 +1,51 @@ +/** + * 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.service.app; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.metamodel.DataContext; + +public class InMemoryDataContextRegistry implements DataContextRegistry { + + private final Map> dataContextIdentifiers; + + public InMemoryDataContextRegistry() { + dataContextIdentifiers = new LinkedHashMap<>(); + } + + @Override + public List getDataContextIdentifiers() { + return dataContextIdentifiers.keySet().stream().collect(Collectors.toList()); + } + + @Override + public DataContext openDataContext(String dataContextIdentifier) { + final Supplier supplier = dataContextIdentifiers.get(dataContextIdentifier); + if (supplier == null) { + throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier); + } + return supplier.get(); + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java new file mode 100644 index 000000000..99a3e4e07 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java @@ -0,0 +1,41 @@ +/** + * 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.service.app; + +public class InMemoryTenantContext implements TenantContext { + + private final String tenantIdentifier; + private final DataContextRegistry dataContextRegistry; + + public InMemoryTenantContext(String tenantIdentifier) { + this.tenantIdentifier = tenantIdentifier; + this.dataContextRegistry = new InMemoryDataContextRegistry(); + } + + @Override + public String getTenantIdentifier() { + return tenantIdentifier; + } + + @Override + public DataContextRegistry getDataContextRegistry() { + return dataContextRegistry; + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java new file mode 100644 index 000000000..c74fb2224 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java @@ -0,0 +1,64 @@ +/** + * 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.service.app; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * In-memory {@link TenantRegistry}. This is not particularly + * production-friendly as it is non-persistent, but it is useful for demo + * purposes. + */ +public class InMemoryTenantRegistry implements TenantRegistry { + + private final Map tenants; + + public InMemoryTenantRegistry() { + tenants = new LinkedHashMap<>(); + } + + @Override + public List getTenantIdentifiers() { + return tenants.keySet().stream().collect(Collectors.toList()); + } + + @Override + public TenantContext getTenantContext(String tenantIdentifier) { + return tenants.get(tenantIdentifier); + } + + @Override + public TenantContext createTenantContext(String tenantIdentifier) { + if (tenants.containsKey(tenantIdentifier)) { + throw new IllegalArgumentException("Tenant already exist: " + tenantIdentifier); + } + final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier); + tenants.put(tenantIdentifier, tenantContext); + return tenantContext; + } + + @Override + public boolean deleteTenantContext(String tenantIdentifier) { + return tenants.remove(tenantIdentifier) != null; + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java new file mode 100644 index 000000000..4e27922a8 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java @@ -0,0 +1,31 @@ +/** + * 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.service.app; + +/** + * Represents a context-object containing all the information and services + * related to a particular tenant. + */ +public interface TenantContext { + + public String getTenantIdentifier(); + + public DataContextRegistry getDataContextRegistry(); + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java new file mode 100644 index 000000000..450db2df5 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java @@ -0,0 +1,35 @@ +/** + * 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.service.app; + +import java.util.List; + +/** + * Represents the application's central registry of tenants + */ +public interface TenantRegistry { + + public List getTenantIdentifiers(); + + public TenantContext getTenantContext(String tenantIdentifier); + + public TenantContext createTenantContext(String tenantIdentifier) throws IllegalArgumentException; + + public boolean deleteTenantContext(String tenantIdentifier); +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java new file mode 100644 index 000000000..afefb9e96 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java @@ -0,0 +1,113 @@ +/** + * 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.service.controllers; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.UriBuilder; + +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.apache.metamodel.service.controllers.model.Link; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.ExceptionHandler; +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; +import org.springframework.web.client.HttpServerErrorException; + +@RestController +@RequestMapping(value = "/{tenant}", produces = MediaType.APPLICATION_JSON_VALUE) +public class TenantController { + + @Autowired + TenantRegistry tenantRegistry; + + @RequestMapping(method = RequestMethod.PUT) + @ResponseBody + public Map putTenant(@PathVariable("tenant") String tenantName) { + final TenantContext tenantContext; + try { + tenantContext = tenantRegistry.createTenantContext(tenantName); + } catch (IllegalArgumentException e) { + throw new HttpServerErrorException(HttpStatus.CONFLICT, e.getMessage()); + } + final String tenantIdentifier = tenantContext.getTenantIdentifier(); + + final Map map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("id", tenantIdentifier); + + return map; + } + + @RequestMapping(method = RequestMethod.DELETE) + @ResponseBody + public Map deleteTenant(@PathVariable("tenant") String tenantName) { + final boolean deleted = tenantRegistry.deleteTenantContext(tenantName); + + if (!deleted) { + throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName); + } + + final Map map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("id", tenantName); + map.put("deleted", deleted); + + return map; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map getTenant(@PathVariable("tenant") String tenantName) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName); + if (tenantContext == null) { + throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName); + } + + final String tenantIdentifier = tenantContext.getTenantIdentifier(); + + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}"); + + final List dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers(); + final List dataContextLinks = dataContextIdentifiers.stream().map(s -> new Link(s, uriBuilder.build( + tenantIdentifier, s))).collect(Collectors.toList()); + + final Map map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("id", tenantIdentifier); + map.put("data-contexts", dataContextLinks); + return map; + } + + @ExceptionHandler(HttpServerErrorException.class) + public void handleException(HttpServerErrorException e, HttpServletResponse resp) throws IOException { + resp.sendError(e.getStatusCode().value(), e.getStatusText()); + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java new file mode 100644 index 000000000..487155391 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java @@ -0,0 +1,59 @@ +/** + * 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.service.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 Link implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + private URI uri; + + public Link() { + } + + public Link(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; + } +} diff --git a/service-webapp/src/main/resources/context/application-context.xml b/service-webapp/src/main/resources/context/application-context.xml index b0c945d67..89a1b2865 100644 --- a/service-webapp/src/main/resources/context/application-context.xml +++ b/service-webapp/src/main/resources/context/application-context.xml @@ -29,4 +29,6 @@ under the License. + + \ No newline at end of file From fabd1b9784a7ec6512bf716fe8e6fa39e8cbf15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 23 May 2016 23:11:43 -0700 Subject: [PATCH 03/19] Added Docker capabilities: A Dockerfile and README.md --- service-webapp/Dockerfile | 25 +++++++++++++++++++++++++ service-webapp/README.md | 12 ++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 service-webapp/Dockerfile create mode 100644 service-webapp/README.md diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile new file mode 100644 index 000000000..033d6e316 --- /dev/null +++ b/service-webapp/Dockerfile @@ -0,0 +1,25 @@ +# 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 tomcat:8.0-jre8 + +RUN rm -rf $CATALINA_HOME/webapps \ + mkdir $CATALINA_HOME/webapps +COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war + +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/service-webapp/README.md b/service-webapp/README.md new file mode 100644 index 000000000..20bca993c --- /dev/null +++ b/service-webapp/README.md @@ -0,0 +1,12 @@ +# MetaModel-as-a-service + +This is a web application that allows you to access MetaModel's unified API for datastore exploration and querying - using a set of RESTful services. + +## Docker building and running + +``` +docker build -t metamodel-service . +docker run --rm -p 8080:8080 metamodel-service +``` + +And then go to http://localhost:8080 (assuming localhost is your docker machine). From 972ea4b1549db94ab595956a043e86fa80dfceed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Sun, 5 Jun 2016 23:00:03 -0700 Subject: [PATCH 04/19] Added DataContextController. Removed half-baked error handling for now. --- .../service/app/DataContextDefinition.java | 24 +++++ .../service/app/DataContextRegistry.java | 2 + .../app/InMemoryDataContextRegistry.java | 17 ++++ .../controllers/DataContextController.java | 87 +++++++++++++++++++ .../service/controllers/SchemaController.java | 76 ++++++++++++++++ .../service/controllers/TableController.java | 84 ++++++++++++++++++ .../service/controllers/TenantController.java | 77 +++++++--------- .../model/RestDataContextDefinition.java | 33 +++++++ .../model/{Link.java => RestLink.java} | 7 +- .../RootInformationControllerTest.java | 2 +- 10 files changed, 361 insertions(+), 48 deletions(-) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java rename service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/{Link.java => RestLink.java} (92%) diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java new file mode 100644 index 000000000..955ff6ce9 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java @@ -0,0 +1,24 @@ +/** + * 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.service.app; + +public interface DataContextDefinition { + + // TODO +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java index dd18a04d7..d2a7526ff 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java @@ -29,5 +29,7 @@ public interface DataContextRegistry { public List getDataContextIdentifiers(); + public String registerDataContext(String dataContextIdentifier, DataContextDefinition dataContextDef) throws IllegalArgumentException; + public DataContext openDataContext(String dataContextIdentifier) throws IllegalArgumentException; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java index 89d835dfa..f9b6aa0c0 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.apache.metamodel.DataContext; +import org.apache.metamodel.pojo.PojoDataContext; public class InMemoryDataContextRegistry implements DataContextRegistry { @@ -34,6 +35,22 @@ public InMemoryDataContextRegistry() { dataContextIdentifiers = new LinkedHashMap<>(); } + @Override + public String registerDataContext(final String dataContextIdentifier, final DataContextDefinition dataContextDef) + throws IllegalArgumentException { + if (dataContextIdentifiers.containsKey(dataContextIdentifier)) { + throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier); + } + dataContextIdentifiers.put(dataContextIdentifier, new Supplier() { + @Override + public DataContext get() { + // TODO: Do a proper transformation from definition to instance + return new PojoDataContext(); + } + }); + return dataContextIdentifier; + } + @Override public List getDataContextIdentifiers() { return dataContextIdentifiers.keySet().stream().collect(Collectors.toList()); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java new file mode 100644 index 000000000..441c74b5d --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java @@ -0,0 +1,87 @@ +/** + * 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.service.controllers; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.validation.Valid; +import javax.ws.rs.core.UriBuilder; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.apache.metamodel.service.controllers.model.RestLink; +import org.apache.metamodel.service.controllers.model.RestDataContextDefinition; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/{tenant}/{dataContext}", produces = MediaType.APPLICATION_JSON_VALUE) +public class DataContextController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public DataContextController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.PUT) + @ResponseBody + public Map put(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataContextId, + @Valid @RequestBody RestDataContextDefinition dataContextDefinition) { + final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataContextRegistry() + .registerDataContext(dataContextId, dataContextDefinition); + + return get(tenantId, dataContextIdentifier); + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataContextId) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId); + + final String tenantIdentifier = tenantContext.getTenantIdentifier(); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}"); + + final List schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder + .build(tenantIdentifier, dataContextId, s))).collect(Collectors.toList()); + + final Map map = new LinkedHashMap<>(); + map.put("type", "data-context"); + map.put("name", dataContextId); + map.put("tenant", tenantIdentifier); + map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantIdentifier, dataContextId)); + map.put("schemas", schemaLinks); + return map; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java new file mode 100644 index 000000000..9c18c3b7f --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java @@ -0,0 +1,76 @@ +/** + * 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.service.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.schema.Schema; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.apache.metamodel.service.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}/{dataContext}/schemas/{schema}", produces = MediaType.APPLICATION_JSON_VALUE) +public class SchemaController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public SchemaController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataContextId, @PathVariable("schema") String schemaId) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId); + + final Schema schema = dataContext.getSchemaByName(schemaId); + final String tenantIdentifier = tenantContext.getTenantIdentifier(); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}/tables/{table}"); + + final String schemaName = schema.getName(); + final List tableLinks = Arrays.stream(schema.getTableNames()).map(t -> new RestLink(String.valueOf(t), + uriBuilder.build(tenantIdentifier, dataContextId, schemaName, t))).collect(Collectors.toList()); + + final Map map = new LinkedHashMap<>(); + map.put("type", "schema"); + map.put("name", schemaName); + map.put("data-context", dataContextId); + map.put("tenant", tenantIdentifier); + map.put("tables", tableLinks); + return map; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java new file mode 100644 index 000000000..373c1dbe0 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java @@ -0,0 +1,84 @@ +/** + * 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.service.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.schema.Schema; +import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.apache.metamodel.service.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}/{dataContext}/schemas/{schema}/tables/{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 get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataContextId, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId); + + final Schema schema = dataContext.getSchemaByName(schemaId); + final Table table = schema.getTableByName(tableId); + + final String tenantIdentifier = tenantContext.getTenantIdentifier(); + final UriBuilder uriBuilder = UriBuilder.fromPath( + "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}"); + + final String tableName = table.getName(); + final String schemaName = schema.getName(); + final List columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf(c), + uriBuilder.build(tenantIdentifier, dataContextId, schemaName, tableName, c))).collect(Collectors + .toList()); + + final Map map = new LinkedHashMap<>(); + map.put("type", "table"); + map.put("name", tableName); + map.put("schema", schemaName); + map.put("data-context", dataContextId); + map.put("tenant", tenantIdentifier); + map.put("columns", columnsLinks); + return map; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java index afefb9e96..48a0ed153 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java @@ -18,50 +18,67 @@ */ package org.apache.metamodel.service.controllers; -import java.io.IOException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.UriBuilder; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; -import org.apache.metamodel.service.controllers.model.Link; +import org.apache.metamodel.service.controllers.model.RestLink; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.ExceptionHandler; 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; -import org.springframework.web.client.HttpServerErrorException; @RestController @RequestMapping(value = "/{tenant}", produces = MediaType.APPLICATION_JSON_VALUE) public class TenantController { + private final TenantRegistry tenantRegistry; + @Autowired - TenantRegistry tenantRegistry; + public TenantController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map getTenant(@PathVariable("tenant") String tenantName) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName); + if (tenantContext == null) { + throw new IllegalArgumentException("No such tenant: " + tenantName); + } + + final String tenantIdentifier = tenantContext.getTenantIdentifier(); + + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}"); + + final List dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers(); + final List dataContextLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder.build( + tenantIdentifier, s))).collect(Collectors.toList()); + + final Map map = new LinkedHashMap<>(); + map.put("type", "tenant"); + map.put("name", tenantIdentifier); + map.put("data-contexts", dataContextLinks); + return map; + } @RequestMapping(method = RequestMethod.PUT) @ResponseBody public Map putTenant(@PathVariable("tenant") String tenantName) { - final TenantContext tenantContext; - try { - tenantContext = tenantRegistry.createTenantContext(tenantName); - } catch (IllegalArgumentException e) { - throw new HttpServerErrorException(HttpStatus.CONFLICT, e.getMessage()); - } + final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName); final String tenantIdentifier = tenantContext.getTenantIdentifier(); final Map map = new LinkedHashMap<>(); map.put("type", "tenant"); - map.put("id", tenantIdentifier); + map.put("name", tenantIdentifier); return map; } @@ -72,42 +89,14 @@ public Map deleteTenant(@PathVariable("tenant") String tenantNam final boolean deleted = tenantRegistry.deleteTenantContext(tenantName); if (!deleted) { - throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName); + throw new IllegalArgumentException("No such tenant: " + tenantName); } final Map map = new LinkedHashMap<>(); map.put("type", "tenant"); - map.put("id", tenantName); + map.put("name", tenantName); map.put("deleted", deleted); return map; } - - @RequestMapping(method = RequestMethod.GET) - @ResponseBody - public Map getTenant(@PathVariable("tenant") String tenantName) { - final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName); - if (tenantContext == null) { - throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName); - } - - final String tenantIdentifier = tenantContext.getTenantIdentifier(); - - final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}"); - - final List dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers(); - final List dataContextLinks = dataContextIdentifiers.stream().map(s -> new Link(s, uriBuilder.build( - tenantIdentifier, s))).collect(Collectors.toList()); - - final Map map = new LinkedHashMap<>(); - map.put("type", "tenant"); - map.put("id", tenantIdentifier); - map.put("data-contexts", dataContextLinks); - return map; - } - - @ExceptionHandler(HttpServerErrorException.class) - public void handleException(HttpServerErrorException e, HttpServletResponse resp) throws IOException { - resp.sendError(e.getStatusCode().value(), e.getStatusText()); - } } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java new file mode 100644 index 000000000..ba2002ebd --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java @@ -0,0 +1,33 @@ +/** + * 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.service.controllers.model; + +import javax.validation.constraints.NotNull; + +import org.apache.metamodel.service.app.DataContextDefinition; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class RestDataContextDefinition implements DataContextDefinition { + + @JsonProperty(value = "type", required = true) + @NotNull + public String type; + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java similarity index 92% rename from service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java rename to service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java index 487155391..28303890f 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java @@ -25,17 +25,18 @@ * Represents a hyper-link to a service (typically provided in the REST * responses) */ -public class Link implements Serializable { +public class RestLink implements Serializable { private static final long serialVersionUID = 1L; private String name; + private URI uri; - public Link() { + public RestLink() { } - public Link(String name, URI uri) { + public RestLink(String name, URI uri) { this(); this.name = name; this.uri = uri; diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java index 58a5ca558..8e3dab322 100644 --- a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java @@ -47,7 +47,7 @@ public void init() { } @Test - public void testGenericMessageSuccess() throws Exception { + public void testGet() throws Exception { final MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/").contentType( MediaType.APPLICATION_JSON); From 6f6f776b1372f7dc6a24e427ced1c6046d696004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 6 Jun 2016 20:14:03 -0700 Subject: [PATCH 05/19] Fixed cosmetic review remarks (EOLs and Docker EXPOSE redundancy) --- service-webapp/Dockerfile | 1 - .../src/main/resources/context/application-context.xml | 2 +- service-webapp/src/main/resources/logback.xml | 2 +- service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile index 033d6e316..d154aca50 100644 --- a/service-webapp/Dockerfile +++ b/service-webapp/Dockerfile @@ -21,5 +21,4 @@ RUN rm -rf $CATALINA_HOME/webapps \ mkdir $CATALINA_HOME/webapps COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war -EXPOSE 8080 CMD ["catalina.sh", "run"] diff --git a/service-webapp/src/main/resources/context/application-context.xml b/service-webapp/src/main/resources/context/application-context.xml index 89a1b2865..e31feb102 100644 --- a/service-webapp/src/main/resources/context/application-context.xml +++ b/service-webapp/src/main/resources/context/application-context.xml @@ -31,4 +31,4 @@ under the License. - \ No newline at end of file + diff --git a/service-webapp/src/main/resources/logback.xml b/service-webapp/src/main/resources/logback.xml index de862de4e..8ba8596c6 100644 --- a/service-webapp/src/main/resources/logback.xml +++ b/service-webapp/src/main/resources/logback.xml @@ -30,4 +30,4 @@ under the License. - \ No newline at end of file + diff --git a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml index 12af16fca..41cf38c1f 100644 --- a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml +++ b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml @@ -54,4 +54,4 @@ under the License. - \ No newline at end of file + From 8722c196673713501edbc232ffe5ed3a213fbc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 6 Jun 2016 20:55:15 -0700 Subject: [PATCH 06/19] Built out an extensive unittest to document a full usage scenario. --- ...inition.java => DataSourceDefinition.java} | 2 +- ...tRegistry.java => DataSourceRegistry.java} | 8 +- ...y.java => InMemoryDataSourceRegistry.java} | 20 +-- .../service/app/InMemoryTenantContext.java | 8 +- .../metamodel/service/app/TenantContext.java | 4 +- .../service/controllers/ColumnController.java | 87 ++++++++++ ...troller.java => DataSourceController.java} | 34 ++-- .../service/controllers/SchemaController.java | 16 +- .../service/controllers/TableController.java | 20 +-- .../service/controllers/TenantController.java | 16 +- ...ion.java => RestDataSourceDefinition.java} | 4 +- .../TenantInteractionScenarioTest.java | 151 ++++++++++++++++++ 12 files changed, 304 insertions(+), 66 deletions(-) rename service-webapp/src/main/java/org/apache/metamodel/service/app/{DataContextDefinition.java => DataSourceDefinition.java} (95%) rename service-webapp/src/main/java/org/apache/metamodel/service/app/{DataContextRegistry.java => DataSourceRegistry.java} (74%) rename service-webapp/src/main/java/org/apache/metamodel/service/app/{InMemoryDataContextRegistry.java => InMemoryDataSourceRegistry.java} (70%) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java rename service-webapp/src/main/java/org/apache/metamodel/service/controllers/{DataContextController.java => DataSourceController.java} (71%) rename service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/{RestDataContextDefinition.java => RestDataSourceDefinition.java} (88%) create mode 100644 service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java similarity index 95% rename from service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java rename to service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java index 955ff6ce9..943222ac7 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java @@ -18,7 +18,7 @@ */ package org.apache.metamodel.service.app; -public interface DataContextDefinition { +public interface DataSourceDefinition { // TODO } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java similarity index 74% rename from service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java rename to service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java index d2a7526ff..1930354f0 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java @@ -25,11 +25,11 @@ /** * Represents a user's/tenant's registry of {@link DataContext}s. */ -public interface DataContextRegistry { +public interface DataSourceRegistry { - public List getDataContextIdentifiers(); + public List getDataSourceNames(); - public String registerDataContext(String dataContextIdentifier, DataContextDefinition dataContextDef) throws IllegalArgumentException; + public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws IllegalArgumentException; - public DataContext openDataContext(String dataContextIdentifier) throws IllegalArgumentException; + public DataContext openDataContext(String dataSourceName) throws IllegalArgumentException; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java similarity index 70% rename from service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java rename to service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java index f9b6aa0c0..f3c45e8d8 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java @@ -27,21 +27,21 @@ import org.apache.metamodel.DataContext; import org.apache.metamodel.pojo.PojoDataContext; -public class InMemoryDataContextRegistry implements DataContextRegistry { +public class InMemoryDataSourceRegistry implements DataSourceRegistry { - private final Map> dataContextIdentifiers; + private final Map> dataSources; - public InMemoryDataContextRegistry() { - dataContextIdentifiers = new LinkedHashMap<>(); + public InMemoryDataSourceRegistry() { + dataSources = new LinkedHashMap<>(); } @Override - public String registerDataContext(final String dataContextIdentifier, final DataContextDefinition dataContextDef) + public String registerDataSource(final String dataContextIdentifier, final DataSourceDefinition dataContextDef) throws IllegalArgumentException { - if (dataContextIdentifiers.containsKey(dataContextIdentifier)) { + if (dataSources.containsKey(dataContextIdentifier)) { throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier); } - dataContextIdentifiers.put(dataContextIdentifier, new Supplier() { + dataSources.put(dataContextIdentifier, new Supplier() { @Override public DataContext get() { // TODO: Do a proper transformation from definition to instance @@ -52,13 +52,13 @@ public DataContext get() { } @Override - public List getDataContextIdentifiers() { - return dataContextIdentifiers.keySet().stream().collect(Collectors.toList()); + public List getDataSourceNames() { + return dataSources.keySet().stream().collect(Collectors.toList()); } @Override public DataContext openDataContext(String dataContextIdentifier) { - final Supplier supplier = dataContextIdentifiers.get(dataContextIdentifier); + final Supplier supplier = dataSources.get(dataContextIdentifier); if (supplier == null) { throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java index 99a3e4e07..04fb708a3 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java @@ -21,20 +21,20 @@ public class InMemoryTenantContext implements TenantContext { private final String tenantIdentifier; - private final DataContextRegistry dataContextRegistry; + private final DataSourceRegistry dataContextRegistry; public InMemoryTenantContext(String tenantIdentifier) { this.tenantIdentifier = tenantIdentifier; - this.dataContextRegistry = new InMemoryDataContextRegistry(); + this.dataContextRegistry = new InMemoryDataSourceRegistry(); } @Override - public String getTenantIdentifier() { + public String getTenantName() { return tenantIdentifier; } @Override - public DataContextRegistry getDataContextRegistry() { + public DataSourceRegistry getDataSourceRegistry() { return dataContextRegistry; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java index 4e27922a8..c550a60d1 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java @@ -24,8 +24,8 @@ */ public interface TenantContext { - public String getTenantIdentifier(); + public String getTenantName(); - public DataContextRegistry getDataContextRegistry(); + public DataSourceRegistry getDataSourceRegistry(); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java new file mode 100644 index 000000000..1eb1a13eb --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java @@ -0,0 +1,87 @@ +/** + * 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.service.controllers; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.schema.Column; +import org.apache.metamodel.schema.Schema; +import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +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}/columns/{column}", + "/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}" }, produces = MediaType.APPLICATION_JSON_VALUE) +public class ColumnController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public ColumnController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId, @PathVariable("column") String columnId) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final Schema schema = dataContext.getSchemaByName(schemaId); + final Table table = schema.getTableByName(tableId); + final Column column = table.getColumnByName(columnId); + + final String tenantName = tenantContext.getTenantName(); + final String tableName = table.getName(); + final String schemaName = schema.getName(); + + final Map metadata = new LinkedHashMap<>(); + metadata.put("number", column.getColumnNumber()); + metadata.put("size", column.getColumnSize()); + metadata.put("nullable", column.isNullable()); + metadata.put("primary-key", column.isPrimaryKey()); + metadata.put("indexed", column.isIndexed()); + metadata.put("column-type", column.getType() == null ? null : column.getType().getName()); + metadata.put("native-type", column.getNativeType()); + metadata.put("remarks", column.getRemarks()); + + final Map map = new LinkedHashMap<>(); + map.put("type", "column"); + map.put("name", column.getName()); + map.put("table", tableName); + map.put("schema", schemaName); + map.put("data-context", dataSourceName); + map.put("tenant", tenantName); + map.put("metadata", metadata); + + return map; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java similarity index 71% rename from service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java rename to service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java index 441c74b5d..8be66b17d 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java @@ -31,7 +31,7 @@ import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; import org.apache.metamodel.service.controllers.model.RestLink; -import org.apache.metamodel.service.controllers.model.RestDataContextDefinition; +import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; @@ -42,23 +42,23 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = "/{tenant}/{dataContext}", produces = MediaType.APPLICATION_JSON_VALUE) -public class DataContextController { +@RequestMapping(value = "/{tenant}/{datasource}", produces = MediaType.APPLICATION_JSON_VALUE) +public class DataSourceController { private final TenantRegistry tenantRegistry; @Autowired - public DataContextController(TenantRegistry tenantRegistry) { + public DataSourceController(TenantRegistry tenantRegistry) { this.tenantRegistry = tenantRegistry; } @RequestMapping(method = RequestMethod.PUT) @ResponseBody public Map put(@PathVariable("tenant") String tenantId, - @PathVariable("dataContext") String dataContextId, - @Valid @RequestBody RestDataContextDefinition dataContextDefinition) { - final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataContextRegistry() - .registerDataContext(dataContextId, dataContextDefinition); + @PathVariable("datasource") String dataSourceId, + @Valid @RequestBody RestDataSourceDefinition dataContextDefinition) { + final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataSourceRegistry() + .registerDataSource(dataSourceId, dataContextDefinition); return get(tenantId, dataContextIdentifier); } @@ -66,21 +66,21 @@ public Map put(@PathVariable("tenant") String tenantId, @RequestMapping(method = RequestMethod.GET) @ResponseBody public Map get(@PathVariable("tenant") String tenantId, - @PathVariable("dataContext") String dataContextId) { + @PathVariable("datasource") String dataSourceName) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); - final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); - final String tenantIdentifier = tenantContext.getTenantIdentifier(); - final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}"); + final String tenantName = tenantContext.getTenantName(); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}"); final List schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder - .build(tenantIdentifier, dataContextId, s))).collect(Collectors.toList()); + .build(tenantName, dataSourceName, s))).collect(Collectors.toList()); final Map map = new LinkedHashMap<>(); - map.put("type", "data-context"); - map.put("name", dataContextId); - map.put("tenant", tenantIdentifier); - map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantIdentifier, dataContextId)); + map.put("type", "datasource"); + map.put("name", dataSourceName); + map.put("tenant", tenantName); + map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName)); map.put("schemas", schemaLinks); return map; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java index 9c18c3b7f..bfdbe5f13 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java @@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = "/{tenant}/{dataContext}/schemas/{schema}", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(value = {"/{tenant}/{dataContext}/schemas/{schema}", "/{tenant}/{dataContext}/s/{schema}"}, produces = MediaType.APPLICATION_JSON_VALUE) public class SchemaController { private final TenantRegistry tenantRegistry; @@ -53,23 +53,23 @@ public SchemaController(TenantRegistry tenantRegistry) { @RequestMapping(method = RequestMethod.GET) @ResponseBody public Map get(@PathVariable("tenant") String tenantId, - @PathVariable("dataContext") String dataContextId, @PathVariable("schema") String schemaId) { + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); - final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); final Schema schema = dataContext.getSchemaByName(schemaId); - final String tenantIdentifier = tenantContext.getTenantIdentifier(); - final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}/tables/{table}"); + final String tenantName = tenantContext.getTenantName(); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}/t/{table}"); final String schemaName = schema.getName(); final List tableLinks = Arrays.stream(schema.getTableNames()).map(t -> new RestLink(String.valueOf(t), - uriBuilder.build(tenantIdentifier, dataContextId, schemaName, t))).collect(Collectors.toList()); + uriBuilder.build(tenantName, dataSourceName, schemaName, t))).collect(Collectors.toList()); final Map map = new LinkedHashMap<>(); map.put("type", "schema"); map.put("name", schemaName); - map.put("data-context", dataContextId); - map.put("tenant", tenantIdentifier); + map.put("data-context", dataSourceName); + map.put("tenant", tenantName); map.put("tables", tableLinks); return map; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java index 373c1dbe0..cca0edc0c 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java @@ -41,7 +41,8 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}", produces = MediaType.APPLICATION_JSON_VALUE) +@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; @@ -54,30 +55,29 @@ public TableController(TenantRegistry tenantRegistry) { @RequestMapping(method = RequestMethod.GET) @ResponseBody public Map get(@PathVariable("tenant") String tenantId, - @PathVariable("dataContext") String dataContextId, @PathVariable("schema") String schemaId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, @PathVariable("table") String tableId) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); - final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); final Schema schema = dataContext.getSchemaByName(schemaId); final Table table = schema.getTableByName(tableId); - final String tenantIdentifier = tenantContext.getTenantIdentifier(); - final UriBuilder uriBuilder = UriBuilder.fromPath( - "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}"); + 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 = schema.getName(); - final List columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf(c), - uriBuilder.build(tenantIdentifier, dataContextId, schemaName, tableName, c))).collect(Collectors + final List columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf( + c), uriBuilder.build(tenantName, dataSourceName, schemaName, tableName, c))).collect(Collectors .toList()); final Map map = new LinkedHashMap<>(); map.put("type", "table"); map.put("name", tableName); map.put("schema", schemaName); - map.put("data-context", dataContextId); - map.put("tenant", tenantIdentifier); + map.put("data-context", dataSourceName); + map.put("tenant", tenantName); map.put("columns", columnsLinks); return map; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java index 48a0ed153..ef3011fa0 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java @@ -55,18 +55,18 @@ public Map getTenant(@PathVariable("tenant") String tenantName) throw new IllegalArgumentException("No such tenant: " + tenantName); } - final String tenantIdentifier = tenantContext.getTenantIdentifier(); + final String tenantNameNormalized = tenantContext.getTenantName(); - final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}"); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}"); - final List dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers(); - final List dataContextLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder.build( - tenantIdentifier, s))).collect(Collectors.toList()); + final List dataContextIdentifiers = tenantContext.getDataSourceRegistry().getDataSourceNames(); + final List dataSourceLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder + .build(tenantNameNormalized, s))).collect(Collectors.toList()); final Map map = new LinkedHashMap<>(); map.put("type", "tenant"); - map.put("name", tenantIdentifier); - map.put("data-contexts", dataContextLinks); + map.put("name", tenantNameNormalized); + map.put("datasources", dataSourceLinks); return map; } @@ -74,7 +74,7 @@ public Map getTenant(@PathVariable("tenant") String tenantName) @ResponseBody public Map putTenant(@PathVariable("tenant") String tenantName) { final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName); - final String tenantIdentifier = tenantContext.getTenantIdentifier(); + final String tenantIdentifier = tenantContext.getTenantName(); final Map map = new LinkedHashMap<>(); map.put("type", "tenant"); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java similarity index 88% rename from service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java rename to service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java index ba2002ebd..ad5f5ba16 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java @@ -20,11 +20,11 @@ import javax.validation.constraints.NotNull; -import org.apache.metamodel.service.app.DataContextDefinition; +import org.apache.metamodel.service.app.DataSourceDefinition; import com.fasterxml.jackson.annotation.JsonProperty; -public class RestDataContextDefinition implements DataContextDefinition { +public class RestDataSourceDefinition implements DataSourceDefinition { @JsonProperty(value = "type", required = true) @NotNull diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java new file mode 100644 index 000000000..acdbb1507 --- /dev/null +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java @@ -0,0 +1,151 @@ +/** + * 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.service.controllers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Map; + +import org.apache.metamodel.service.app.InMemoryTenantRegistry; +import org.apache.metamodel.service.app.TenantRegistry; +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); + + mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController, + tableController, columnController).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'}".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=Schema, uri=/tenant1/mydata/s/Schema}]", + 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/information_schema")) + .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("information_schema", map.get("name")); + + assertEquals("[{name=tables, uri=/tenant1/mydata/s/information_schema/t/tables}, " + + "{name=columns, uri=/tenant1/mydata/s/information_schema/t/columns}, " + + "{name=relationships, uri=/tenant1/mydata/s/information_schema/t/relationships}]", map.get( + "tables").toString()); + } + + // explore table + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get( + "/tenant1/mydata/s/information_schema/t/columns")).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("columns", map.get("name")); + + assertEquals("[{name=name, uri=/tenant1/mydata/s/information_schema/t/columns/c/name}, " + + "{name=type, uri=/tenant1/mydata/s/information_schema/t/columns/c/type}, " + + "{name=native_type, uri=/tenant1/mydata/s/information_schema/t/columns/c/native_type}, " + + "{name=size, uri=/tenant1/mydata/s/information_schema/t/columns/c/size}, " + + "{name=nullable, uri=/tenant1/mydata/s/information_schema/t/columns/c/nullable}, " + + "{name=indexed, uri=/tenant1/mydata/s/information_schema/t/columns/c/indexed}, " + + "{name=table, uri=/tenant1/mydata/s/information_schema/t/columns/c/table}, " + + "{name=remarks, uri=/tenant1/mydata/s/information_schema/t/columns/c/remarks}]", map.get( + "columns").toString()); + } + + // explore column + { + final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get( + "/tenant1/mydata/s/information_schema/t/columns/c/nullable")).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("nullable", map.get("name")); + + assertEquals( + "{number=4, size=null, nullable=true, primary-key=false, indexed=false, column-type=BOOLEAN, native-type=null, remarks=null}", + map.get("metadata").toString()); + } + } +} From 8d4d03519d56ce482139ea42fb178504e288c892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Wed, 8 Jun 2016 02:35:20 -0700 Subject: [PATCH 07/19] Added ability to query data and to specify POJO table defs. --- .../service/app/DataContextSupplier.java | 72 +++++++++++++++++ .../service/app/DataSourceDefinition.java | 6 ++ .../app/InMemoryDataSourceRegistry.java | 24 +++--- .../service/controllers/QueryController.java | 79 +++++++++++++++++++ .../model/RestDataSourceDefinition.java | 25 +++++- .../TenantInteractionScenarioTest.java | 66 +++++++++------- 6 files changed, 226 insertions(+), 46 deletions(-) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java new file mode 100644 index 000000000..3b36e2b19 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java @@ -0,0 +1,72 @@ +/** + * 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.service.app; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.pojo.ArrayTableDataProvider; +import org.apache.metamodel.pojo.PojoDataContext; +import org.apache.metamodel.pojo.TableDataProvider; +import org.apache.metamodel.util.SimpleTableDef; +import org.apache.metamodel.util.SimpleTableDefParser; + +public class DataContextSupplier implements Supplier { + + private final String dataSourceName; + private final DataSourceDefinition dataSourceDefinition; + + public DataContextSupplier(String dataSourceName, DataSourceDefinition dataSourceDefinition) { + this.dataSourceName = dataSourceName; + this.dataSourceDefinition = dataSourceDefinition; + } + + @Override + public DataContext get() { + final String type = dataSourceDefinition.getType(); + switch (type.toLowerCase()) { + case "pojo": + final List> tableDataProviders; + + final Object tableDefinitions = dataSourceDefinition.getTableDefinitions(); + if (tableDefinitions == null) { + tableDataProviders = new ArrayList<>(0); + } else if (tableDefinitions instanceof String) { + final SimpleTableDef[] tableDefs = SimpleTableDefParser.parseTableDefs((String) tableDefinitions); + tableDataProviders = Arrays.stream(tableDefs).map((tableDef) -> { + return new ArrayTableDataProvider(tableDef, new ArrayList()); + }).collect(Collectors.toList()); + } else { + throw new UnsupportedOperationException("Unsupported table definition type: " + tableDefinitions); + } + + final String schemaName = dataSourceDefinition.getSchemaName() == null ? dataSourceName + : dataSourceDefinition.getSchemaName(); + + return new PojoDataContext(schemaName, tableDataProviders); + default: + throw new UnsupportedOperationException("Unsupported data source type: " + type); + } + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java index 943222ac7..11140a73e 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java @@ -20,5 +20,11 @@ public interface DataSourceDefinition { + public String getType(); + + public Object getTableDefinitions(); + + public String getSchemaName(); + // TODO } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java index f3c45e8d8..3a6eceed8 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java @@ -25,7 +25,6 @@ import java.util.stream.Collectors; import org.apache.metamodel.DataContext; -import org.apache.metamodel.pojo.PojoDataContext; public class InMemoryDataSourceRegistry implements DataSourceRegistry { @@ -36,19 +35,14 @@ public InMemoryDataSourceRegistry() { } @Override - public String registerDataSource(final String dataContextIdentifier, final DataSourceDefinition dataContextDef) + public String registerDataSource(final String name, final DataSourceDefinition dataSourceDef) throws IllegalArgumentException { - if (dataSources.containsKey(dataContextIdentifier)) { - throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier); + if (dataSources.containsKey(name)) { + throw new IllegalArgumentException("DataContext already exist: " + name); } - dataSources.put(dataContextIdentifier, new Supplier() { - @Override - public DataContext get() { - // TODO: Do a proper transformation from definition to instance - return new PojoDataContext(); - } - }); - return dataContextIdentifier; + + dataSources.put(name, new DataContextSupplier(name, dataSourceDef)); + return name; } @Override @@ -57,10 +51,10 @@ public List getDataSourceNames() { } @Override - public DataContext openDataContext(String dataContextIdentifier) { - final Supplier supplier = dataSources.get(dataContextIdentifier); + public DataContext openDataContext(String name) { + final Supplier supplier = dataSources.get(name); if (supplier == null) { - throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier); + throw new IllegalArgumentException("No such DataContext: " + name); } return supplier.get(); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java new file mode 100644 index 000000000..e5c1e72ac --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java @@ -0,0 +1,79 @@ +/** + * 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.service.controllers; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.data.DataSet; +import org.apache.metamodel.query.Query; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = { "/{tenant}/{dataContext}/query", + "/{tenant}/{dataContext}/q" }, produces = MediaType.APPLICATION_JSON_VALUE) +public class QueryController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public QueryController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, + @RequestParam(value = "sql", required = true) String queryString, + @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 Query query = dataContext.parseQuery(queryString); + if (offset != null) { + query.setFirstRow(offset); + } + if (limit != null) { + query.setMaxRows(limit); + } + + final DataSet dataSet = dataContext.executeQuery(query); + + final Map map = new LinkedHashMap<>(); + map.put("type", "dataset"); + map.put("header", Arrays.stream(dataSet.getSelectItems()).map((si) -> si.toString()).collect(Collectors + .toList())); + map.put("data", dataSet.toObjectArrays()); + return map; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java index ad5f5ba16..48c592672 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java @@ -22,12 +22,35 @@ import org.apache.metamodel.service.app.DataSourceDefinition; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; public class RestDataSourceDefinition implements DataSourceDefinition { @JsonProperty(value = "type", required = true) @NotNull - public String type; + private String type; + @JsonProperty(value = "table-definitions", required = false) + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object tableDefinitions; + + @JsonProperty(value = "schema-name", required = false) + @JsonInclude(JsonInclude.Include.NON_NULL) + private String schemaName; + + @Override + public String getType() { + return type; + } + + @Override + public Object getTableDefinitions() { + return tableDefinitions; + } + + @Override + public String getSchemaName() { + return schemaName; + } } diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java index acdbb1507..35c469535 100644 --- a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java @@ -48,9 +48,10 @@ public void init() { SchemaController schemaController = new SchemaController(tenantRegistry); TableController tableController = new TableController(tenantRegistry); ColumnController columnController = new ColumnController(tenantRegistry); + QueryController queryController = new QueryController(tenantRegistry); mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController, - tableController, columnController).build(); + tableController, columnController, queryController).build(); } @Test @@ -70,15 +71,16 @@ public void testScenario() throws Exception { // create datasource { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content( - "{'type':'pojo'}".replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( - MockMvcResultMatchers.status().isOk()).andReturn(); + "{'type':'pojo','table-definitions':'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=Schema, uri=/tenant1/mydata/s/Schema}]", + "[{name=information_schema, uri=/tenant1/mydata/s/information_schema}, {name=mydata, uri=/tenant1/mydata/s/mydata}]", map.get("schemas").toString()); } @@ -96,56 +98,60 @@ public void testScenario() throws Exception { // explore schema { - final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/information_schema")) - .andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); + 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("information_schema", map.get("name")); + assertEquals("mydata", map.get("name")); - assertEquals("[{name=tables, uri=/tenant1/mydata/s/information_schema/t/tables}, " - + "{name=columns, uri=/tenant1/mydata/s/information_schema/t/columns}, " - + "{name=relationships, uri=/tenant1/mydata/s/information_schema/t/relationships}]", map.get( - "tables").toString()); + assertEquals( + "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello world, uri=/tenant1/mydata/s/mydata/t/hello%20world}]", + map.get("tables").toString()); } // explore table { - final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get( - "/tenant1/mydata/s/information_schema/t/columns")).andExpect(MockMvcResultMatchers.status().isOk()) - .andReturn(); + 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("columns", map.get("name")); - - assertEquals("[{name=name, uri=/tenant1/mydata/s/information_schema/t/columns/c/name}, " - + "{name=type, uri=/tenant1/mydata/s/information_schema/t/columns/c/type}, " - + "{name=native_type, uri=/tenant1/mydata/s/information_schema/t/columns/c/native_type}, " - + "{name=size, uri=/tenant1/mydata/s/information_schema/t/columns/c/size}, " - + "{name=nullable, uri=/tenant1/mydata/s/information_schema/t/columns/c/nullable}, " - + "{name=indexed, uri=/tenant1/mydata/s/information_schema/t/columns/c/indexed}, " - + "{name=table, uri=/tenant1/mydata/s/information_schema/t/columns/c/table}, " - + "{name=remarks, uri=/tenant1/mydata/s/information_schema/t/columns/c/remarks}]", map.get( - "columns").toString()); + 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/information_schema/t/columns/c/nullable")).andExpect(MockMvcResultMatchers - .status().isOk()).andReturn(); + 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("nullable", map.get("name")); + assertEquals("bar", map.get("name")); assertEquals( - "{number=4, size=null, nullable=true, primary-key=false, indexed=false, column-type=BOOLEAN, native-type=null, remarks=null}", + "{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()); + } } } From 76091fe80fc9a525df19a183a5a9047d13677510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Wed, 8 Jun 2016 02:58:41 -0700 Subject: [PATCH 08/19] Added a rudimentary table data controller --- .../service/app/DataContextSupplier.java | 15 ++- .../service/controllers/QueryController.java | 6 + .../controllers/TableDataController.java | 109 ++++++++++++++++++ .../TenantInteractionScenarioTest.java | 46 +++++++- 4 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java index 3b36e2b19..a0fb6ecc6 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java @@ -35,14 +35,23 @@ public class DataContextSupplier implements Supplier { private final String dataSourceName; private final DataSourceDefinition dataSourceDefinition; + private final DataContext eagerLoadedDataContext; public DataContextSupplier(String dataSourceName, DataSourceDefinition dataSourceDefinition) { this.dataSourceName = dataSourceName; this.dataSourceDefinition = dataSourceDefinition; + this.eagerLoadedDataContext = createDataContext(true); } @Override public DataContext get() { + if (eagerLoadedDataContext != null) { + return eagerLoadedDataContext; + } + return createDataContext(false); + } + + private DataContext createDataContext(boolean eager) { final String type = dataSourceDefinition.getType(); switch (type.toLowerCase()) { case "pojo": @@ -64,7 +73,11 @@ public DataContext get() { : dataSourceDefinition.getSchemaName(); return new PojoDataContext(schemaName, tableDataProviders); - default: + } + + if (eager) { + return null; + } else { throw new UnsupportedOperationException("Unsupported data source type: " + type); } } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java index e5c1e72ac..693bc4cb9 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java @@ -60,6 +60,12 @@ public Map get(@PathVariable("tenant") String tenantId, final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); final Query query = dataContext.parseQuery(queryString); + + return executeQuery(dataContext, query, offset, limit); + } + + public static Map executeQuery(DataContext dataContext, Query query, Integer offset, Integer limit) { + if (offset != null) { query.setFirstRow(offset); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java new file mode 100644 index 000000000..a12bbac6f --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java @@ -0,0 +1,109 @@ +/** + * 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.service.controllers; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateSummary; +import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.insert.InsertInto; +import org.apache.metamodel.query.Query; +import org.apache.metamodel.schema.Schema; +import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +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 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 Schema schema = dataContext.getSchemaByName(schemaId); + final Table table = schema.getTableByName(tableId); + + final Query query = dataContext.query().from(table).selectAll().toQuery(); + + return QueryController.executeQuery(dataContext, query, offset, limit); + } + + @RequestMapping(method = RequestMethod.POST) + @ResponseBody + public Map post(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId, @RequestBody Map inputMap) { + + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + if (!(dataContext instanceof UpdateableDataContext)) { + throw new UnsupportedOperationException("Cannot perform updates on read-only datasource: " + + dataSourceName); + } + + final Schema schema = dataContext.getSchemaByName(schemaId); + final Table table = schema.getTableByName(tableId); + + final InsertInto insert = new InsertInto(table); + for (Entry entry : inputMap.entrySet()) { + insert.value(entry.getKey(), entry.getValue()); + } + + final UpdateableDataContext updateableDataContext = (UpdateableDataContext) dataContext; + final UpdateSummary result = updateableDataContext.executeUpdate(insert); + + final Map 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; + } +} diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java index 35c469535..793f88c80 100644 --- a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java @@ -49,9 +49,10 @@ public void init() { 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).build(); + tableController, columnController, queryController, tableDataController).build(); } @Test @@ -71,7 +72,7 @@ public void testScenario() throws Exception { // create datasource { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content( - "{'type':'pojo','table-definitions':'hello world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}" + "{'type':'pojo','table-definitions':'hello_world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}" .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( MockMvcResultMatchers.status().isOk()).andReturn(); @@ -107,7 +108,7 @@ public void testScenario() throws Exception { 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%20world}]", + "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello_world, uri=/tenant1/mydata/s/mydata/t/hello_world}]", map.get("tables").toString()); } @@ -150,7 +151,44 @@ public void testScenario() throws Exception { 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") + 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()); } } From 704790b16ae8754c693251149127ef12b530df7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Sun, 12 Jun 2016 21:33:17 -0700 Subject: [PATCH 09/19] Added error handling component --- pom.xml | 2 +- .../service/app/DataSourceRegistry.java | 16 +- .../app/InMemoryDataSourceRegistry.java | 8 +- .../service/app/InMemoryTenantRegistry.java | 18 +- .../metamodel/service/app/TenantRegistry.java | 13 +- .../AbstractIdentifierNamingException.java | 40 +++++ .../DataSourceAlreadyExistException.java | 28 ++++ .../DataSourceNotUpdateableException.java | 37 +++++ .../exceptions/NoSuchDataSourceException.java | 29 ++++ .../app/exceptions/NoSuchTenantException.java | 28 ++++ .../TenantAlreadyExistException.java | 28 ++++ .../controllers/DataSourceController.java | 8 +- .../service/controllers/RestErrorHandler.java | 156 ++++++++++++++++++ .../controllers/TableDataController.java | 6 +- .../service/controllers/TenantController.java | 12 +- .../controllers/model/RestErrorResponse.java | 80 +++++++++ 16 files changed, 476 insertions(+), 33 deletions(-) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java diff --git a/pom.xml b/pom.xml index 0e9d696bf..678dd3545 100644 --- a/pom.xml +++ b/pom.xml @@ -372,7 +372,7 @@ under the License. false KEYS - *.md + **/*.md example-metamodel-integrationtest-configuration.properties travis-metamodel-integrationtest-configuration.properties **/src/assembly/metamodel-packaged-assembly-descriptor.xml diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java index 1930354f0..3a9ba4309 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java @@ -21,6 +21,10 @@ import java.util.List; import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; /** * Represents a user's/tenant's registry of {@link DataContext}s. @@ -29,7 +33,15 @@ public interface DataSourceRegistry { public List getDataSourceNames(); - public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws IllegalArgumentException; + public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws DataSourceAlreadyExistException; - public DataContext openDataContext(String dataSourceName) throws IllegalArgumentException; + public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException; + + public default UpdateableDataContext openDataContextForUpdate(String dataSourceName) { + final DataContext dataContext = openDataContext(dataSourceName); + if (dataContext instanceof UpdateableDataContext) { + return (UpdateableDataContext) dataContext; + } + throw new DataSourceNotUpdateableException(dataSourceName); + }; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java index 3a6eceed8..b99c6e770 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java @@ -25,6 +25,8 @@ import java.util.stream.Collectors; import org.apache.metamodel.DataContext; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; public class InMemoryDataSourceRegistry implements DataSourceRegistry { @@ -36,9 +38,9 @@ public InMemoryDataSourceRegistry() { @Override public String registerDataSource(final String name, final DataSourceDefinition dataSourceDef) - throws IllegalArgumentException { + throws DataSourceAlreadyExistException { if (dataSources.containsKey(name)) { - throw new IllegalArgumentException("DataContext already exist: " + name); + throw new DataSourceAlreadyExistException(name); } dataSources.put(name, new DataContextSupplier(name, dataSourceDef)); @@ -54,7 +56,7 @@ public List getDataSourceNames() { public DataContext openDataContext(String name) { final Supplier supplier = dataSources.get(name); if (supplier == null) { - throw new IllegalArgumentException("No such DataContext: " + name); + throw new NoSuchDataSourceException(name); } return supplier.get(); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java index c74fb2224..6ac5bec6a 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java @@ -23,6 +23,9 @@ import java.util.Map; import java.util.stream.Collectors; +import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; +import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; + /** * In-memory {@link TenantRegistry}. This is not particularly * production-friendly as it is non-persistent, but it is useful for demo @@ -43,13 +46,17 @@ public List getTenantIdentifiers() { @Override public TenantContext getTenantContext(String tenantIdentifier) { - return tenants.get(tenantIdentifier); + final TenantContext tenant = tenants.get(tenantIdentifier); + if (tenant == null) { + throw new NoSuchTenantException(tenantIdentifier); + } + return tenant; } @Override public TenantContext createTenantContext(String tenantIdentifier) { if (tenants.containsKey(tenantIdentifier)) { - throw new IllegalArgumentException("Tenant already exist: " + tenantIdentifier); + throw new TenantAlreadyExistException(tenantIdentifier); } final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier); tenants.put(tenantIdentifier, tenantContext); @@ -57,8 +64,11 @@ public TenantContext createTenantContext(String tenantIdentifier) { } @Override - public boolean deleteTenantContext(String tenantIdentifier) { - return tenants.remove(tenantIdentifier) != null; + public void deleteTenantContext(String tenantIdentifier) { + final TenantContext removedTenant = tenants.remove(tenantIdentifier); + if (removedTenant == null) { + throw new NoSuchTenantException(tenantIdentifier); + } } } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java index 450db2df5..5c02821cd 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java @@ -20,6 +20,9 @@ import java.util.List; +import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; +import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; + /** * Represents the application's central registry of tenants */ @@ -27,9 +30,9 @@ public interface TenantRegistry { public List getTenantIdentifiers(); - public TenantContext getTenantContext(String tenantIdentifier); - - public TenantContext createTenantContext(String tenantIdentifier) throws IllegalArgumentException; - - public boolean deleteTenantContext(String tenantIdentifier); + public TenantContext getTenantContext(String tenantIdentifier) throws NoSuchTenantException; + + public TenantContext createTenantContext(String tenantIdentifier) throws TenantAlreadyExistException; + + public void deleteTenantContext(String tenantIdentifier) throws NoSuchTenantException; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java new file mode 100644 index 000000000..98f589204 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java @@ -0,0 +1,40 @@ +/** + * 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.service.app.exceptions; + +import org.apache.metamodel.MetaModelException; + +/** + * Exception super class for any exception that arises because an identifier + * (name, ID or such) is invalid for a specific context. + */ +public class AbstractIdentifierNamingException extends MetaModelException { + + private static final long serialVersionUID = 1L; + private final String identifier; + + public AbstractIdentifierNamingException(String identifier) { + super("Illegal value: " + identifier); + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java new file mode 100644 index 000000000..3d5d6506b --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java @@ -0,0 +1,28 @@ +/** + * 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.service.app.exceptions; + +public class DataSourceAlreadyExistException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public DataSourceAlreadyExistException(String name) { + super(name); + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java new file mode 100644 index 000000000..eb828cc72 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java @@ -0,0 +1,37 @@ +/** + * 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.service.app.exceptions; + +import org.apache.metamodel.MetaModelException; + +public class DataSourceNotUpdateableException extends MetaModelException { + + private static final long serialVersionUID = 1L; + + private final String dataSourceName; + + public DataSourceNotUpdateableException(String dataSourceName) { + super(dataSourceName); + this.dataSourceName = dataSourceName; + } + + public String getDataSourceName() { + return dataSourceName; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java new file mode 100644 index 000000000..b59f0162c --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java @@ -0,0 +1,29 @@ +/** + * 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.service.app.exceptions; + +public class NoSuchDataSourceException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchDataSourceException(String name) { + super(name); + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java new file mode 100644 index 000000000..b123ae2bd --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java @@ -0,0 +1,28 @@ +/** + * 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.service.app.exceptions; + +public class NoSuchTenantException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchTenantException(String tenantIdentifier) { + super(tenantIdentifier); + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java new file mode 100644 index 000000000..f24c1147a --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java @@ -0,0 +1,28 @@ +/** + * 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.service.app.exceptions; + +public class TenantAlreadyExistException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public TenantAlreadyExistException(String tenantIdentifier) { + super(tenantIdentifier); + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java index 8be66b17d..9c678fff1 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java @@ -28,10 +28,11 @@ import javax.ws.rs.core.UriBuilder; import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateableDataContext; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; -import org.apache.metamodel.service.controllers.model.RestLink; import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition; +import org.apache.metamodel.service.controllers.model.RestLink; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; @@ -73,13 +74,14 @@ public Map get(@PathVariable("tenant") String tenantId, final String tenantName = tenantContext.getTenantName(); final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}"); - final List schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder - .build(tenantName, dataSourceName, s))).collect(Collectors.toList()); + final List schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, + uriBuilder.build(tenantName, dataSourceName, s))).collect(Collectors.toList()); final Map map = new LinkedHashMap<>(); map.put("type", "datasource"); map.put("name", dataSourceName); map.put("tenant", tenantName); + map.put("updateable", dataContext instanceof UpdateableDataContext); map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName)); map.put("schemas", schemaLinks); return map; diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java new file mode 100644 index 000000000..690ee0e9b --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java @@ -0,0 +1,156 @@ +/** + * 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.service.controllers; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.metamodel.query.parser.QueryParserException; +import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; +import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; +import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; +import org.apache.metamodel.service.controllers.model.RestErrorResponse; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ControllerAdvice +public class RestErrorHandler { + + /** + * Method binding issues (raised by Spring framework) - mapped to + * BAD_REQUEST. + * + * @param ex + * @return + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public RestErrorResponse processValidationError(MethodArgumentNotValidException ex) { + final BindingResult result = ex.getBindingResult(); + + final Map globalErrorsMap = new LinkedHashMap<>(); + final List globalErrors = result.getGlobalErrors(); + for (ObjectError objectError : globalErrors) { + globalErrorsMap.put(objectError.getObjectName(), objectError.getDefaultMessage()); + } + + final List fieldErrors = result.getFieldErrors(); + final Map fieldErrorsMap = new LinkedHashMap<>(); + for (FieldError fieldError : fieldErrors) { + fieldErrorsMap.put(fieldError.getObjectName() + '.' + fieldError.getField(), fieldError + .getDefaultMessage()); + } + + final Map additionalDetails = new LinkedHashMap<>(); + if (!globalErrorsMap.isEmpty()) { + additionalDetails.put("global-errors", globalErrorsMap); + } + if (!fieldErrorsMap.isEmpty()) { + additionalDetails.put("field-errors", fieldErrorsMap); + } + final RestErrorResponse errorResponse = new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), + "Failed to validate request"); + if (!additionalDetails.isEmpty()) { + errorResponse.setAdditionalDetails(additionalDetails); + } + return errorResponse; + } + + /** + * No such [Entity] exception handler method - mapped to NOT_FOUND. + * + * @param ex + * @return + */ + @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class }) + @ResponseStatus(HttpStatus.NOT_FOUND) + @ResponseBody + public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) { + return new RestErrorResponse(HttpStatus.NOT_FOUND.value(), "Not found: " + ex.getIdentifier()); + } + + /** + * [Entity] already exist exception handler method - mapped to CONFLICT. + * + * @param ex + * @return + */ + @ExceptionHandler({ TenantAlreadyExistException.class, DataSourceAlreadyExistException.class }) + @ResponseStatus(HttpStatus.CONFLICT) + @ResponseBody + public RestErrorResponse processEntityAlreadyExist(AbstractIdentifierNamingException ex) { + return new RestErrorResponse(HttpStatus.CONFLICT.value(), "Already exist: " + ex.getIdentifier()); + } + + /** + * DataSource not updateable exception handler method - mapped to + * BAD_REQUEST. + * + * @param ex + * @return + */ + @ExceptionHandler(DataSourceNotUpdateableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public RestErrorResponse processDataSourceNotUpdateable(DataSourceNotUpdateableException ex) { + return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), "DataSource not updateable: " + ex + .getDataSourceName()); + } + + /** + * Query parsing exception - mapped to BAD_REQUEST. + * + * @param ex + * @return + */ + @ExceptionHandler(QueryParserException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public RestErrorResponse processQueryParsingError(QueryParserException ex) { + return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + } + + /** + * Catch-all exception handler method - mapped to INTERNAL_SERVER_ERROR. + * + * @param ex + * @return + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public RestErrorResponse processAnyException(Exception ex) { + final Map additionalDetails = new HashMap<>(); + additionalDetails.put("exception_type", ex.getClass().getName()); + return new RestErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), additionalDetails); + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java index a12bbac6f..e25258fa4 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java @@ -77,11 +77,7 @@ public Map post(@PathVariable("tenant") String tenantId, @PathVariable("table") String tableId, @RequestBody Map inputMap) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); - final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); - if (!(dataContext instanceof UpdateableDataContext)) { - throw new UnsupportedOperationException("Cannot perform updates on read-only datasource: " - + dataSourceName); - } + final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName); final Schema schema = dataContext.getSchemaByName(schemaId); final Table table = schema.getTableByName(tableId); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java index ef3011fa0..9582bbe99 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java @@ -51,10 +51,6 @@ public TenantController(TenantRegistry tenantRegistry) { @ResponseBody public Map getTenant(@PathVariable("tenant") String tenantName) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName); - if (tenantContext == null) { - throw new IllegalArgumentException("No such tenant: " + tenantName); - } - final String tenantNameNormalized = tenantContext.getTenantName(); final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}"); @@ -86,16 +82,12 @@ public Map putTenant(@PathVariable("tenant") String tenantName) @RequestMapping(method = RequestMethod.DELETE) @ResponseBody public Map deleteTenant(@PathVariable("tenant") String tenantName) { - final boolean deleted = tenantRegistry.deleteTenantContext(tenantName); - - if (!deleted) { - throw new IllegalArgumentException("No such tenant: " + tenantName); - } + tenantRegistry.deleteTenantContext(tenantName); final Map map = new LinkedHashMap<>(); map.put("type", "tenant"); map.put("name", tenantName); - map.put("deleted", deleted); + map.put("deleted", true); return map; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java new file mode 100644 index 000000000..ed27dfee1 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/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.service.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 additionalDetails; + + public RestErrorResponse(int code, String message) { + this(code, message, null); + } + + public RestErrorResponse(int code, String message, Map 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 additionalDetails) { + this.additionalDetails = additionalDetails; + } + + public Map getAdditionalDetails() { + return additionalDetails; + } + +} From 79af7dded8df48e829a70b1d8c2defdd7076cb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Sun, 12 Jun 2016 22:11:06 -0700 Subject: [PATCH 10/19] Improved throwing of exceptions from controllers. --- .../service/app/DataContextTraverser.java | 67 +++++++++++++++++++ .../app/exceptions/NoSuchColumnException.java | 29 ++++++++ .../app/exceptions/NoSuchSchemaException.java | 29 ++++++++ .../app/exceptions/NoSuchTableException.java | 29 ++++++++ .../service/controllers/ColumnController.java | 13 ++-- .../service/controllers/RestErrorHandler.java | 6 +- .../service/controllers/SchemaController.java | 8 ++- .../service/controllers/TableController.java | 9 +-- .../controllers/TableDataController.java | 12 ++-- 9 files changed, 183 insertions(+), 19 deletions(-) create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java new file mode 100644 index 000000000..6ec712231 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java @@ -0,0 +1,67 @@ +/** + * 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.service.app; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.schema.Column; +import org.apache.metamodel.schema.Schema; +import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.exceptions.NoSuchColumnException; +import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException; +import org.apache.metamodel.service.app.exceptions.NoSuchTableException; + +/** + * Utility object responsible for traversing the schema/table/column structures + * of a {@link DataContext} based on String identifiers and names. + * + * This class will throw the appropriate exceptions if needed which is more + * communicative than the usual NPEs that would otherwise be thrown. + */ +public class DataContextTraverser { + + private final DataContext dataContext; + + public DataContextTraverser(DataContext dataContext) { + this.dataContext = dataContext; + } + + public Schema getSchema(String schemaName) { + final Schema schema = dataContext.getSchemaByName(schemaName); + if (schema == null) { + throw new NoSuchSchemaException(schemaName); + } + return schema; + } + + public Table getTable(String schemaName, String tableName) { + final Table table = getSchema(schemaName).getTableByName(tableName); + if (table == null) { + throw new NoSuchTableException(tableName); + } + return table; + } + + public Column getColumn(String schemaName, String tableName, String columnName) { + final Column column = getTable(schemaName, tableName).getColumnByName(columnName); + if (column == null) { + throw new NoSuchColumnException(columnName); + } + return column; + } +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java new file mode 100644 index 000000000..1902af12f --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java @@ -0,0 +1,29 @@ +/** + * 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.service.app.exceptions; + +public class NoSuchColumnException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchColumnException(String name) { + super(name); + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java new file mode 100644 index 000000000..5b238a8ab --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java @@ -0,0 +1,29 @@ +/** + * 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.service.app.exceptions; + +public class NoSuchSchemaException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchSchemaException(String name) { + super(name); + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java new file mode 100644 index 000000000..73141cdcc --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java @@ -0,0 +1,29 @@ +/** + * 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.service.app.exceptions; + +public class NoSuchTableException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchTableException(String name) { + super(name); + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java index 1eb1a13eb..7d6d5ffca 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java @@ -23,8 +23,7 @@ import org.apache.metamodel.DataContext; import org.apache.metamodel.schema.Column; -import org.apache.metamodel.schema.Schema; -import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.DataContextTraverser; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; import org.springframework.beans.factory.annotation.Autowired; @@ -55,13 +54,13 @@ public Map get(@PathVariable("tenant") String tenantId, final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); - final Schema schema = dataContext.getSchemaByName(schemaId); - final Table table = schema.getTableByName(tableId); - final Column column = table.getColumnByName(columnId); + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Column column = traverser.getColumn(schemaId, tableId, columnId); final String tenantName = tenantContext.getTenantName(); - final String tableName = table.getName(); - final String schemaName = schema.getName(); + final String tableName = column.getTable().getName(); + final String schemaName = column.getTable().getSchema().getName(); final Map metadata = new LinkedHashMap<>(); metadata.put("number", column.getColumnNumber()); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java index 690ee0e9b..8a081513f 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java @@ -27,7 +27,10 @@ import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException; import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException; +import org.apache.metamodel.service.app.exceptions.NoSuchColumnException; import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; +import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException; +import org.apache.metamodel.service.app.exceptions.NoSuchTableException; import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; import org.apache.metamodel.service.controllers.model.RestErrorResponse; @@ -91,7 +94,8 @@ public RestErrorResponse processValidationError(MethodArgumentNotValidException * @param ex * @return */ - @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class }) + @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class, NoSuchSchemaException.class, + NoSuchTableException.class, NoSuchColumnException.class }) @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) { diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java index bfdbe5f13..6208d497f 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java @@ -28,6 +28,7 @@ import org.apache.metamodel.DataContext; import org.apache.metamodel.schema.Schema; +import org.apache.metamodel.service.app.DataContextTraverser; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; import org.apache.metamodel.service.controllers.model.RestLink; @@ -40,7 +41,8 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(value = {"/{tenant}/{dataContext}/schemas/{schema}", "/{tenant}/{dataContext}/s/{schema}"}, produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}", + "/{tenant}/{dataContext}/s/{schema}" }, produces = MediaType.APPLICATION_JSON_VALUE) public class SchemaController { private final TenantRegistry tenantRegistry; @@ -57,7 +59,9 @@ public Map get(@PathVariable("tenant") String tenantId, final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); - final Schema schema = dataContext.getSchemaByName(schemaId); + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Schema schema = traverser.getSchema(schemaId); final String tenantName = tenantContext.getTenantName(); final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}/t/{table}"); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java index cca0edc0c..4157dacf9 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java @@ -27,8 +27,8 @@ import javax.ws.rs.core.UriBuilder; import org.apache.metamodel.DataContext; -import org.apache.metamodel.schema.Schema; import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.DataContextTraverser; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; import org.apache.metamodel.service.controllers.model.RestLink; @@ -59,15 +59,16 @@ public Map get(@PathVariable("tenant") String tenantId, @PathVariable("table") String tableId) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final DataContextTraverser traverser = new DataContextTraverser(dataContext); - final Schema schema = dataContext.getSchemaByName(schemaId); - final Table table = schema.getTableByName(tableId); + 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 = schema.getName(); + final String schemaName = table.getSchema().getName(); final List columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf( c), uriBuilder.build(tenantName, dataSourceName, schemaName, tableName, c))).collect(Collectors .toList()); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java index e25258fa4..c48d74f81 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java @@ -27,8 +27,8 @@ import org.apache.metamodel.UpdateableDataContext; import org.apache.metamodel.insert.InsertInto; import org.apache.metamodel.query.Query; -import org.apache.metamodel.schema.Schema; import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.DataContextTraverser; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; import org.springframework.beans.factory.annotation.Autowired; @@ -61,9 +61,10 @@ public Map get(@PathVariable("tenant") String tenantId, @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 Schema schema = dataContext.getSchemaByName(schemaId); - final Table table = schema.getTableByName(tableId); + final Table table = traverser.getTable(schemaId, tableId); final Query query = dataContext.query().from(table).selectAll().toQuery(); @@ -79,8 +80,9 @@ public Map post(@PathVariable("tenant") String tenantId, final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName); - final Schema schema = dataContext.getSchemaByName(schemaId); - final Table table = schema.getTableByName(tableId); + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Table table = traverser.getTable(schemaId, tableId); final InsertInto insert = new InsertInto(table); for (Entry entry : inputMap.entrySet()) { From 0a82d13b19ea0f8aa3aabcd140caaf3274a8e5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 13 Jun 2016 22:15:34 -0700 Subject: [PATCH 11/19] Updated interface definitions as per new Swagger yaml file --- .../service/controllers/ColumnController.java | 2 +- .../controllers/DataSourceController.java | 2 +- .../service/controllers/SchemaController.java | 2 +- .../service/controllers/TableController.java | 2 +- .../controllers/TableDataController.java | 26 +- .../TenantInteractionScenarioTest.java | 4 +- service-webapp/swagger.yaml | 503 ++++++++++++++++++ 7 files changed, 526 insertions(+), 15 deletions(-) create mode 100644 service-webapp/swagger.yaml diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java index 7d6d5ffca..c811def53 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java @@ -77,7 +77,7 @@ public Map get(@PathVariable("tenant") String tenantId, map.put("name", column.getName()); map.put("table", tableName); map.put("schema", schemaName); - map.put("data-context", dataSourceName); + map.put("datasource", dataSourceName); map.put("tenant", tenantName); map.put("metadata", metadata); diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java index 9c678fff1..524cc0996 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java @@ -82,7 +82,7 @@ public Map get(@PathVariable("tenant") String tenantId, map.put("name", dataSourceName); map.put("tenant", tenantName); map.put("updateable", dataContext instanceof UpdateableDataContext); - map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName)); + map.put("query_uri", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName)); map.put("schemas", schemaLinks); return map; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java index 6208d497f..993c1d31f 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java @@ -72,7 +72,7 @@ public Map get(@PathVariable("tenant") String tenantId, final Map map = new LinkedHashMap<>(); map.put("type", "schema"); map.put("name", schemaName); - map.put("data-context", dataSourceName); + map.put("datasource", dataSourceName); map.put("tenant", tenantName); map.put("tables", tableLinks); return map; diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java index 4157dacf9..c91428986 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java @@ -77,7 +77,7 @@ public Map get(@PathVariable("tenant") String tenantId, map.put("type", "table"); map.put("name", tableName); map.put("schema", schemaName); - map.put("data-context", dataSourceName); + map.put("datasource", dataSourceName); map.put("tenant", tenantName); map.put("columns", columnsLinks); return map; diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java index c48d74f81..bae992368 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java @@ -19,13 +19,16 @@ package org.apache.metamodel.service.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.InsertInto; +import org.apache.metamodel.insert.RowInsertionBuilder; import org.apache.metamodel.query.Query; import org.apache.metamodel.schema.Table; import org.apache.metamodel.service.app.DataContextTraverser; @@ -75,7 +78,7 @@ public Map get(@PathVariable("tenant") String tenantId, @ResponseBody public Map post(@PathVariable("tenant") String tenantId, @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, - @PathVariable("table") String tableId, @RequestBody Map inputMap) { + @PathVariable("table") String tableId, @RequestBody final List> inputRecords) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName); @@ -84,13 +87,18 @@ public Map post(@PathVariable("tenant") String tenantId, final Table table = traverser.getTable(schemaId, tableId); - final InsertInto insert = new InsertInto(table); - for (Entry entry : inputMap.entrySet()) { - insert.value(entry.getKey(), entry.getValue()); - } - - final UpdateableDataContext updateableDataContext = (UpdateableDataContext) dataContext; - final UpdateSummary result = updateableDataContext.executeUpdate(insert); + final UpdateSummary result = dataContext.executeUpdate(new UpdateScript() { + @Override + public void run(UpdateCallback callback) { + for (Map inputMap : inputRecords) { + final RowInsertionBuilder insert = callback.insertInto(table); + for (Entry entry : inputMap.entrySet()) { + insert.value(entry.getKey(), entry.getValue()); + } + insert.execute(); + } + } + }); final Map response = new LinkedHashMap<>(); response.put("status", "ok"); diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java index 793f88c80..570615351 100644 --- a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java @@ -158,7 +158,7 @@ public void testScenario() throws Exception { // insert into table (x2) { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post( - "/tenant1/mydata/s/mydata/t/hello_world/d").content("{'greeting':'Howdy','who':'MetaModel'}" + "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Howdy','who':'MetaModel'}]" .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( MockMvcResultMatchers.status().isOk()).andReturn(); @@ -168,7 +168,7 @@ public void testScenario() throws Exception { } { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post( - "/tenant1/mydata/s/mydata/t/hello_world/d").content("{'greeting':'Hi','who':'Apache'}" + "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Hi','who':'Apache'}]" .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( MockMvcResultMatchers.status().isOk()).andReturn(); diff --git a/service-webapp/swagger.yaml b/service-webapp/swagger.yaml new file mode 100644 index 000000000..1edd47a09 --- /dev/null +++ b/service-webapp/swagger.yaml @@ -0,0 +1,503 @@ +swagger: '2.0' +info: + title: Apache MetaModel RESTful API + description: Delivers 'MetaModel-as-a-Service' for unified data federation. + version: "5.0.0" +produces: + - application/json +paths: + /: + get: + summary: Hello MetaModel + 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) + 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" + /{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 From 3ac879eac2057d295c711f51d8dd70c9341faeb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Mon, 13 Jun 2016 22:39:17 -0700 Subject: [PATCH 12/19] Fixed swagger file header --- service-webapp/swagger.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/service-webapp/swagger.yaml b/service-webapp/swagger.yaml index 1edd47a09..c52cc2bc2 100644 --- a/service-webapp/swagger.yaml +++ b/service-webapp/swagger.yaml @@ -1,3 +1,19 @@ +# 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 RESTful API From 11d9a9e8ac10f0f1d5da08a091d3b03a4f0a3256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Sun, 10 Jul 2016 22:11:22 -0700 Subject: [PATCH 13/19] Made the new REST API use the METAMODEL-1099 based factories --- .../factory/DataContextPropertiesImpl.java | 3 + .../metamodel/pojo/PojoDataContext.java | 4 +- .../pojo/PojoDataContextFactory.java | 71 ++++++++++++ ...pache.metamodel.factory.DataContextFactory | 1 + .../app/CachedDataSourceRegistryWrapper.java | 109 ++++++++++++++++++ .../service/app/DataContextSupplier.java | 59 ++-------- .../service/app/DataSourceDefinition.java | 8 +- .../service/app/DataSourceRegistry.java | 3 +- .../app/InMemoryDataSourceRegistry.java | 5 +- .../service/app/InMemoryTenantContext.java | 2 +- .../controllers/DataSourceController.java | 17 ++- .../model/RestDataSourceDefinition.java | 27 +++-- .../TenantInteractionScenarioTest.java | 2 +- 13 files changed, 237 insertions(+), 74 deletions(-) create mode 100644 pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java create mode 100644 pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory create mode 100644 service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java diff --git a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java index e66a811a2..13ce9c195 100644 --- a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java +++ b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java @@ -288,6 +288,9 @@ public String getDatabaseName() { @Override public SimpleTableDef[] getTableDefs() { final Object obj = get(PROPERTY_TABLE_DEFS); + if (obj == null) { + return null; + } if (obj instanceof SimpleTableDef[]) { return (SimpleTableDef[]) obj; } diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java index 9369e9603..340228fa0 100644 --- a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java +++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java @@ -49,6 +49,8 @@ public class PojoDataContext extends QueryPostprocessDataContext implements UpdateableDataContext, Serializable { private static final long serialVersionUID = 1L; + + public static final String DEFAULT_SCHEMA_NAME = "Schema"; private final Map> _tables; private final String _schemaName; @@ -68,7 +70,7 @@ public PojoDataContext() { * @param tables */ public PojoDataContext(List> tables) { - this("Schema", tables); + this(DEFAULT_SCHEMA_NAME, tables); } /** diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java new file mode 100644 index 000000000..35842bf98 --- /dev/null +++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java @@ -0,0 +1,71 @@ +/** + * 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.pojo; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.metamodel.ConnectionException; +import org.apache.metamodel.DataContext; +import org.apache.metamodel.factory.DataContextFactory; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.factory.ResourceFactoryRegistry; +import org.apache.metamodel.factory.UnsupportedDataContextPropertiesException; +import org.apache.metamodel.util.SimpleTableDef; + +public class PojoDataContextFactory implements DataContextFactory { + + public static final String PROPERTY_TYPE = "pojo"; + + @Override + public boolean accepts(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) { + return PROPERTY_TYPE.equals(properties.getDataContextType()); + } + + @Override + public DataContext create(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) + throws UnsupportedDataContextPropertiesException, ConnectionException { + + assert accepts(properties, resourceFactoryRegistry); + + final String schemaName; + if (properties.getDatabaseName() != null) { + schemaName = properties.getDatabaseName(); + } else { + schemaName = "Schema"; + } + + final List> tableDataProviders; + + final SimpleTableDef[] tableDefs = properties.getTableDefs(); + if (tableDefs == null) { + tableDataProviders = new ArrayList<>(); + } else { + tableDataProviders = new ArrayList<>(tableDefs.length); + for (int i = 0; i < tableDefs.length; i++) { + final TableDataProvider tableDataProvider = new ArrayTableDataProvider(tableDefs[i], + new ArrayList()); + tableDataProviders.add(tableDataProvider); + } + } + + return new PojoDataContext(schemaName, tableDataProviders); + } + +} diff --git a/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory new file mode 100644 index 000000000..76f808d99 --- /dev/null +++ b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory @@ -0,0 +1 @@ +org.apache.metamodel.pojo.PojoDataContextFactory \ No newline at end of file diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java new file mode 100644 index 000000000..fffe83d1a --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java @@ -0,0 +1,109 @@ +/** + * 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.service.app; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.MetaModelException; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; +import org.apache.metamodel.util.FileHelper; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; + +/** + * A wrapper that adds a cache around a {@link DataSourceRegistry} in order to + * prevent re-connecting all the time to the same data source. + */ +public class CachedDataSourceRegistryWrapper implements DataSourceRegistry { + + /** + * The default timeout (in seconds) before the cache evicts and closes the + * created {@link DataContext}s. + */ + public static final int DEFAULT_TIMEOUT_SECONDS = 60; + + private final DataSourceRegistry delegate; + private final LoadingCache loadingCache; + + public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate) { + this(delegate, DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate, long cacheTimeout, TimeUnit cacheTimeoutUnit) { + this.delegate = delegate; + this.loadingCache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, cacheTimeoutUnit).removalListener( + createRemovalListener()).build(createCacheLoader()); + } + + private RemovalListener createRemovalListener() { + return new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + final DataContext dataContext = notification.getValue(); + // some DataContexts are closeable - attempt closing it here + FileHelper.safeClose(dataContext); + } + }; + } + + private CacheLoader createCacheLoader() { + return new CacheLoader() { + @Override + public DataContext load(String key) throws Exception { + return delegate.openDataContext(key); + } + }; + } + + @Override + public List getDataSourceNames() { + return delegate.getDataSourceNames(); + } + + @Override + public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties) + throws DataSourceAlreadyExistException { + loadingCache.invalidate(dataContextName); + return delegate.registerDataSource(dataContextName, dataContextProperties); + } + + @Override + public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException { + try { + return loadingCache.get(dataSourceName); + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw new MetaModelException("Unexpected error happened while getting DataContext '" + dataSourceName + + "' from cache", e); + } + } + +} diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java index a0fb6ecc6..2f42c6fae 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java @@ -18,68 +18,31 @@ */ package org.apache.metamodel.service.app; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.apache.metamodel.DataContext; -import org.apache.metamodel.pojo.ArrayTableDataProvider; -import org.apache.metamodel.pojo.PojoDataContext; -import org.apache.metamodel.pojo.TableDataProvider; -import org.apache.metamodel.util.SimpleTableDef; -import org.apache.metamodel.util.SimpleTableDefParser; +import org.apache.metamodel.factory.DataContextFactoryRegistryImpl; +import org.apache.metamodel.factory.DataContextProperties; public class DataContextSupplier implements Supplier { private final String dataSourceName; - private final DataSourceDefinition dataSourceDefinition; - private final DataContext eagerLoadedDataContext; + private final DataContextProperties dataContextProperties; - public DataContextSupplier(String dataSourceName, DataSourceDefinition dataSourceDefinition) { + public DataContextSupplier(String dataSourceName, DataContextProperties dataContextProperties) { this.dataSourceName = dataSourceName; - this.dataSourceDefinition = dataSourceDefinition; - this.eagerLoadedDataContext = createDataContext(true); + this.dataContextProperties = dataContextProperties; } @Override public DataContext get() { - if (eagerLoadedDataContext != null) { - return eagerLoadedDataContext; - } - return createDataContext(false); + final DataContext dataContext = DataContextFactoryRegistryImpl.getDefaultInstance().createDataContext( + dataContextProperties); + return dataContext; } - private DataContext createDataContext(boolean eager) { - final String type = dataSourceDefinition.getType(); - switch (type.toLowerCase()) { - case "pojo": - final List> tableDataProviders; - - final Object tableDefinitions = dataSourceDefinition.getTableDefinitions(); - if (tableDefinitions == null) { - tableDataProviders = new ArrayList<>(0); - } else if (tableDefinitions instanceof String) { - final SimpleTableDef[] tableDefs = SimpleTableDefParser.parseTableDefs((String) tableDefinitions); - tableDataProviders = Arrays.stream(tableDefs).map((tableDef) -> { - return new ArrayTableDataProvider(tableDef, new ArrayList()); - }).collect(Collectors.toList()); - } else { - throw new UnsupportedOperationException("Unsupported table definition type: " + tableDefinitions); - } - - final String schemaName = dataSourceDefinition.getSchemaName() == null ? dataSourceName - : dataSourceDefinition.getSchemaName(); - - return new PojoDataContext(schemaName, tableDataProviders); - } - - if (eager) { - return null; - } else { - throw new UnsupportedOperationException("Unsupported data source type: " + type); - } + @Override + public String toString() { + return "DataContextSupplier[" + dataSourceName + "]"; } - } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java index 11140a73e..2aaa8e5e0 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java @@ -18,13 +18,11 @@ */ package org.apache.metamodel.service.app; +import java.util.Map; + public interface DataSourceDefinition { public String getType(); - public Object getTableDefinitions(); - - public String getSchemaName(); - - // TODO + public Map getProperties(); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java index 3a9ba4309..e0c869716 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java @@ -22,6 +22,7 @@ import org.apache.metamodel.DataContext; import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.factory.DataContextProperties; import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException; import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; @@ -33,7 +34,7 @@ public interface DataSourceRegistry { public List getDataSourceNames(); - public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws DataSourceAlreadyExistException; + public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties) throws DataSourceAlreadyExistException; public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException; diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java index b99c6e770..386232a61 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.apache.metamodel.DataContext; +import org.apache.metamodel.factory.DataContextProperties; import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; @@ -37,13 +38,13 @@ public InMemoryDataSourceRegistry() { } @Override - public String registerDataSource(final String name, final DataSourceDefinition dataSourceDef) + public String registerDataSource(final String name, final DataContextProperties dataContextProperties) throws DataSourceAlreadyExistException { if (dataSources.containsKey(name)) { throw new DataSourceAlreadyExistException(name); } - dataSources.put(name, new DataContextSupplier(name, dataSourceDef)); + dataSources.put(name, new DataContextSupplier(name, dataContextProperties)); return name; } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java index 04fb708a3..022ab2821 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java @@ -25,7 +25,7 @@ public class InMemoryTenantContext implements TenantContext { public InMemoryTenantContext(String tenantIdentifier) { this.tenantIdentifier = tenantIdentifier; - this.dataContextRegistry = new InMemoryDataSourceRegistry(); + this.dataContextRegistry = new CachedDataSourceRegistryWrapper(new InMemoryDataSourceRegistry()); } @Override diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java index 9c678fff1..6ac0c43a2 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java @@ -19,6 +19,7 @@ package org.apache.metamodel.service.controllers; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -29,6 +30,8 @@ import org.apache.metamodel.DataContext; import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.factory.DataContextPropertiesImpl; import org.apache.metamodel.service.app.TenantContext; import org.apache.metamodel.service.app.TenantRegistry; import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition; @@ -58,8 +61,20 @@ public DataSourceController(TenantRegistry tenantRegistry) { public Map put(@PathVariable("tenant") String tenantId, @PathVariable("datasource") String dataSourceId, @Valid @RequestBody RestDataSourceDefinition dataContextDefinition) { + + final Map map = new HashMap<>(); + map.putAll(dataContextDefinition.getProperties()); + map.put(DataContextPropertiesImpl.PROPERTY_DATA_CONTEXT_TYPE, dataContextDefinition.getType()); + + if (!map.containsKey(DataContextPropertiesImpl.PROPERTY_DATABASE)) { + // add the data source ID as database name if it is not already set. + map.put(DataContextPropertiesImpl.PROPERTY_DATABASE, dataSourceId); + } + + final DataContextProperties properties = new DataContextPropertiesImpl(map); + final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataSourceRegistry() - .registerDataSource(dataSourceId, dataContextDefinition); + .registerDataSource(dataSourceId, properties); return get(tenantId, dataContextIdentifier); } diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java index 48c592672..b6fdb2863 100644 --- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java @@ -18,39 +18,38 @@ */ package org.apache.metamodel.service.controllers.model; +import java.util.HashMap; +import java.util.Map; + import javax.validation.constraints.NotNull; import org.apache.metamodel.service.app.DataSourceDefinition; -import com.fasterxml.jackson.annotation.JsonInclude; +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 properties = new HashMap<>(); + @JsonProperty(value = "type", required = true) @NotNull private String type; - @JsonProperty(value = "table-definitions", required = false) - @JsonInclude(JsonInclude.Include.NON_NULL) - private Object tableDefinitions; - - @JsonProperty(value = "schema-name", required = false) - @JsonInclude(JsonInclude.Include.NON_NULL) - private String schemaName; - @Override public String getType() { return type; } + @JsonAnyGetter @Override - public Object getTableDefinitions() { - return tableDefinitions; + public Map getProperties() { + return properties; } - @Override - public String getSchemaName() { - return schemaName; + @JsonAnySetter + public void set(String name, Object value) { + properties.put(name, value); } } diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java index 793f88c80..01bc7f50b 100644 --- a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java +++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java @@ -72,7 +72,7 @@ public void testScenario() throws Exception { // create datasource { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content( - "{'type':'pojo','table-definitions':'hello_world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}" + "{'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(); From 35c9861eb042f69435b6478ac53b0d0452b9e727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Sun, 10 Jul 2016 22:23:01 -0700 Subject: [PATCH 14/19] Updated swagger file with info about PUT datasource endpoint --- service-webapp/swagger.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/service-webapp/swagger.yaml b/service-webapp/swagger.yaml index c52cc2bc2..2db13b215 100644 --- a/service-webapp/swagger.yaml +++ b/service-webapp/swagger.yaml @@ -180,6 +180,17 @@ paths: 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 From 0dd9bd8659707da2d757843a58bf5b9e66856604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20S=C3=B8rensen?= Date: Tue, 26 Jul 2016 22:09:59 -0700 Subject: [PATCH 15/19] Added swagger-ui (2.1.5) to static webapp resources --- .../webapp/WEB-INF/dispatcher-servlet.xml | 5 +- .../src/main/webapp/swagger-ui/css/print.css | 1362 + .../src/main/webapp/swagger-ui/css/reset.css | 125 + .../src/main/webapp/swagger-ui/css/screen.css | 1489 + .../src/main/webapp/swagger-ui/css/style.css | 250 + .../main/webapp/swagger-ui/css/typography.css | 14 + .../swagger-ui/fonts/DroidSans-Bold.ttf | Bin 0 -> 42480 bytes .../webapp/swagger-ui/fonts/DroidSans.ttf | Bin 0 -> 41028 bytes .../webapp/swagger-ui/images/collapse.gif | Bin 0 -> 69 bytes .../main/webapp/swagger-ui/images/expand.gif | Bin 0 -> 73 bytes .../swagger-ui/images/explorer_icons.png | Bin 0 -> 5763 bytes .../swagger-ui/images/favicon-16x16.png | Bin 0 -> 645 bytes .../swagger-ui/images/favicon-32x32.png | Bin 0 -> 1654 bytes .../main/webapp/swagger-ui/images/favicon.ico | Bin 0 -> 5430 bytes .../webapp/swagger-ui/images/logo_small.png | Bin 0 -> 770 bytes .../swagger-ui/images/pet_store_api.png | Bin 0 -> 824 bytes .../webapp/swagger-ui/images/throbber.gif | Bin 0 -> 9257 bytes .../webapp/swagger-ui/images/wordnik_api.png | Bin 0 -> 980 bytes .../src/main/webapp/swagger-ui/index.html | 106 + .../src/main/webapp/swagger-ui/lang/en.js | 56 + .../src/main/webapp/swagger-ui/lang/es.js | 53 + .../src/main/webapp/swagger-ui/lang/fr.js | 54 + .../src/main/webapp/swagger-ui/lang/geo.js | 56 + .../src/main/webapp/swagger-ui/lang/it.js | 52 + .../src/main/webapp/swagger-ui/lang/ja.js | 53 + .../src/main/webapp/swagger-ui/lang/ko-kr.js | 53 + .../src/main/webapp/swagger-ui/lang/pl.js | 53 + .../src/main/webapp/swagger-ui/lang/pt.js | 53 + .../src/main/webapp/swagger-ui/lang/ru.js | 56 + .../src/main/webapp/swagger-ui/lang/tr.js | 53 + .../main/webapp/swagger-ui/lang/translator.js | 39 + .../src/main/webapp/swagger-ui/lang/zh-cn.js | 53 + .../webapp/swagger-ui/lib/backbone-min.js | 15 + .../main/webapp/swagger-ui/lib/es5-shim.js | 2065 ++ .../webapp/swagger-ui/lib/handlebars-2.0.0.js | 28 + .../swagger-ui/lib/highlight.9.1.0.pack.js | 2 + .../lib/highlight.9.1.0.pack_extended.js | 34 + .../webapp/swagger-ui/lib/jquery-1.8.0.min.js | 2 + .../swagger-ui/lib/jquery.ba-bbq.min.js | 18 + .../swagger-ui/lib/jquery.slideto.min.js | 1 + .../swagger-ui/lib/jquery.wiggle.min.js | 8 + .../main/webapp/swagger-ui/lib/js-yaml.min.js | 3 + .../webapp/swagger-ui/lib/jsoneditor.min.js | 11 + .../main/webapp/swagger-ui/lib/lodash.min.js | 102 + .../src/main/webapp/swagger-ui/lib/marked.js | 1272 + .../swagger-ui/lib/object-assign-pollyfill.js | 23 + .../webapp/swagger-ui/lib/swagger-oauth.js | 347 + .../src/main/webapp/swagger-ui/o2c.html | 20 + .../src/main/webapp/swagger-ui/swagger-ui.js | 24691 ++++++++++++++++ .../main/webapp/swagger-ui/swagger-ui.min.js | 10 + 50 files changed, 32686 insertions(+), 1 deletion(-) create mode 100755 service-webapp/src/main/webapp/swagger-ui/css/print.css create mode 100755 service-webapp/src/main/webapp/swagger-ui/css/reset.css create mode 100755 service-webapp/src/main/webapp/swagger-ui/css/screen.css create mode 100755 service-webapp/src/main/webapp/swagger-ui/css/style.css create mode 100755 service-webapp/src/main/webapp/swagger-ui/css/typography.css create mode 100755 service-webapp/src/main/webapp/swagger-ui/fonts/DroidSans-Bold.ttf create mode 100755 service-webapp/src/main/webapp/swagger-ui/fonts/DroidSans.ttf create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/collapse.gif create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/expand.gif create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/explorer_icons.png create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/favicon-16x16.png create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/favicon-32x32.png create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/favicon.ico create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/logo_small.png create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/pet_store_api.png create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/throbber.gif create mode 100755 service-webapp/src/main/webapp/swagger-ui/images/wordnik_api.png create mode 100755 service-webapp/src/main/webapp/swagger-ui/index.html create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/en.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/es.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/fr.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/geo.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/it.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/ja.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/ko-kr.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/pl.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/pt.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/ru.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/tr.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/translator.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lang/zh-cn.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/backbone-min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/es5-shim.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/handlebars-2.0.0.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/highlight.9.1.0.pack.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/highlight.9.1.0.pack_extended.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/jquery-1.8.0.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/jquery.ba-bbq.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/jquery.slideto.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/jquery.wiggle.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/js-yaml.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/jsoneditor.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/lodash.min.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/marked.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/object-assign-pollyfill.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/lib/swagger-oauth.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/o2c.html create mode 100755 service-webapp/src/main/webapp/swagger-ui/swagger-ui.js create mode 100755 service-webapp/src/main/webapp/swagger-ui/swagger-ui.min.js diff --git a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml index 41cf38c1f..8100579c5 100644 --- a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml +++ b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml @@ -28,13 +28,16 @@ under the License. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - + + + + diff --git a/service-webapp/src/main/webapp/swagger-ui/css/print.css b/service-webapp/src/main/webapp/swagger-ui/css/print.css new file mode 100755 index 000000000..d4c1e7045 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/css/print.css @@ -0,0 +1,1362 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #88F; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section .hljs, +.swagger-section .hljs-subst { + color: #444; +} +.swagger-section .hljs-keyword, +.swagger-section .hljs-attribute, +.swagger-section .hljs-selector-tag, +.swagger-section .hljs-meta-keyword, +.swagger-section .hljs-doctag, +.swagger-section .hljs-name { + font-weight: bold; +} +.swagger-section .hljs-built_in, +.swagger-section .hljs-literal, +.swagger-section .hljs-bullet, +.swagger-section .hljs-code, +.swagger-section .hljs-addition { + color: #1F811F; +} +.swagger-section .hljs-regexp, +.swagger-section .hljs-symbol, +.swagger-section .hljs-variable, +.swagger-section .hljs-template-variable, +.swagger-section .hljs-link, +.swagger-section .hljs-selector-attr, +.swagger-section .hljs-selector-pseudo { + color: #BC6060; +} +.swagger-section .hljs-type, +.swagger-section .hljs-string, +.swagger-section .hljs-number, +.swagger-section .hljs-selector-id, +.swagger-section .hljs-selector-class, +.swagger-section .hljs-quote, +.swagger-section .hljs-template-tag, +.swagger-section .hljs-deletion { + color: #880000; +} +.swagger-section .hljs-title, +.swagger-section .hljs-section { + color: #880000; + font-weight: bold; +} +.swagger-section .hljs-comment { + color: #888888; +} +.swagger-section .hljs-meta { + color: #2B6EA1; +} +.swagger-section .hljs-emphasis { + font-style: italic; +} +.swagger-section .hljs-strong { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + min-width: 760px; + max-width: 960px; + margin-left: auto; + margin-right: auto; + /* JSONEditor specific styling */ +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + min-width: 230px; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .editor_holder { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap .editor_holder label { + font-weight: normal!important; + /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */ +} +.swagger-section .swagger-ui-wrap .editor_holder label.required { + font-weight: bold!important; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; + overflow: auto; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated { + text-decoration: line-through; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; + display: inline-block; +} +.swagger-section .authorize-wrapper { + margin: 15px 0 10px; +} +.swagger-section .authorize-wrapper_operation { + float: right; +} +.swagger-section .authorize__btn:hover { + text-decoration: underline; + cursor: pointer; +} +.swagger-section .authorize__btn_operation:hover .authorize-scopes { + display: block; +} +.swagger-section .authorize-scopes { + position: absolute; + margin-top: 20px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .authorize-scopes .authorize__scope { + text-decoration: none; +} +.swagger-section .authorize__btn_operation { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .authorize__btn_operation_login { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .authorize__btn_operation_logout { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section #auth_container { + color: #fff; + display: inline-block; + border: none; + padding: 5px; + width: 87px; + height: 13px; +} +.swagger-section #auth_container .authorize__btn { + color: #fff; +} +.swagger-section .auth_container { + padding: 0 0 10px; + margin-bottom: 5px; + border-bottom: solid 1px #CCC; + font-size: 0.9em; +} +.swagger-section .auth_container .auth__title { + color: #547f00; + font-size: 1.2em; +} +.swagger-section .auth_container .basic_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .auth_container .auth__description { + color: #999999; + margin-bottom: 5px; +} +.swagger-section .auth_container .auth__button { + margin-top: 10px; + height: 30px; +} +.swagger-section .auth_container .key_auth__field { + margin: 5px 0; +} +.swagger-section .auth_container .key_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .api-popup-dialog { + position: absolute; + display: none; +} +.swagger-section .api-popup-dialog-wrapper { + z-index: 1000; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 13px; + color: #777; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +.swagger-section .api-popup-dialog-shadow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + background-color: gray; + z-index: 900; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog .api-popup-content { + max-height: 500px; + overflow-y: auto; +} +.swagger-section .api-popup-dialog .api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog .api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +#header { + display: none; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + max-height: none; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 100px; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 100px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + display: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints { + display: block !important; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + display: block !important; +} diff --git a/service-webapp/src/main/webapp/swagger-ui/css/reset.css b/service-webapp/src/main/webapp/swagger-ui/css/reset.css new file mode 100755 index 000000000..b2b078943 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/css/reset.css @@ -0,0 +1,125 @@ +/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/service-webapp/src/main/webapp/swagger-ui/css/screen.css b/service-webapp/src/main/webapp/swagger-ui/css/screen.css new file mode 100755 index 000000000..9d680e2d9 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/css/screen.css @@ -0,0 +1,1489 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #88F; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section .hljs, +.swagger-section .hljs-subst { + color: #444; +} +.swagger-section .hljs-keyword, +.swagger-section .hljs-attribute, +.swagger-section .hljs-selector-tag, +.swagger-section .hljs-meta-keyword, +.swagger-section .hljs-doctag, +.swagger-section .hljs-name { + font-weight: bold; +} +.swagger-section .hljs-built_in, +.swagger-section .hljs-literal, +.swagger-section .hljs-bullet, +.swagger-section .hljs-code, +.swagger-section .hljs-addition { + color: #1F811F; +} +.swagger-section .hljs-regexp, +.swagger-section .hljs-symbol, +.swagger-section .hljs-variable, +.swagger-section .hljs-template-variable, +.swagger-section .hljs-link, +.swagger-section .hljs-selector-attr, +.swagger-section .hljs-selector-pseudo { + color: #BC6060; +} +.swagger-section .hljs-type, +.swagger-section .hljs-string, +.swagger-section .hljs-number, +.swagger-section .hljs-selector-id, +.swagger-section .hljs-selector-class, +.swagger-section .hljs-quote, +.swagger-section .hljs-template-tag, +.swagger-section .hljs-deletion { + color: #880000; +} +.swagger-section .hljs-title, +.swagger-section .hljs-section { + color: #880000; + font-weight: bold; +} +.swagger-section .hljs-comment { + color: #888888; +} +.swagger-section .hljs-meta { + color: #2B6EA1; +} +.swagger-section .hljs-emphasis { + font-style: italic; +} +.swagger-section .hljs-strong { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + min-width: 760px; + max-width: 960px; + margin-left: auto; + margin-right: auto; + /* JSONEditor specific styling */ +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + min-width: 230px; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .editor_holder { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap .editor_holder label { + font-weight: normal!important; + /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */ +} +.swagger-section .swagger-ui-wrap .editor_holder label.required { + font-weight: bold!important; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; + overflow: auto; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated { + text-decoration: line-through; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; + display: inline-block; +} +.swagger-section .authorize-wrapper { + margin: 15px 0 10px; +} +.swagger-section .authorize-wrapper_operation { + float: right; +} +.swagger-section .authorize__btn:hover { + text-decoration: underline; + cursor: pointer; +} +.swagger-section .authorize__btn_operation:hover .authorize-scopes { + display: block; +} +.swagger-section .authorize-scopes { + position: absolute; + margin-top: 20px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .authorize-scopes .authorize__scope { + text-decoration: none; +} +.swagger-section .authorize__btn_operation { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .authorize__btn_operation_login { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .authorize__btn_operation_logout { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section #auth_container { + color: #fff; + display: inline-block; + border: none; + padding: 5px; + width: 87px; + height: 13px; +} +.swagger-section #auth_container .authorize__btn { + color: #fff; +} +.swagger-section .auth_container { + padding: 0 0 10px; + margin-bottom: 5px; + border-bottom: solid 1px #CCC; + font-size: 0.9em; +} +.swagger-section .auth_container .auth__title { + color: #547f00; + font-size: 1.2em; +} +.swagger-section .auth_container .basic_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .auth_container .auth__description { + color: #999999; + margin-bottom: 5px; +} +.swagger-section .auth_container .auth__button { + margin-top: 10px; + height: 30px; +} +.swagger-section .auth_container .key_auth__field { + margin: 5px 0; +} +.swagger-section .auth_container .key_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .api-popup-dialog { + position: absolute; + display: none; +} +.swagger-section .api-popup-dialog-wrapper { + z-index: 1000; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 13px; + color: #777; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +.swagger-section .api-popup-dialog-shadow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + background-color: gray; + z-index: 900; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog .api-popup-content { + max-height: 500px; + overflow-y: auto; +} +.swagger-section .api-popup-dialog .api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog .api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +.swagger-section .access { + float: right; +} +.swagger-section .auth { + float: right; +} +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .api-ic .api_information_panel { + position: relative; + margin-top: 20px; + margin-left: -5px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .api-ic .api_information_panel p .api-msg-enabled { + color: green; +} +.swagger-section .api-ic .api_information_panel p .api-msg-disabled { + color: red; +} +.swagger-section .api-ic:hover .api_information_panel { + position: absolute; + display: block; +} +.swagger-section .ic-info { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-warning { + background-position: -60px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-error { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-off { + background-position: -90px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section .ic-on { + background-position: -160px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section #header { + background-color: #89bf04; + padding: 9px 14px 19px 14px; + height: 23px; + min-width: 775px; +} +.swagger-section #input_baseUrl { + width: 400px; +} +.swagger-section #api_selector { + display: block; + clear: none; + float: right; +} +.swagger-section #api_selector .input { + display: inline-block; + clear: none; + margin: 0 10px 0 0; +} +.swagger-section #api_selector input { + font-size: 0.9em; + padding: 3px; + margin: 0; +} +.swagger-section #input_apiKey { + width: 200px; +} +.swagger-section #explore, +.swagger-section #auth_container .authorize__btn { + display: block; + text-decoration: none; + font-weight: bold; + padding: 6px 8px; + font-size: 0.9em; + color: white; + background-color: #547f00; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} +.swagger-section #explore:hover, +.swagger-section #auth_container .authorize__btn:hover { + background-color: #547f00; +} +.swagger-section #header #logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + color: white; +} +.swagger-section #header #logo .logo__img { + display: block; + float: left; + margin-top: 2px; +} +.swagger-section #header #logo .logo__title { + display: inline-block; + padding: 5px 0 0 10px; +} +.swagger-section #content_message { + margin: 10px 15px; + font-style: italic; + color: #999999; +} +.swagger-section #message-bar { + min-height: 30px; + text-align: center; + padding-top: 10px; +} +.swagger-section .swagger-collapse:before { + content: "-"; +} +.swagger-section .swagger-expand:before { + content: "+"; +} +.swagger-section .error { + outline-color: #cc0000; + background-color: #f2dede; +} diff --git a/service-webapp/src/main/webapp/swagger-ui/css/style.css b/service-webapp/src/main/webapp/swagger-ui/css/style.css new file mode 100755 index 000000000..fc21a31db --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/css/style.css @@ -0,0 +1,250 @@ +.swagger-section #header a#logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/logo.png) no-repeat left center; + padding: 20px 0 20px 40px; +} +#text-head { + font-size: 80px; + font-family: 'Roboto', sans-serif; + color: #ffffff; + float: right; + margin-right: 20%; +} +.navbar-fixed-top .navbar-nav { + height: auto; +} +.navbar-fixed-top .navbar-brand { + height: auto; +} +.navbar-header { + height: auto; +} +.navbar-inverse { + background-color: #000; + border-color: #000; +} +#navbar-brand { + margin-left: 20%; +} +.navtext { + font-size: 10px; +} +.h1, +h1 { + font-size: 60px; +} +.navbar-default .navbar-header .navbar-brand { + color: #a2dfee; +} +/* tag titles */ +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #393939; + font-family: 'Arvo', serif; + font-size: 1.5em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #525252; + padding-left: 0px; + display: block; + clear: none; + float: left; + font-family: 'Arvo', serif; + font-weight: bold; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #0A0A0A; +} +.container1 { + width: 1500px; + margin: auto; + margin-top: 0; + background-image: url('../images/shield.png'); + background-repeat: no-repeat; + background-position: -40px -20px; + margin-bottom: 210px; +} +.container-inner { + width: 1200px; + margin: auto; + background-color: rgba(223, 227, 228, 0.75); + padding-bottom: 40px; + padding-top: 40px; + border-radius: 15px; +} +.header-content { + padding: 0; + width: 1000px; +} +.title1 { + font-size: 80px; + font-family: 'Vollkorn', serif; + color: #404040; + text-align: center; + padding-top: 40px; + padding-bottom: 100px; +} +#icon { + margin-top: -18px; +} +.subtext { + font-size: 25px; + font-style: italic; + color: #08b; + text-align: right; + padding-right: 250px; +} +.bg-primary { + background-color: #00468b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:focus { + color: #08b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:hover { + color: #08b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:focus:hover { + color: #08b; +} +.text-faded { + font-size: 25px; + font-family: 'Vollkorn', serif; +} +.section-heading { + font-family: 'Vollkorn', serif; + font-size: 45px; + padding-bottom: 10px; +} +hr { + border-color: #00468b; + padding-bottom: 10px; +} +.description { + margin-top: 20px; + padding-bottom: 200px; +} +.description li { + font-family: 'Vollkorn', serif; + font-size: 25px; + color: #525252; + margin-left: 28%; + padding-top: 5px; +} +.gap { + margin-top: 200px; +} +.troubleshootingtext { + color: rgba(255, 255, 255, 0.7); + padding-left: 30%; +} +.troubleshootingtext li { + list-style-type: circle; + font-size: 25px; + padding-bottom: 5px; +} +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} +.block.response_body.json:hover { + cursor: pointer; +} +.backdrop { + color: blue; +} +#myModal { + height: 100%; +} +.modal-backdrop { + bottom: 0; + position: fixed; +} +.curl { + padding: 10px; + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; + max-height: 400px; + margin-top: 5px; + overflow-y: auto; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + border-radius: 4px; +} +.curl_title { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; + font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} +.footer { + display: none; +} +.swagger-section .swagger-ui-wrap h2 { + padding: 0; +} +h2 { + margin: 0; + margin-bottom: 5px; +} +.markdown p { + font-size: 15px; + font-family: 'Arvo', serif; +} +.swagger-section .swagger-ui-wrap .code { + font-size: 15px; + font-family: 'Arvo', serif; +} +.swagger-section .swagger-ui-wrap b { + font-family: 'Arvo', serif; +} +#signin:hover { + cursor: pointer; +} +.dropdown-menu { + padding: 15px; +} +.navbar-right .dropdown-menu { + left: 0; + right: auto; +} +#signinbutton { + width: 100%; + height: 32px; + font-size: 13px; + font-weight: bold; + color: #08b; +} +.navbar-default .nav > li .details { + color: #000000; + text-transform: none; + font-size: 15px; + font-weight: normal; + font-family: 'Open Sans', sans-serif; + font-style: italic; + line-height: 20px; + top: -2px; +} +.navbar-default .nav > li .details:hover { + color: black; +} +#signout { + width: 100%; + height: 32px; + font-size: 13px; + font-weight: bold; + color: #08b; +} diff --git a/service-webapp/src/main/webapp/swagger-ui/css/typography.css b/service-webapp/src/main/webapp/swagger-ui/css/typography.css new file mode 100755 index 000000000..efb785fab --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/css/typography.css @@ -0,0 +1,14 @@ +/* Google Font's Droid Sans */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 400; + src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype'); +} +/* Google Font's Droid Sans Bold */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 700; + src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype'); +} diff --git a/service-webapp/src/main/webapp/swagger-ui/fonts/DroidSans-Bold.ttf b/service-webapp/src/main/webapp/swagger-ui/fonts/DroidSans-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..036c4d135bf954ef3c2b15bc6493f72a01909aee GIT binary patch literal 42480 zcmb@ud3;k<`Y?XZxl8taZ`v$PleA6Sq)E4wwh3L*(v7yz(uFQSDf_;Oh@iLuA}9{7 zBj`AYs2I>eTxP^w^sDIWI1a-&f{qKLDC4NZV4MD)bJGGkzMt>=$1m+o?mhS3bI-G! zXFnHVgpd}#SmcPsrZ%<4LvJH=+aIAdwYIt@24@&JJ_pB++Lp;v9zVM@jL=Rs+;p^d zN_*wPTUTyJ=(q3C^OL9eg9s@E#HmN}W4<#^CpF zIX-~Ca^QT?Txd|-!_9!>K{(EsyJ+PVyI-61B=imUFpUeB%$hMcV^cbe70%;*i)LK0 zlra%K?B4{}I~LDaH2Z=2PXWHonehC>OP8!z`M`^+2?*^t1=oDObouP1vzPs83qp$X z(C172hOWe#gkE$&v;wXZBO;s@?h&m>z;ne2 z&aV|65PBLrDg3-Aw7iGrAP%(yEeKL0Kk9~C-@+cC07eWui#!Ks5qCEif_7W-mw5*b z;b9n^Ow2Q4mJs$7BHu)jqX+s0s1VAZIHd|Xki${U+hGjXiB{mR90>2G=h!~(F#I?K zgwX~VA@l_!CG}_(^BTII^PyaJE}AKlqX_pJY9mf`Jvj`&hfz5@9Yx?=Cuu_wvJ;h) zI=H79DWQ$FPlv4;wlHigY#p#Q!d47h9c&SLJ-rw9JI6QXQDou2Mne=v&V`O}7PN-D z8y#WS!8QoTZ^H3g=m?XBD#_l_FsFs~I&?(TjE?Ztu*uOH_E_?llft=t)Cc!vanGOw z>}jC#eCU@2$RCBR1?7{yXgiEq4!{ zZDY~s1!j?OeJ3MEe%K~ZSO5=uxN@|N;u}9WdJ@{FlW&182hdcYYu?z_4Y;oq@J@06 zvrRMrZ9ofw4wM#S8_*$}(t=4FJqN!-vEw9^qA7(@=c%x(2QFF_@j9L zg-xVH9Re+Y4wM$-8_?j7K#N=9_fFVO0exO-TW|AjiKEQ^hi8X**kX2M>3R^17T>80P zn8P4bVQyam$0;zs86-tlqfwksUL}7ae|6{`DGr+>-BIeO9vy{i9B9wQb2^9FkqYO+ z^xWttqc4u$Ix;fykC8K{o;Vdh_4uhrPwhIjZR}a}dB5<%yda2#Gnh)Iap^REEsT_5 zIbI}|NM&+`Ql-{tb$Wx*WVWPOQ`2mAhcn%k;r4hlv$B0Txq1FTelS!}SX5jRE}c+T zUJ;2-tgHf!QCl~uzM-+Hd2&l@+mxy89n(6yx_hSg_Vo|UKsVgDdF#$Q@B7UI5AOZ# zLk~Z)|Ix=DKM+6o#P6OweB`O;p8fqF(DK3A^WMJV{-q1h>iOuVyAjNmIctOgF1!lu zd*+&1i|N6l^=Rg`*WZEAtFOKJKd&Eq8ohwtMaSQPGi%>?3te~frfplc@3{4rU+qG- z-}~!(UVQ15WeB}|rO-{ZrmM4k>Xf$DmdVXcjScma>S|*()m4=fqmhd8vI(W(lH#Jm z{D40%Hz(8Uac8*Fop!TcqgE*uGO0u?;yIQf80FNsVgru2XCTgcTy=H1^w>268fIM9 zFc5b@Q|zbL#2xg$8F1<+S4QE=p`TqDO-)s8qGaMZ+OtLJX18K}<9!3Sm1Dp%EPX>JZW zD3!sk4EExguB8WYW(5{@NM=pxL4w2z`q((*u9-0yZ)xqUskSXO51aF|^Y&8^_OFynVn0)NGj2xh2lJCk?u40LNQr#5c|aewt57 z;ZnzyKUkfvEn1Bu?1wSJwGJ5cq``TPIOmD;^sdWq04}7rY=JKzg7Syt&si%xz@yPR z!Y=5B_EY1k85sLtJ=Y9RbmZp5>wHPlPwk9Hs{tF)8Dnfwb1>kCJ7x?3iRMvCwsyw- zuBCCkt1`(q&_IV(GjB?#a7(haCa$lFgW))KSKMC%_X4xmY@xhGhd}#swRS#@LZhb+ z7C5X=girzMs)pvcu?qOgQ?sRWa42pcunxjZ8*+47o$+WF5V*_LIlGH88ek~v6g&<- z09Kq-P3>%$;%aE^?gXkjyM#0J1nrJ>*Ziy-SEn`E4X|8X>=rvZiIwSs3)Rrzh{2Al zvJAfCBDWYeH6UDQrL0<6=IF#$G=4P*zUpig7#Ov>5oainb+jX{J2y=yc6?b-Cs`;?b5)iXw_0 z0n1~^5OAKPO2gF7%a1NY7N7|^;k1CcF=WPLzKb}d$c@(u$K$vLp84tNN#my-Tg0x0 zDO)HdTw@&~82O|)qAVOO(E!*m*XewA#b&r1YM9Z&eBN>}8l`i2E}gbpT$2X3xTbWL z30H#zS#Q0PK3j_#aKqHf+#C>Ql?Pq;rq+W|eAATf&ZpI&YHym_d5BBv#ih{9M3v}k@|)-N}sE`=5872qCAd_fD!n|aqOx9?6@ipV#3SgQrGOt zxXe{a+at6+l5FQ`yU0};$3~o+bLAFwjq7`JE&|v>lJm@{z2!T+`(HTlPh5Pi_T02{ zjQ^Y6-;n6pmb34iWddJe_m_oVwtqSMOLp4d@z7~}Wc1tdyqfZSy zweczD$mr20p3>-{uP+WiDN)3Z9)#|{h~EFWR2lQ%8@YG%US|6}-`qp)zRO}iddE9= zkR97BcKgl#nGz~vzE_7(RLPcNR{t+)5U=IJi#Cc2Qeox@B!8)Dj34E8g-86ulz zo#vbNIuBxeOV{<=wxPZw3Pg`rU>|(-!NE#;0?rGqD-gW| z(`)G0N_qzNg&!aNLC^_Y0~gclgx>Iq~p;g>r@Y;UzVf&B) ztwU(^E82YNoBGNBe}0laI?l%>*p5ZGQRw@7^f5Yt-b9Dc^8m*l_#1*h_~0TO!G7$- zda#jyK(`22A3;aZ8u+{HL$5=dX#-q$0i8piK!3mR!6|U=9`qj8T{?;7XdikHZ9`98 z*4BsDV-xxwoyJq~59kl*^-Je|vUd_wfKH;fsbhhYxEcm;{5Z1<8g53P!DwD5_rVi> zM`6Wl0fx`u=r?fo7Wn;t_;828``3?tgT6+8L2tkfFQff{pSzr&;$5>^aVsR?gk8B{^>+F;0*jNd4TC-ZeR{Ge`izJ zO7;eJ5Bo>%fC|u z6w4L=N2ylEl-DZ%qD-i+RDGc?Q(vn-uUV-1SgY4g)jpuRN%xt)M88FUK>vUA-x)Fu zQNvQhXU2NtoyK9)V$*ZxyUl;MWLU1X{MB+HWogP0D{Gx={hw5G>Yb_Q(n``Eux+&4 z?0fCQV6R{4IOUAN--GGe^gCTet|wi8$#^m&;eODQ?YYWxr)Q71(L2+--}_Z&Eb~uU zeAd*g9a%4D>$6+35BP{L)7R!(>H9|x$w|ox=G5m5=4{OQI+x_8}x16$^ zvoLmx-x9HGu(2xqBlrq}=Ya}!uo$reEMc+a9vxv& z#6R*j_V+Fe^}^?Dkbc7_KY(3~6BksFP|!vUdL?nC=MlW;yAbY9JYWq(-L6O=H8l`% zxuXFq{*-z5!U_MxOszI^qCYp9t8=?QzVk~OE9_0WKve;P)>zz|@=_h!A4MSc%o+~JXPL`^r^H>gQA!99tr z$d2BMZe*C06tmHmCSrM$iMJrkvfRP%%(%sj&1R>;o+@S~dc9Vql(D*l(g8JAtL1XZ zk5W7!T`DEgR9vUPiUV>nE4QQ1;p_p8#i034jz5*-+vIqv90%l4Ir&oaqlWxigL^bM zqM58Aa*bTWnRL9AL;g@Cqz!{LoaR@Uphjy7Yv7=l z9)!k@LVkc;jwEBLS?85y&a8t z@}jQjk5L?n;%F(a`%Z*E6JaD$ivl7>B(lj0tOdS;bp=}sxPmMdnhfZ#VaxU98RaX> zuPf)ul{;*EY{UjeLo^~&)!A~hCrT%;V9k=ed;{!s8V0&cDo#&sHerV(G&M@ zDi8LQDs?(>i)cVZL?SLK2I&cG07%h<0BcEn`83+FfK1v3t=RAD4IS%M|E)K8tXC7J z_zoQl0t0J8>SIAbAF!*);3~{xyc!#0(s;ai*qg@`0yh^Ih72}r5Zg~Ek{hOy99-KXKTZOm(mcG2i zdx}(_eL_j%j)HJeF+SF?aiBu_)-fVC+I3iL@YszEu3%V(C$7f6<#i3~dcu-l-@#@2 z{*jW!B~B_`_0yNPnKNt_<*=lT+~SWI&hPur_#^Zi`{7Idw=)7$my~EifZ$&B z@R|GowJxZ6A=nszyil;Hu)vkhb7O*nqM8a033g%K|NS(ckQ>bR4+L}ZvsoS2^|W6z zEhn@6+U|}`ow?+m;rG8`d`o|N!A7`XYVraWZCHQR#tZA#FMM=$B(nO^g$wtus;F4C zAJ<)|=ME2(@w1Q6vyY6QrM%xZ`W5#Q;5QeA(Im7xI?-F3nN_;El$6%lYF!z*Eb>&} zP)(}q^rWa*#p>~QHvEq5l#NWVHP{HN*Xv29O|Q3QGEy0~VlWgb+owz9bqF=&)k=lC zA~<64dPBJxu38pb*bryL1{8^eA~5Gcya6A>4j>rjzc17a`~IZdpv@pNG{UzUP@RRn zN=${Xz`jW?9+$}?T!D+7N=D??+W_Cdhf0irrCsStkl9941?FB#+&H7Wv(<*KZrm~Z$?N4P1sY{-{*GgN}>^v*1UG1qO% z%WRrFSUj<8(OtbwYiG7(hSeBu|SGF z71w7Eko9(7szPqfbrwv|%jpF73UmR!SI`B4s7=M;Q4YVuo#IG8cS#=z>Ojy2{@$c6 z=NNNB&PcUjrJV(|-e>#*JRVn#@O3wLF1O$`vKiqjhH3-`?Ux75x(P!2N zL7{(hRK_d+DLaP@%JbZRXx}g^Xy1aQaaDk5A>NRUA%nE<^ELD9GIngcknH=abEn_# z?)&4%U#oDR_IJHi2cDJ`~W=8LMN~$XdjIWzm~Wa zFFA=9CvH1Q22bK;iR~v7x6`pF&f_=1_LQTJ=)^tBxbl?poRak`BTDj!>{%HplQqi7 zTG?jVT{4&w7yWF58g*Q$OAB=Q=$sO**UxsMu(oozXusc zRjvAMpnjp#P);m(-vvkk;-vkA!@x71s3mSVWjJSG{Dz2uNWnT+qXFoQyGMombVz3@ z>WhD70m5sUG<#@g;F`hp7+)UXF}@#b$wLrbA9-i>guBZ@T~%Iu#UF0G`njtsM(R7}&+X}%TR16C zPfi|8{Nu%;p%=07;6ZGDan8_-i7yUa7``z4<4E-C-vdmL6K3*ZRBGgo>Qz{&6rsg; zW9+~KAOlrAGAAcp?-|hBnh=#-8anlqj0$^kh{;d9iNPM_%r=K=-!A+?f@w~fG`Ype zo6~Ia8=t}8zG2i1Hk}t_vlFaEi&MPV;DGzSYY}XJCm%e793zxd;UBu-w<>#y{7MWIQl|ZQwkP1c&Yv_S5C6cm=~B*JHdQ!_N%ecWsPblF zkvYR6ca*jUawmmSPdLMkzUEF{noIMxy})bqcU?p6Y?_TN2f9mv!X?o(2_lPSqPrPj zVW4M8Y_UrTWuz0hLvjR z7+vTC|ecEt{T01cA%iNzNO~BF_qk|40Vk$7{=zHLb>iQk;Zz*0lt1H7b zeb!*^4L5Jzo-!d^pj%hWunDo*(a!8GUHZ_%HNW2!lj>}GEYrIzswHa|F5{&#zLK0t zTu7XT94f_kBf#f_Tz(dCTT|4}rDJI(mNM80{)wX4zt~Z3C|=wGvWlSKDg&`0)ev1fDgs2QW6C8tyvRE&uLYB@8lOYdG3NVsGm*xpIF*t$Y1n8)?Y&1=n zeb0$o4Z%=A=V|xdcworK$@SsJ{t5ke&MT?9D!zE(b6Y3l;jF5BYgbQRV^NwV)Ib)^ zd*}HF*VYh*Xa6mcRb8=fC?k@aQnC8cC2OABRMT|dKR>lc1}Yb<%P6gLhi11zMGDZg zSeW-uA|B;NQ)J*0vf>S7JJ}7>o&|-7II;qKMao`u_Jn%DgL*p%;yDBitMtm4aNkH#(owV5tdn43@gF*oH+?EVa4B^|*fVFo(}@ z_*L#t9N{=|00$PUT0~JCUHq}@TNgRzIzgwiC=+KcZm|qlh{YzA7U7~VYksUD1vS+* zgEfq(2G`V}+|_!^gw;?XLy3^|Opf)2>3l!aOP!fB;It(7U>D44s$r7v8#C@} zUuNzxN$ZY^0zXzVI>EXFDpGNmM@V7M16S5%#s*4Cmvj`=UHiL5tB+hgF{g2;G`}O_ zo^;g%t5ZUa!Fh9C#W9yLr!JKGmNT4Xw)nyhdxQsAU|LRFS zx6dsp8oaHi;fjI!RQsf^g%hW3>dN`?xy-77byjP3RXAW&8bjrEOo`r8;>s@fyR~Xh zenqCM#6ycC1f69c_KSyu$Cn^b24*2a`t zkRAva0_n(74)qq=K(m2XaVeQ`qnL8EJ8AKbOH9@-uB#YRjy8tCgyTWykMV+)QB7(s~V=ez}Ur?1fuDU3I z|7CCdZ}tZ>=b}L}F-xb*nkdK=YO}ouW0WHYS`ck{ME2ae*6_!NfN+n6Xg1IB$wcmlOhF>Ia85&8ti zd*Vc5-N}>CHQf0VI49r+ya2DmtOQ#y;UId(u^2Ojs0I-u@&GIP!L%lPDXAyKLb3?q z$^QjT0QMrBFboQ(PC^02on+IWOb?KTA2yu-cxMUD4R?yNWf5J)aCv!Z@s#51LYpk{fi*DkQZRBN4Mx_7 zrZm)goCTp0ceuVZ`$J2vOP}9&Lq}v*`{W#FevaQ!me_xhr^cd{1PzT2@tZ+kv_qz$ zJ(|maeQsrCF6x&?UD)L+MsthnGrPEmda|0R)iuG^nJFo)Y*`KRABzBp>SI)3 zK`aXUUr+<=bYFeqsGnMSy)RG`JsC&!{!DtA7+y~m?zs3$`tQl(Vgt46Gd$G)2j$Hg zwL-k5f<$mqiSwItr*G}^RF!1ORJveAL(Y_II`b-4?wyl6(AAu3APR$3wo(8KT26LT+%i_Y^ByKIoacVdwMn;Sg&ncCx1X+OL5d;pGigt)th*XNq z8_Za3#yn$$$Tv&-P_m@oXtXM^l4Dq;1*bX^>J9rry_8S^MSU;uJhc#orIB0s&ky)| zeZ5YIJ3Fycf*A-QDme&GUl>e?XD436Ct>=1n6UQoW~-TF6&kHt!9cPy@gVoyg=#W@ zquRDvgDzuPRdrdOEO8V-WFR+%*u!A2njo6zN2{ZaybgO<40G2b!?FyE4coeT-ja>8 zyESw2?fKZ8(QO(G$Xo-@n(@PCe3Kc^G2=!vo?ylnGZxc$Hp=KXYSa6n_i=NN%3Jt= zd8Gc~Ot2Tez~Z1rMpA+5oMVQp$S`JOI0Z!t#;gzK`9;nS@RV!KeGS)x5 z7aLw*QXClemcp0`r5oo0y2*iZo~S|5jpjg>1?#){L9?!3#OV7u;3(>W)A7M3_~1P2 z0+W=^QyN_MVGWFubIpD}@#Vq92K?y#m^m^l@oIkWjnlR-fB6-1=IO+lXQ9e>`tK6o z9l7%0%97!_bAT_(GX$P3L$PSKm_>6GytEq+$|e1e^Y|tnH}ZG_k1aeF({PcL>nDt~ zpECHE@lVkOE)5JS2v`HOfiq5{I>N@1;loV*$XEEAgofxLE0}mbk+@Ny6Fiqmz;h+w z=a!0hgNHXqW|wW3JuUbe2GEF&_Ucda*S(6dD|b)V?*NY=YanZB7O`jHv>y3Trnlb* zXhP}cehr)6FG?~>(vK6kg(e568w50Q{t}N|60yKTPVxzj_gO}}+^Wlv_}OQvu{sTQ%EYH!Lx1Fl;wGZr~J< zJTi&AU@rC}1mY-y0i@GGAWJWkPJ zKEJJk+j?Qc)rZ%VjNzC1|8lfE+8icp2}y&ffz%?MB<+$4wow>Q!d-YR2H#qS2PvOm z8hlei{*#j%O9nbdegJ|*kK=!iEK1U3d@ge+D{Aqx5q3NKILopk zy9hJgXpn~}%GlIR3X->h0uY0RzazMa&d(p~q1;%I3H&aAI}L67oth?j6tvW(3wCPL za8Vi-rJ2&aX$%;g6A-5AF$*zP^-a2cy7M|lr`3{WBYV=2ms?TfFZlc6vDjRnpiXP0szZ$0P2mTf@fF^rO4RGwly_Q+#Vij z0SKRNtansZRal@L9X^EWMQ)c6-}R!*sL9rY=41(;qrOKvl5VXuM zM0$T}K61MzKk)B;`~H3ZWcYn}-w*ds9(H6-xq4dHhE}h)_3F-P8>eKF4{efePhO4{yY5x5Q!x zeb{f=Zh73oA$7NA(2V+#3aeCVale*P3tE60!AU(tLm$)u0DUMJ9W+-6>JgX*EED{7 z`KIgFY?w?olwN;gPc6)-UEAKeKFaWOS1p?R#z6drmXY(^;mE3oDbB#!dI4}|1#4JgI4 za?BP4%u*_k1!aV2EX?q95uXlj_)lZ%KgYCp@|spWFR?uF9h9HT*#8abYuGwMJAdh+#erNF-8mznn>v3i6IRXM%YJlNloFfP>(eVGN`TL!2rp z`z{gALSt?YmIkUsF!6~U@Pxmfdd<=+uAU5p+X3dJJhN(AU`eN^wnVE*>`T1FWh^&Vw-C*cJpkd-n2njZC(%bq;Bz>$3 zK!|~g@*sl53t2%M!>e^VdttazG-PeCc{qRyaK?Qe2Q`uAaSpaOOP=N|Y%c%Rdbc zqijlzI~*)q8Ca5mdA&zpq>t&@1=@Amty)H_mMZIHT0E#(sku(W6lr3b4h`eh zV50^@c$r3Lwqhn}e=yk?rUI5?FhN$L>!>FPE@=gHz#@!MZio8@H!-=fyDMXJ1~F&g z42f2gnkva8Y^mKN=R(lB*VQHiPq8}(SPqg{A(g&Er=c%202NXjIQ62>aQ zQqX2PCO1`|ig^$OTCJeRXc$fdCXK)_v~Y&Hz2k_a#YbS+K=BN1qXv#oxNhw0(m@vZ z!JOa+3kc^-6rtZr(nEtWH( z;kV?)wQWt+Mq^FO)W&i(W4p*_91FCRp{t@L5{X4ZBvL7oiNVlKM#+E`2hdWq10sH4 z0V*~s@Dc^)MMfw$;`z%W1$veh>%~MytOV-Dv4jVc<&rrF!8*X4fUJw?qu7HMKp>ro zkiAX*gI}MxFY!M7J&B)7%)%X5i{-dE(MPgIJ|N$cmq)%O$`QeNfjROhj9rYDMU5Po zpco&Q;(#a6X_n!~=p5Fh&Vl8O@&uu7g@(sc1ZR};u4WW0#4#_**7w#MTRHK z@DMmQboi-o0}K1?Wb!z99`ZRvDq{Pgh(X*>5qyTa9~unF5eUEvEC=HhFg@moBojPN z);_Ey3r6-auaE2^>yI&KF?xMiE6AGdP^Ym1#wbGRQ5}oEg~iGsSHRT&H#yJ$8~+DI zPJ@U8RGbB)Rp`#836T|vRQ&f-rxMTc7v8(@D3w`q@O_iDiZo{rCNvP&5J#ory0;J{rr}V-$k%n1t~-qG|?xi_h`A1pQm|ALifedBH=c@Cu_b zg)~sxr*Vcp`Uw9$k-ChV`rdzpWq@KLhMemkfH#VxHXk)@3}T2YTUaQmqM*SlH%2O^ z;8Su#Apy8<{*Vy477TOLs{>Qq1#(v;#)o&{Z@x+Vp=NtER9qe&t|y!BPdsxky?Jyv z@f_1US_hD6VWBaH@zW4QhVU5lMsZc>WSWN$Fi>f8voPOa-R%jcg?Aza%8puUz=08A zxm?7nME#;Av`|n579d3BAdpV0SA+sS=m(-`g-#IE26BCR|MW&R?oj*k0tWU(>Xgn_ zt0BF$wK2^K4o11 zt$`XKI|@KN-NA`yk}R*5n-(;vJTdLmd>zsnd`)Vp(duXd=|zD7TL+4dC=i0WzLYtn z3ft=v(JZ^37x6T+E20_RWLTM6+~m8CTZY2nxwmzeOsftV43Hnrr=FSIP*mF;2-W83 zi7sbSQBQk2yQ2QKKi{(B#BKHF%uwn&C@tFMY;tXC|Qs#<}pYMPaH#+}&joa|iUWc0OMheOjORhycSQJ4jS)j`E+ zuP~Q5fPf|k#)5c)ZAJ?VApp*hWmmkgc~ZmX7gl%dZ|JPNvUthh;DTwl)VpJ~P5R2M zC+>LY^i8GNo-fke4?nzbPp&JWg_s`2F^r8Zzl7uD=u!;sI#YWPP~DMlF&L4`2g9N` zrsJaWD2ZbM&6H0rjSP~mAzDoe{+`A~nWvcMB54*jIb3{3!127>I*YpN!bU020af$Q zOi%!hyNZh%{U)r>Yo0LB-J`F+m7;iOy(M>o%bS_rj?l$!zVO%iO*E%1}onW4JB0?Sor) z{&i=4)wRFB^7iPQT93@=G=61B(*va_AAw!W)bVx*T+~N%q-Kc{>M7M~tKa&#m8h-Q zOk#|P;XoQgO_~#SqPF#gRqsuuf4Ts35 zpCpd&+gC7fQ~UI*bL#BXd1d*ZFl&czWY+F%Yg@2wsw+)RMX%vR29XVs zg48g*OkG=xYg-)FhGyJsfRs~j4*z*O;AwKQ=0SV9gMm%6=4*Bu1QcvUZmOt$MyTo>&bz@hc@s3kFm+xO5&W?5Y4OvcuE~6;Db56;^J#+QJ{A_t*x*^aU zzW2VFbGBJ43S5h;YwDtrNt@Z}dqe4yvfH-}LZPF^ono-4ICtZ!*8aN|gng~c>&m;M znKFqZJCy1u$jvl(_AT$eWmW+v5;H&YG)2`}AIVlY3Ynpv!NH#H`IN8rfDJPrWB|PV zAnN3-1}nia)NV)ifPJH#QORQRsWt=GWL7J{1OR4&=?@W6I{WE_0JPCUwq#KZO#6%S z6|{tn7})uV;|M$Uv(WKTJ zEoi;GM1Yz!J5zGY)6>gxt=8PK^z`!F6kI{a!)@?|T}q`Z?8`27sZ_4g3y;9EE<{ET zB=lp?_o5n@ZQ2wq0TWmrv#Hv%p3lMoh_>&@Vk}cWqIpk+YCMYD1W}g=gmHn>&SEMs zf)LCFvkL4D2{CD*CP`QB+;ihe%kgkC8-CzYtY6GQC& zAG4ZnoLwqf@G}6$=y?Qir$mbnOL;6On7WNmXias3`&cAX)^V%>R=9{jyYXNoXF!25 zr-srVHDnPLl9+U=N(M_CH2OL**x2x*8>xY)z*wL;z+PWczrr-4W;3<$X`?WEFvNVR z#g_~Ki|{K;u?B*^3-JL61D{(%5w0c!`|;F-b>v2Tpe^wruOF#TjPW3T7skv$4X*Ba z4hOgaZU-3eF?fpw*h`eiNp+a?>hZg@Gq`^2Uj7`&o^0?F3WTr-V}csU*bf-rh4Bp- z0^P874toS|6Jxn6mfoJzpMwR?_LLI8>5UIUflg*%3s9Z@t2S%sWzdnr+>lC=1 znnuYF%{$O=_hqafG70r!#<3q7{}fCn13Qp7Ue{FGln0aGOqRx6)zY1};(njST;0}_ z_vBNFx9ge~O}{iB*uL$)icH<)cKZF&2P0m3>aqJT&Q*Xl1x<)%)M%kv)L<5-Ybr_& zq;5=2PFJg%Yoc)c@^nSNIO?&vI;o|s#XmO8nzreIwi_;=sbeEc&eC!FVGzb61M7xL z4<+VoSgOZTNHf>Ur#btb7}Xj(9FiuhnrYHpX4oV}2bH&%YHLH2{^y&w{&{C(ajoXgYuJO{?y%5SCqb}fy z&fTBcj$ai|t1d98S(f1v$Itte)MhN6cFRyn>AYJHUv4|SrdQQT8yL_E$aQj_n5#Xg1jOcNSkqMw8DKXDCDHo}_L=*qMwsX_ zlf@J&rXstbs2I`_U`KWrH{=}wcf<>&vRAglA3Ak3NY(P;$y-x*jBY@^2lp!-#%X~pSn{ZD`$djz8S_(=7}au3^6n+j$| z#sPzee5dI)9&iPp9a!BbnD7_tTM91~FJBC~3aR)g}3eNdg z5cmha*cKR%0_}?0d4*9?q!?1HRvcEms`yOtgFG43>pbTND||%iB&lAw8p~lj0MuC(ZnjYZ)8(= zU4G%3Fj-H55@OT*3V5z{ zZ{|%pu|}<6yAwwe?_vKkiB6*g`iUU@E@7_3Evq=SMh1X3j4sexlt3D+?6wD z&6>G6-IJr*#Co z(H;)`A_$v8DHXwH@Paw+ZdeHa3@nQO2jb=-sJy^*g&(X+$V5bnaE$NZhxlFmUj8uu zC;l^DY~sCqF_g3dt4SEd%FGa_+#@;)v0;jHDh_E`nZmB{D;`(Cn+btMP4G-A4x?6? z#H`lU&4U6D2@V4ZNH&yZ3W0bKF{h#qI61`NsSGY+VhqWkE3zTyO;-Ymr3MxYQ&+G! z1$%2O+U`pRnv)JJ&`VevIA)OvG2CR}iY^wi3dX1m?9uBJcjE46eh(|NcjEPly>Gph z*h?l5S0avEMm`*Q1i(?F{ zouF0$Fr-DwQyfqQOeUM%tg*@wzzpklgqS&9>MhjZ0dT;mRFn$fQdsE(r_I#a(E#t! z=`C;^&3a$-G9nl^KdDwhjjKqi5CN=-SjC_2FKDzHqOL%7zDuElslT;gwck`y8a9zt zKk5!XD&xQ9#3}yDELukbYYv!2fDZ}e4os7nE@mDqKT&JUbr8UUP^N^1U@HaO3c+=V zZtsyDm5~h~@1o#f#KaxqAu*GTaKkMSz8CKyN1>)7s?gRE5!=kEh`5=q`8*9FNYELS zmV(3rNMU&iT>*Nr*oFpL4cN&n8h!&GOLRchL=3<6BzfV?t0S#RyNzp_F5typ6ZzpS1}|YYFxwes64h=>=lALVQGKr*i#jL6J#;m~ z*PzDe`bAj8>v0u=`Q#Pq&LPrf(C|D0 z`-&?WbMC~M9PlEo0q^A=LiUc*Dc!vGm#{JsEqG}pphx%?B}k{e9| z?x>tmv3^r-{roD&#{$^c*SAps`ZI%%G587wLs|){)R_SgK?vSS zp@MS!$jGbI%A&#mmSKW4On&kM{k^&nsGji*^;jfi-*=(JUflQdMFga8IG=N0EcCv( z90KP4cCZUiL%l&3nsrc_bp%6dK?bV{bdS5AcOQokoNtdW?mOyZY>Nd`-T|_Y&$dHx zD{-#EDATffgM7PuH!PTtZB*_6YCy&#^j7a_|68HcP~r9`-|1fUtGzVQ0Ab1e00*xk zpn){u?E?&ys3whj!7-%u-68|APlr`ECG&3YIpk@qjhH(Og(b!2%IOnQmS5AjEf~70 zM*p^=;riD$Z+r8m2F0Tfg4Ly$eJs_fB$-n-OnYRv+x$(MoxBcofpyWKBP#(m54t-# zUz(AUk)6TFK3CyCsqibX6jL=#b%%;EKzM1f^Xatzq>%+_>(WSR8fFzZO_8QB*rTQa z6EQKf44VwZ0k35tEch=<`x@D8z>*GRuGPP$|3J^^R~XG_MEG@xp}@jASo0J-4Yt~8 z0qovnI98DDRLLb5w};>Y_)sGiCrfp~AAqPb7|TFW5qN4a6U(uyFhsFM_AP#*rMGrf zq^bcjl!vT2g;uh6Se;$vHy=6_S#lTmW+45uG*?sq3n#Z1x0Yrq6T|1V*`;lzx1M}y z+3vwS%A?a!f|axTq4K&2EZF`sXQLA)b(MjHR8=Zc#xydd%+_clW#&dR3CXMEB*a(2 zsu87=vGHcPL8X#I9T&>ouXV7l{d-Uxy#xGiH_3TGq0?-$IboTbB=dk(!37UUkpJ|n z!M~mc6N^fB`u8gJ7XG_YC}W1u0L6w5oKtTxtktK9k7AP$oaJfJWK!FSyv3ekA@-VF z3`*zqzEsOKw{P$bg^C9J8-8_-B`q~&{jD2vW)+pp@~yvZL*a2-sM?zuE40}PW0~IS zkd3@oG%IJr&JC7SYw9)IugRWMR5CMn{jKXwsg{&=x32G*@2v@?rG;v|nKdDsEmR}$ z5u_K{nV@rw=<%plrPIlw>dUAyOg8L>%1+imReYJj0542|OgqL=vIn#uO-ze8#sCp| z*Si^N6yQo$_pxyiq~F%0>dMte~zYtP!j>q>S(xAXV>l4F)E!( zhheGpdOp4GPI^sOYDtzF5i+Dg;DC$lx;2%~?p<2{y>n z7Jdg+BD-Gv{`>C}r_Y~1|9s*eTt-vgBZfm;4t@9`e335!bVM%GQzDcR)e(L_6A(pV zsR1LRF2!`Er3c>G)NH0;3pRl@ZBD>Q!q)&gZ%VI3)? z2*z>)KlAGikbb{Lgz zb*LvYBgD(nGICNJMWK9)$eNKuX?C7H&+O${h|jwbtai6utJh1fMHcR6*EcG>5gb+s z7=zUsIt$ErHb_@C{X8n~{z;VHbKdBtGt6T$dFZd(h<6%21-8_X*JSdBQp?)7^{$Xp zt#*c7=|TD%OuziN5~R-H=vRC-$n{iIf*R4<=;RbVPElj#2#cY@{ ze_%oQx^Q@P(}FzWk%8E`?n1I_HP?D{Oz{` z#0io1Tqmv8b?n0HK@T$Md zU-1&bmz5zWO#9D)LJ|>TsEc9%CeE7bnA_dptp8*1$Y+aj6N-0>&Y;Biyaxj>uvA`_USAO zYbh0|CmLkF zFL~=tP>=VLg_AI%V@mTe=?2Mm2|-ddyf|zltT<3grAi6oFqPuc*YkAdYFz!gNckgp*$WTdDDpQ4|NM)WUDkG2yGBG3|Ll{g5fdohz z2%UrgPN)px^#XER0i&%5h=_oGO$mq!(vfyvNzb!wkItiATyYp=c5+DZ@>)34Al>KNT$f6y3oA#Z}-8srGs1U^Ro z(pCfoJI2Nz^Gk_1X^cCmCC~W}U@!g&{@%z_N3xTK{3UnQ)NF3d&u`pZQ*+mn{0o-U zBAcxU{9U4&WBCF7N626XD z^r}iDbwqihsZgXCv5znH-m=I(J;(C=xtUwbr)-@zz0#Cemc6baw=CW;!Jaj*sI_F@ z%98flbj_!gE!F_P)PgDTn;wXZJD-eU8k%S|A23HJg(gl&Ox~GQ5;M7;bR8~RIU_tf zEIKi>=yhxiQ#p_k^%d4X1u>z4uo{{fnw^yjOF944{F2g%;hK*ltut@AB{tNYQ#h$` zLNM@8dw}g&e_L5mS?m-er=4xLSwn=iDr(+%-zZ~bWFT!!8fc3dswn8v$pH&{HYBww zo0C{rV0VD*nS_4LwILY8-1M?y!y{S zyR;F3L^#eXN#XB9zarv0d15+wM|x7@JCV2lC}P_U@fM`_Z&V=gp)0VTpdFG(Hlr+C zCVgCZXh?{UPtcZ9#0lQbF0-p>TB&ujb+`4hRTCa+42cerLPE5mfjbc?@>39L7WAAP zm4K8DzCnybQAP=3g}#PNZITM?2noUuM%QdukWphbCL|>ot!dL6>Mlk`Pt1t=kD|pT zF$UkG8of>v8JAEL*jwxun^g=vG=QJfVC`mt{b;<=9Bs}rH<_=RzcUY+^-AcbYwHk^ z;6V;sJ-zX_47{V%{w}&e3Eo88ka3>qO$m0X=3D3!xh^;~)TeF@&$u8>=jTI;OL7f9 zpq~U$J-ZNoVm0gq;uyCRLKoFF;fhx2YRxoJJCqbkY@eqOPv;z`T`d zD=6&5q0ZvS!np-02s^=3Zd5i>*oi7v*ojFgzZ;hk8vd>G092V+;5qTn(Ax9liaT10HMS3?_+gd>80_hqo!Z#uFoZR`f;d`9GNm|`g(Wzb~Y3FhnHczAUGHFz2SMv z8`J=hh^GyH>N`62+RTNZ3@G32vJ@4SiyZ+T6YhEo)@FHm&_Q5~;-BvK;A=*3n13!y zKn#T{6W5w}6%0aUZjysF!G3{@gLLa481WE;k8x!v6jiU5Wg0_jjGSqOJ~__<3Vf4!AAG>eE4s^2~2tU7@c zoJ>YB5`dFLK?k?dxna!zG!ILUx7Y*$h;+iASkOzdN-t*wy>Npz5nJd-9YGe(yy#_Q z`W`s)3wjYR)Ea_4sonYed*$E9qP^0af}=-CFX^fOmHM=^2&Mle9tncs{)SZL4X^%B z1;f1}%7D6sXx{mWcN-ob*@h`d|0l2XJws1=)oJO-HVl1_y}fj=^nF8RUg_BlZGcI> z^tot*QlIvr5~Pnftv2E8(2!$9E8r)g#)mLru3K3)C1|nF%9zp^zHl6$1hZ>P)Z!%g zW%9rlTcokc@*KawuX1?@=N*s?IiDn~^?Zm-rKqMrb2!CKAV(D$e z$jSzrW5HNfP;80N5O@?I&_rB<8e}3A+Q$j`S@)|ECe(Nn_si!*q=|37L97XRj?_Y7 zCY~1|C!~2hDcpp!*XcYCY=N(UPP7l`Y23r>2YqshT~hi4y$HJcvm+|i&zr7<@3O!# zL@(+?HczCNk_F#-%#o3M)AT*qPFMIAy)Z2~%ad9e59Ax|m46>Q>7DeG*GY8T~oR*~mVq0g2t~-*ely_9hl!`MIy)g|cigpt7__6U>4 z!H#nxgLZ+-*D&*ug4Z8`v75y45;9{ES+svg0TRSNRpW{c{{mkg?#uO%9r%9dhx>R2 z&*_CIp0U??+4=s%()&E4*ZCn&qp$Dn=NZlq5n~-ValpU)N7xg^gI*_;MUGF7OmO>1 zN8TE+BHNmjW-}w;Ae^x-mR^J^f))rwZK_s6L={b{(U!&EtZWuu=Lgc9MX|H9$rD}mKrdduWP1xw zen~i?$3%B5y=S@ecV``4(jU^`f!_6O_w~r&5cs4kbBFM46?9Rk(p|Bj3vLl~deUKy z_xGmLk?DKbw+dZ|7n&^5p45UaMyBs$JH7LFI-=7&Z-DNU^h8HWecIU`q{sXx@g(AZ zvL#hVwKM$FFNq?_j{*A=jR4w>pmhLL%`jA; zkVfhZ!vlj-Y zOrKmjKQAn$WLil|OY`cVI4@7X|JC(fFK?^Sd|cK%b0YT0Lt>NdiP7b?4U>dPqyc#&Am;bTjATLivgWfpwD$| z>LEr_URDrEd9OQiCmF_j`W$pMGJOx?iI9{>FYL+6_oQ~GwYl@|V?*w=@%d9mNgL=+ zdkN7+DBlUlH{M=8N?MIOZ98_pgk)C|1QwZ=RjZ^{DBE3+U%1o1hNg@3xs)Du2j1zy zpU_6#arpb?f_i4Mhsu`t1;W0HYhYxJfiu23sVXQhFCevs`(lrxCM#h<-K@HfI%!gM zL_mFQUQyDtBq$7d(=&}5r703?W|9LWRZJ69p-wML=WR|LJL3VJ>5$jT4245QbP>3g~qx}q0W zr{;K4kCJzv*^|~kr+AdKM1x8_+L2b!R1wjXAr&VcP(55UIP?J&LH{P|{Y%ltsm>i} zW2l2`f=G^e+~I5TNU*5O9x&L4z0&s#(f&6^gm_`G#@~~g@G`U?=|AvF-{<_wJAbFc zKiu;M(qBXRT{^12QlEB_o@C2lUv}A7^9~!1j(z5{5;k6?O{v zQJAyMI;%`~z=$`JSl=;BIPlR+cG0`zMekmBL}wV>v%i~;TSR}&QRo;<34GTk z==g*qAwA4+108#(?^&VHCcQArmgGsT(y@2iKDg|-^S~~Q2ypFqYmWKGK1%U@}5QyeA z%LmX0S$MHjE8QvWlr;WQ8Z0(4DB5z;iECDFw3b?%tj}3BR^Jm326Kd(c=a^4H-p&; z>`#16mS&`ICpGP_1(zg?C+LtVeyMiYW_PAEGKwu?n_k}^mS~TPnm{|?6QZK*iD4tZ z6psf*^q>f>T7mJ>qS*u8YaC05E$iIhC-IMx z_=co?Nm5*rJxK~l;(tlxUq|z^(L6nRaIhJzX|~wX92}q;tS(z;x+Nr zY(ZvICf7%WM-@a>M`@y}1M4$zkj=HS1ngrZ+VE$5VyaJ!C8jW@CPoue8*I|2hNl*! zR;OxGYsn$PwQCM}1Qz(TP!pTzuK9xon(|N4G9{8M?J=GZ7C)*et#e7z`I;7T2P#^X zU39Rbau&G+m2}y24wRHuU7u*+k;z4EjeNJx`9n}bXxkDghdhKr%u5z`Ouw@G0eA_G z3!k-gSdX0C5l=l`mgB7R(H{f73jYEz78WXF0W1leF;a}3Jq}xvA!68A7@l6w zde}WGZ=_ddN5#aM3r~Ance$ z8*m~4#(xiD-x0TtMvaz7Co-DCC^^Z^!cPkO5b-Qe_}Ci|NrBs|{T39YMMq+XB;U#+ zBSW%ljbSXIRttw5VXZ4%av&%}E zZX6Ka6dbhw4>2c7Jd+T|ql*3t?t2v7QAr=!@Bq+q|?SF{$m`d66i1{#p zm=7W30moR#_q36r)BjcGDKOhK1~dh9AV4&>5lAyGVm~1G12!O)_zzHb(@X**$2hUM z1y6!dImW&=H-DA=?Qx~$lf!sR=;ZS92+hZ_^@WA?vC2MItUD$IJS6+&>AGKtJ%xQQ zGPAJNFxMC?aGds`KVXDk!CK#36XFBw*^sS9C}f)01+fXEW8=tUBL_c>+d9jy!w>uR z-0x^Oe+022d;+m=v)gElHZ~bMj2dHEaCnXJX)E80H3+@skKe7kjoK#oj>6mrR#A-~ zq=^f-Ag&((#6skT9SObS7%9wT5p-XQ$?CUoTu# z5))IrsGwkBF|G>=Gwk+^jNDwp`z5sdW7tYZV#Svo^q`q{z{Ce`3hUt7YOXG{*2ISZ|U+Dp_*jG&QjyjQUbfKqSS^;Epf9bfwkbG_Br~He9~Q4?@RlfM@aqV_=Ac02s_C%eFOAbcsHysd zz`&%;+=wgUI2rk3m@CBi9i)`zI5XZ=?$ojzwOkP>U5$}0;-$L-rK|DKPg9i;2HjPx zdV(HM?TUQPf2Kq_zY<8N*l16O6>Pfmw!+C?%z63M?FeM9INwx?>rre+K>3& zg!c%xv23vsss$;Q2zuR0dxROwfH|~hSxLM3t!#q&* zRZN2GhJPKH?Cv$^SI%g!PIP`HyAK7rBX3xTj^3VUEiYLVW8Y1d`z?=HWIqdzK%h44 zOEl-3tIVg-)B_NH#7Ras!~AJ#ty%k+#oyH)*AYh4Fb^4k?!;L}TGtIfC<5Or-`$&c=6Rg}pzxbz{G@b1lY)7mx-4TU4hww{=QnAL2sfJrqvWPg?cLz)O0X{k zI^ir^`>K`>7}JpXfx`&v1K4c7OUl(oI#vs{8)>t-EWNg^cv4$kMn+xRq~f;PbZMY| z)`vuiYs49$O+U6Z^zzWnpA3r`6zyH@G$Uz$gwDajtz}&^SdQ7G`iL zYlPXGx3QbjKG@-T@})d!yBVI4IyGOHrJa)Rh`nTK*=SqG#Mhh~APDWjxQ5;Onc;^} z(Lrn8hfvXRebOFr@SYmYiexAQYzYPtr`?Z*!fR!6UT7u4C4@(ZXJMkb8vb4QP`KWg zx3aJ@faQbYMVp2LFvU(99g&IN zXykD?v=L_n!5ktc4_5JXa+EZ9=eN##_&o!6IvDz1N_1XIdjo6G+^OCNum-K4dLPK2@HnpR3T}Nkg zcU#9=dJCC#AQP(ATvTZ-gD$F~v!kuWHn({#?x*1A)%clB=~#`w<@kL$DzX-jTJf%p zWdKIY#pZdnylZ*u+LqQ%TZYY(wN1(JZ@hX#`g&1pm)dR{cHlAvu54@>TaTwyS9+R5 zdsX$Von2_BEhjTO+qP_d+v*luP7a=X3U#$P!&6&PBsD(c#u~Pv`8LsW-DrHXK*Aba zJK0b0yo0U4-{Jj5D5V-vs!g~q6TR4pzeE*X=s`lGLKkgFOEf`Gh(46>?PwR#M;G#G z!>_JB?-IQ(x~i?q)@ug=w*45qG2?A+b zYg^vh+1-q5`})qdu9mjtL=Ih`ha3BMbb@2z^{Z48Z)g(L1Sqx?g*nRfNvte_3E2c&RP|puJ@GQQhK0O zj3B}vkv!F4Io_rbxCWo~>XYGP$~_Ql0>y~2+`JZ8v~__zY+bFLZ7Xcuo7T0iXkOlG zYiaFjTe%kKUD45LTaO=sXW*NDL-(K~T35F&@9yka+qN8|bIqFdxN!|b8u{I5@kH!B zW{DHpH=uW$(K7GBMs=mJpQ+XwDu7kp-RmZ1Wo_87A+uQ>Wy>+ZGf@Ej`VVI7LjQ22 zccMC(BF{BIO5WYzY(*OVRVk*&()-ZDX&nz3eNto$X=|u%Ba2Jj^BbPw;fxf{0-M&i>4HvVCj^ z>tmm=Ke5LV%;0~qAJ`x}$W9=V#_Q}P`0R3cbhWUzK~--+mbuK{VehhEBg)1K_C9_ZU5AK5qTen>-Y;N?W%YoR~yU_S*t65+4MQ0l?R`~%yFd3+PQ zo!!aqfF;P2Fh{tH-OcV{16XmKWBu&kU?ci5`vp72PO~%YHRga+b&1`_&O_1iGS}!a z?z6MYD||b8Fgszewi;G+tnchvzYgrAqmz1;BpC_1D_Qg?{=Dvd{wn=&v3J4`bbTXy z)_(}eC=g#P^aSs4{QV97YQ#691)SfAeobe5b#wPx3a1K`RwvF$*W*0 z7K=|ZV4~krzaGCk#P?l(`|<28ziapm`X~FB@E!h3{hjzMH9QoMDzyi^5oiv0Bj63> zEk1$s0y|wF&2$`_D?Y|dksFmjpIv^*rj6?7UeklFkKZoS5%u$YP%7&Fc~A{v{-(3| zp_BNV0=k_8x}5{cT>;un0rk!S{id+0p*~g(r~ynL>c{EVeQX|J0e)KqSd4Mgfa|@; zXA58}U>jgN;6A{Pp$1TE3T&EFKzS*ktQ5$mbJ$Vj@eJS?;90D>C`-h%RKNs4E}&qjmldLZMYv8xxs!3dAI~2GJPg1O7%ZE#CJ7Bn}Ori~*D}fHDT~>o$}zfHL|}M!#Cd0LmCZ83QO| z0A&oIi~-V=ql^KRF@Q1#P{x2L<4xrI4nW7!@uUENvtijJz+}J^05WP%0ga}BL#3b& z{is7f>d=om^n;tEfFq=!ZT%>>ALaI=+C)3dVH`xS0_(4Fbd?U!}Z0 zQQky+SIX>X#kjv0um!Lcunn*sa35d?-ra>O{N34ufIWaGP(m2$Sd2Ori#i&CwIDz& z%8bXaMWBg^fXM(?eecEdEr6|nZGi27`v6qJ6G-SSNcco6%+c~j-IfKQBC-B z(N!^W9D9WR6x5{9-dGg#pQ+pE6hmWc0P~&}W8V)zG-CpI=w{muV>%yi6o57=Fz%`V z)qonnbi6kkW9SyZ9DJXP>pWcN<64jFLR=T2mlgwV1vCH}0r%p!Er6|nZGi27`v5zJ z2m@pDii1dV2yhti6yONpX~0pyGk{}&X933nCjh5Vz5~}60H*+>3x;;r?~B>rLS19f2bw`YZ@QV|VQF*$)}R8>b$8_T9icDp1aFe%=Q>_5+U! zU!RLB@%H(+)&mv-?#1^lfUSUSfbD?$06Wl@n~kZ1NOK5q81NL}2;gbJSbYB!${LIJ zzl7gj2D}0|4{+g#TD}6*v;x$061X}kxX(zwQGqwA0M&pR0Bj@B>H)NR0MyeD>KQ<* z`_bwFwbcWl7Q*>h9P6YQFJtkdgLv-{;4t7Rz!AXHfTMtC0LK8&0*(Vt08XJS2d*yw zP6N&W&H~N>UczrL16~1~2fT^4z5}p%amnF)t_rED0XV%Ft)rF=+_ayL;`e6&#{ka) zjss2r9Do-9rvYaGX94E`E>2r92GtSo99|c|~?COn8VhX98GPXUeqo(3ER zJOelecouLRZ~|}&WjSzt0dN{{25=T|4)79wdl~Qw;5^_0>UL3JL>aNr6r#qo%1~xT z*No=trqV^=Ws1BM0(m9`@(j&;N{?#YJvv=HRc3I?vrpuyNCYnIO9DGYr3QEI75GN9 zeGqU6a2W6u;0WMpfOkm>4_+WjQfg1-D^I6+J*C{0l4+z61&>9ku9{IkO8b?3lv&+J z&?3>=Sag--mbwDKS>QQn5pTgOKp)Y{14}q^QfUhh60Ise8S6AG) zqMig-1_TWKzpvFBs0a1p+qjb4?UAlc=wG5OSI?2`OFi;W0Frmd;_og>H@U~g$ebvb z2mX#65E}-m#+utBnE^E%DZyt94Kg7YVPq8H_mMJV8J;Z#jFsyS{5G0IAB>Vb@FJ>0mefJJ}3Ve2`1J66a^RBpO14?}!PtSvU zy7;gW-+}Xt!gM_M~Ye-(xP9jU)UWf}WF%JD#v literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/fonts/DroidSans.ttf b/service-webapp/src/main/webapp/swagger-ui/fonts/DroidSans.ttf new file mode 100755 index 0000000000000000000000000000000000000000..e517a0c5b9dfcdde4c4dee7569fa191b40f208bb GIT binary patch literal 41028 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 V%-PYbojQLtAN=*7zd~Ud`%m*7fg1n- literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/images/collapse.gif b/service-webapp/src/main/webapp/swagger-ui/images/collapse.gif new file mode 100755 index 0000000000000000000000000000000000000000..8843e8ce5a46781defd33d5304a0cb4191e72546 GIT binary patch literal 69 zcmZ?wbhEHbwd`J-OQ1PRqNMh0sDWgikt literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/images/expand.gif b/service-webapp/src/main/webapp/swagger-ui/images/expand.gif new file mode 100755 index 0000000000000000000000000000000000000000..477bf13718dc56928f313ef6eeb1c2b1a47db69a GIT binary patch literal 73 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 literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/images/favicon-32x32.png b/service-webapp/src/main/webapp/swagger-ui/images/favicon-32x32.png new file mode 100755 index 0000000000000000000000000000000000000000..32f319f89bd07e691de2ccaf21632bb9793a9cd9 GIT binary patch literal 1654 zcmV-+28sEJP)Z zYjBlU6^5U+_c^%{LI@BBNTw~6nu{hdI!rrEwGFgm#aci_3sGk}<7m?sf9eb^ z&gef46cL=-R%Vz2t>aV&tTwa+k{T7cTawZs4M`*flAN4x@8yqkX_Av0()aswzFB*( z_g(v2XYI9wX~KJNyap8@D?yj3`UN7k2td_e5i#!rt)NGYgj$R-e!9LVh4Vp9;GzxA zMK7v)oerFfUJTsuv(5*(Q~8(4BX%1_Ls=xV0tMmqzMUI4PvXOov* zN@+ngMR`jQiMjRax>fZ#BX;*hCo--8?_I7}_iH1eH6lVJdXsn0?&9dFMn390GigKx zvpz#@c|A3YzfF2L8&$=tJ7mOs!x(#QN&()xLa+W!YeGv@mDYd1!hz-=a(&=GlM;_b zPG%vitA9$x!Y2?Bybn%_m{rEu%M%KSMehG%O=wBrhS}Hj9gdybeM`h+QN3s*YnL|? zvgvUVc7-t}c@jwh@o&E0n$ViS4YT|3W1Q?bbX(*J+;!)d`PKvb6Cw_Y*hdmMVUhyW zZ8T!ns4DxK>OV1ZfPY>3J+HpK1yx1F)_HZC5;g(w-sLEI-iR$|x$s+l^XGNR&0(9# z+U1ShGrx|W{)=pX?Y>FLo_?g2xjCQaWXJ2g+O&meFp#vZ`{G`z$~NHDbpvJAgsz!b z^uFGREr>*Ka-jL?Q4VX$H&eA}1J?(yaQ5=yDWg5td6>SNom7^sV|jV~DBHp21|rcL z7_oCzwI0BD?~T9~fOpTln3C=D^Q!>ZbNFi<93yT#96a8@?!#XNptPWRl&yd8BaWWl z34kD5y!Xa<4JwQYEl^dOPB*4F%u0_Hvw`81zwm@e6sU}FPKxd5=|)r)V{EC{pvuH* zJ6!Jl2VH$<$8i_SQ&l%@0nSHJRgly=clG*tI(ymx5M-qRT?Ww6)jY1Vth8L5k8)$E zd)fkqhWl`Sm>KDF#NbSZjd4f1W4iLB@ygIB2Q(Evj zKI~~rAs@*lV&6Bw-ypSUBrp)UhBaZbGx8@q#<9o<&%&CtWIY&*`2|&ppREQs0#KB< zgo0U%##!zYYNsv0+Hfp4R8wsEISVP7vn=s?#2B$H-n(`Yp>|B|1{jR=0g#zCWqX%I zR@!U;hN9O~Y_;X}i3a5ZBlfs4#&|`42~e~6Npdm^M{Vf1dK`d_D_^Fr>iH?jAFkZS zH}BsU+uL<~lx=2a0X5~D0Z?>@F~%DJ5oz@5yVJwjtghZZYSYovJ2-LvC30t!u()K^ zlmg02Rxx|VJX$Zjl5)4yRc$9DJQJ_JTg3bVMs!5ZMK_7qUje-Q*G)8^+cPEEt)Q}O zJ?p-(7XYvB$&d~2f)O2&h~4AW9TE}NE^p+nc@KSJTfS+;@4SL}@UgYJ}w)Q)V=$7{=rRQ;RAgzi>VS}wfAf#wGK z2d~@`@yyISSY5T9%ChzGtaqnG%mc=liQDoKB63yzUJ+Xt8%%ES&Y7Jwo!-IKzH^fj z=jW7BTfT+b^39}&XT}GU;0+^o{j>&?k41Rn)ol`yZ6ims&fdS%-gS)L{`TapmX$u6 zyqTqx=2ufZ=iU*cneSC~&lw5r`oD)&=Y!n94L=*WXvefB)7Ws@5xC*4w>6xOb0Q(H zL4_bI75%KLUtq)*#v*lBL~MLK=dE~!3#Q}ufBOd*=m{K;N&o-=07*qoM6N<$g6kDD A00000 literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/images/favicon.ico b/service-webapp/src/main/webapp/swagger-ui/images/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..8b60bcf06a7685b9ea53983c125e7058906fbcbd GIT binary patch literal 5430 zcmcgwYiv|S6douFYJ&9 zoHH|LSE(UtsLIS#7_-&1VM>ivN@Zu;-zA{00&N^|8r`@w~u}3a1X>=27;pf_a{DPJMTR$JSvjrVZRL?J~Ga>i0GO0Y%B;(7M$}2m+kcrz@>Hae2Pk^@b^f8%H zL>`N;qJuC@Oe+t~7I z<(F|AfU$#EfN{kFKA+53u@A<+4YnEIf**{2=CuCE#{h4Wo9v-X#BN#D=ro%g~u zG`=OQBiQEDZgc6n2RYf*+oRWbKVZW9eJJuGNa8A-F{8(f+y@)q2tylad}QmVTBh{m zkpvl@O*Y<9#KzvA;3if`|g4qjlr75YyFqbGlV2Q--{B?*ERj@$;+A#v6A5% zKJZ-k(ti_bGM?eOj#f{ZHOKc6lK8&sk-j?4Kdjr4z{}bU>r#UnlQAQ!`bOZB;Ct>?+|WpbgrOY2zPu-B!V?-m447=gVK(*?-RQObKSO{281uhZ-RJZ_ax0d9HE}&gAII8uJO7-x&ULT3$-hG# zr#*GDR$v46R``GL&)??M=Z|+Z8{*GeE`I+!{6E}3tWV7S1MCU*9ccYQdsa7AC-|$l z32U8*#-^_c?O%nK{oI ze@n2sr7p*=%uQX9Dk-qo-r2V)7}P0ZWaab6B%)NcuSZfL*ZERjG4kd;J~ udLgV-)@7w`Z&hkIdqAlS_J#Nx!E}|RnRSkVm|Sa24|P&EF^Huxf&C8$As%)B literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/images/logo_small.png b/service-webapp/src/main/webapp/swagger-ui/images/logo_small.png new file mode 100755 index 0000000000000000000000000000000000000000..5496a65579ae903d4008f9d268fac422ef9d3679 GIT binary patch literal 770 zcmV+d1O5DoP)K11rQipnJ)eVnTSzHNF zN8ab&RhE5cC$$4FI-PZXx$pga@8yN)KS}L2Us~^y$(x-xioWbnFcV+~b9ig=!ft8Q z0RD+rpA8910Smyc0GviVUOPGiY6YM@-r6Nn8S&~cxHl27$l)-R$1(!Xx045RDy;_& zeXkG{;_#i9rz0B6149#Ddj=KM6MV^rTD%ylzGdCBX<^=^@I0X3SCR7OMbn}sUKdeF zKO-flaJa%@kJ27@Rod?J9=+Qx5|=PtG8n> zy~9rIu}+48M}FW5Bbqw3t#po?c?kmG!FX32W(dOjzTb+U@64MzHItoeB!M0Jcd}|E z>ekW`<~FjR_ZVVJkF|_htH&v!({Oad?xax?0K0sLwBY%nr46DpCmIIaa?@|Y&?n0q z@kJlMy`pE2HtEgASNd~xNzt$Kn7w#^Fy5oi`e$bUE*+f>Vk5z7=-2pj68afrqli$_ zvqe##5V?a)QU_-s9+s?mJYT5m`MQDRH4cYs^L1lCW;Dua5Ln9lG0BC@9DJQHA(}y&Z}$apb{kU zbezR}b^|O%6i+$BFsT3zqAe8wg9`vfiRp#{)z2bsJw`vBQL7Bt!IexM3$Hsf0tHK3 z+R=x{lR$K`s;7__?ASPW=3?*xgCpGaiadSEpoi0pw-_V#OXM8Ap{4qlG08x0ig9IY z3Ijqh(t1_=g#jocuqyJO=729e9OSiNDSrhR0Gc5G)(QGH?*IS*07*qoM6N<$f<~fU A82|tP literal 0 HcmV?d00001 diff --git a/service-webapp/src/main/webapp/swagger-ui/images/pet_store_api.png b/service-webapp/src/main/webapp/swagger-ui/images/pet_store_api.png new file mode 100755 index 0000000000000000000000000000000000000000..f9f9cd4aeb35a108c4b2f1dddb59977d56c595d8 GIT binary patch literal 824 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 z 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 + + + + Swagger UI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/en.js b/service-webapp/src/main/webapp/swagger-ui/lang/en.js new file mode 100755 index 000000000..918313665 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/en.js @@ -0,0 +1,56 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Warning: Deprecated", + "Implementation Notes":"Implementation Notes", + "Response Class":"Response Class", + "Status":"Status", + "Parameters":"Parameters", + "Parameter":"Parameter", + "Value":"Value", + "Description":"Description", + "Parameter Type":"Parameter Type", + "Data Type":"Data Type", + "Response Messages":"Response Messages", + "HTTP Status Code":"HTTP Status Code", + "Reason":"Reason", + "Response Model":"Response Model", + "Request URL":"Request URL", + "Response Body":"Response Body", + "Response Code":"Response Code", + "Response Headers":"Response Headers", + "Hide Response":"Hide Response", + "Headers":"Headers", + "Try it out!":"Try it out!", + "Show/Hide":"Show/Hide", + "List Operations":"List Operations", + "Expand Operations":"Expand Operations", + "Raw":"Raw", + "can't parse JSON. Raw result":"can't parse JSON. Raw result", + "Example Value":"Example Value", + "Model Schema":"Model Schema", + "Model":"Model", + "Click to set as parameter value":"Click to set as parameter value", + "apply":"apply", + "Username":"Username", + "Password":"Password", + "Terms of service":"Terms of service", + "Created by":"Created by", + "See more at":"See more at", + "Contact the developer":"Contact the developer", + "api version":"api version", + "Response Content Type":"Response Content Type", + "Parameter content type:":"Parameter content type:", + "fetching resource":"fetching resource", + "fetching resource list":"fetching resource list", + "Explore":"Explore", + "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.", + "Please specify the protocol for":"Please specify the protocol for", + "Can't read swagger JSON from":"Can't read swagger JSON from", + "Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI", + "Unable to read api":"Unable to read api", + "from path":"from path", + "server returned":"server returned" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/es.js b/service-webapp/src/main/webapp/swagger-ui/lang/es.js new file mode 100755 index 000000000..13fa015e6 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/es.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Advertencia: Obsoleto", + "Implementation Notes":"Notas de implementación", + "Response Class":"Clase de la Respuesta", + "Status":"Status", + "Parameters":"Parámetros", + "Parameter":"Parámetro", + "Value":"Valor", + "Description":"Descripción", + "Parameter Type":"Tipo del Parámetro", + "Data Type":"Tipo del Dato", + "Response Messages":"Mensajes de la Respuesta", + "HTTP Status Code":"Código de Status HTTP", + "Reason":"Razón", + "Response Model":"Modelo de la Respuesta", + "Request URL":"URL de la Solicitud", + "Response Body":"Cuerpo de la Respuesta", + "Response Code":"Código de la Respuesta", + "Response Headers":"Encabezados de la Respuesta", + "Hide Response":"Ocultar Respuesta", + "Try it out!":"Pruébalo!", + "Show/Hide":"Mostrar/Ocultar", + "List Operations":"Listar Operaciones", + "Expand Operations":"Expandir Operaciones", + "Raw":"Crudo", + "can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo", + "Example Value":"Valor de Ejemplo", + "Model Schema":"Esquema del Modelo", + "Model":"Modelo", + "apply":"aplicar", + "Username":"Nombre de usuario", + "Password":"Contraseña", + "Terms of service":"Términos de Servicio", + "Created by":"Creado por", + "See more at":"Ver más en", + "Contact the developer":"Contactar al desarrollador", + "api version":"versión de la api", + "Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta", + "fetching resource":"buscando recurso", + "fetching resource list":"buscando lista del recurso", + "Explore":"Explorar", + "Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.", + "Please specify the protocol for":"Por favor, especificar el protocola para", + "Can't read swagger JSON from":"No se puede leer el JSON de swagger desde", + "Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI", + "Unable to read api":"No se puede leer la api", + "from path":"desde ruta", + "server returned":"el servidor retornó" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/fr.js b/service-webapp/src/main/webapp/swagger-ui/lang/fr.js new file mode 100755 index 000000000..388dff14b --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/fr.js @@ -0,0 +1,54 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Avertissement : Obsolète", + "Implementation Notes":"Notes d'implémentation", + "Response Class":"Classe de la réponse", + "Status":"Statut", + "Parameters":"Paramètres", + "Parameter":"Paramètre", + "Value":"Valeur", + "Description":"Description", + "Parameter Type":"Type du paramètre", + "Data Type":"Type de données", + "Response Messages":"Messages de la réponse", + "HTTP Status Code":"Code de statut HTTP", + "Reason":"Raison", + "Response Model":"Modèle de réponse", + "Request URL":"URL appelée", + "Response Body":"Corps de la réponse", + "Response Code":"Code de la réponse", + "Response Headers":"En-têtes de la réponse", + "Hide Response":"Cacher la réponse", + "Headers":"En-têtes", + "Try it out!":"Testez !", + "Show/Hide":"Afficher/Masquer", + "List Operations":"Liste des opérations", + "Expand Operations":"Développer les opérations", + "Raw":"Brut", + "can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut", + "Example Value":"Exemple la valeur", + "Model Schema":"Définition du modèle", + "Model":"Modèle", + "apply":"appliquer", + "Username":"Nom d'utilisateur", + "Password":"Mot de passe", + "Terms of service":"Conditions de service", + "Created by":"Créé par", + "See more at":"Voir plus sur", + "Contact the developer":"Contacter le développeur", + "api version":"version de l'api", + "Response Content Type":"Content Type de la réponse", + "fetching resource":"récupération de la ressource", + "fetching resource list":"récupération de la liste de ressources", + "Explore":"Explorer", + "Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.", + "Please specify the protocol for":"Veuillez spécifier un protocole pour", + "Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de", + "Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI", + "Unable to read api":"Impossible de lire l'api", + "from path":"à partir du chemin", + "server returned":"réponse du serveur" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/geo.js b/service-webapp/src/main/webapp/swagger-ui/lang/geo.js new file mode 100755 index 000000000..609c20d9c --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/geo.js @@ -0,0 +1,56 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"ყურადღება: აღარ გამოიყენება", + "Implementation Notes":"იმპლემენტაციის აღწერა", + "Response Class":"რესპონს კლასი", + "Status":"სტატუსი", + "Parameters":"პარამეტრები", + "Parameter":"პარამეტრი", + "Value":"მნიშვნელობა", + "Description":"აღწერა", + "Parameter Type":"პარამეტრის ტიპი", + "Data Type":"მონაცემის ტიპი", + "Response Messages":"პასუხი", + "HTTP Status Code":"HTTP სტატუსი", + "Reason":"მიზეზი", + "Response Model":"რესპონს მოდელი", + "Request URL":"მოთხოვნის URL", + "Response Body":"პასუხის სხეული", + "Response Code":"პასუხის კოდი", + "Response Headers":"პასუხის ჰედერები", + "Hide Response":"დამალე პასუხი", + "Headers":"ჰედერები", + "Try it out!":"ცადე !", + "Show/Hide":"გამოჩენა/დამალვა", + "List Operations":"ოპერაციების სია", + "Expand Operations":"ოპერაციები ვრცლად", + "Raw":"ნედლი", + "can't parse JSON. Raw result":"JSON-ის დამუშავება ვერ მოხერხდა. ნედლი პასუხი", + "Example Value":"მაგალითი", + "Model Schema":"მოდელის სტრუქტურა", + "Model":"მოდელი", + "Click to set as parameter value":"პარამეტრისთვის მნიშვნელობის მისანიჭებლად, დააკლიკე", + "apply":"გამოყენება", + "Username":"მოხმარებელი", + "Password":"პაროლი", + "Terms of service":"მომსახურების პირობები", + "Created by":"შექმნა", + "See more at":"ნახე ვრცლად", + "Contact the developer":"დაუკავშირდი დეველოპერს", + "api version":"api ვერსია", + "Response Content Type":"პასუხის კონტენტის ტიპი", + "Parameter content type:":"პარამეტრის კონტენტის ტიპი:", + "fetching resource":"რესურსების მიღება", + "fetching resource list":"რესურსების სიის მიღება", + "Explore":"ნახვა", + "Show Swagger Petstore Example Apis":"ნახე Swagger Petstore სამაგალითო Api", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"სერვერთან დაკავშირება ვერ ხერხდება. შეამოწმეთ access-control-origin.", + "Please specify the protocol for":"მიუთითეთ პროტოკოლი", + "Can't read swagger JSON from":"swagger JSON წაკითხვა ვერ მოხერხდა", + "Finished Loading Resource Information. Rendering Swagger UI":"რესურსების ჩატვირთვა სრულდება. Swagger UI რენდერდება", + "Unable to read api":"api წაკითხვა ვერ მოხერხდა", + "from path":"მისამართიდან", + "server returned":"სერვერმა დააბრუნა" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/it.js b/service-webapp/src/main/webapp/swagger-ui/lang/it.js new file mode 100755 index 000000000..8529c2a90 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/it.js @@ -0,0 +1,52 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Attenzione: Deprecato", + "Implementation Notes":"Note di implementazione", + "Response Class":"Classe della risposta", + "Status":"Stato", + "Parameters":"Parametri", + "Parameter":"Parametro", + "Value":"Valore", + "Description":"Descrizione", + "Parameter Type":"Tipo di parametro", + "Data Type":"Tipo di dato", + "Response Messages":"Messaggi della risposta", + "HTTP Status Code":"Codice stato HTTP", + "Reason":"Motivo", + "Response Model":"Modello di risposta", + "Request URL":"URL della richiesta", + "Response Body":"Corpo della risposta", + "Response Code":"Oggetto della risposta", + "Response Headers":"Intestazioni della risposta", + "Hide Response":"Nascondi risposta", + "Try it out!":"Provalo!", + "Show/Hide":"Mostra/Nascondi", + "List Operations":"Mostra operazioni", + "Expand Operations":"Espandi operazioni", + "Raw":"Grezzo (raw)", + "can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).", + "Model Schema":"Schema del modello", + "Model":"Modello", + "apply":"applica", + "Username":"Nome utente", + "Password":"Password", + "Terms of service":"Condizioni del servizio", + "Created by":"Creato da", + "See more at":"Informazioni aggiuntive:", + "Contact the developer":"Contatta lo sviluppatore", + "api version":"versione api", + "Response Content Type":"Tipo di contenuto (content type) della risposta", + "fetching resource":"recuperando la risorsa", + "fetching resource list":"recuperando lista risorse", + "Explore":"Esplora", + "Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.", + "Please specify the protocol for":"Si prega di specificare il protocollo per", + "Can't read swagger JSON from":"Impossibile leggere JSON swagger da:", + "Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata", + "Unable to read api":"Impossibile leggere la api", + "from path":"da cartella", + "server returned":"il server ha restituito" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/ja.js b/service-webapp/src/main/webapp/swagger-ui/lang/ja.js new file mode 100755 index 000000000..3207bfc0b --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/ja.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"警告: 廃止予定", + "Implementation Notes":"実装メモ", + "Response Class":"レスポンスクラス", + "Status":"ステータス", + "Parameters":"パラメータ群", + "Parameter":"パラメータ", + "Value":"値", + "Description":"説明", + "Parameter Type":"パラメータタイプ", + "Data Type":"データタイプ", + "Response Messages":"レスポンスメッセージ", + "HTTP Status Code":"HTTPステータスコード", + "Reason":"理由", + "Response Model":"レスポンスモデル", + "Request URL":"リクエストURL", + "Response Body":"レスポンスボディ", + "Response Code":"レスポンスコード", + "Response Headers":"レスポンスヘッダ", + "Hide Response":"レスポンスを隠す", + "Headers":"ヘッダ", + "Try it out!":"実際に実行!", + "Show/Hide":"表示/非表示", + "List Operations":"操作一覧", + "Expand Operations":"操作の展開", + "Raw":"Raw", + "can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果", + "Model Schema":"モデルスキーマ", + "Model":"モデル", + "apply":"実行", + "Username":"ユーザ名", + "Password":"パスワード", + "Terms of service":"サービス利用規約", + "Created by":"Created by", + "See more at":"See more at", + "Contact the developer":"開発者に連絡", + "api version":"APIバージョン", + "Response Content Type":"レスポンス コンテンツタイプ", + "fetching resource":"リソースの取得", + "fetching resource list":"リソース一覧の取得", + "Explore":"Explore", + "Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.", + "Please specify the protocol for":"プロトコルを指定してください", + "Can't read swagger JSON from":"次からswagger JSONを読み込めません", + "Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています", + "Unable to read api":"APIを読み込めません", + "from path":"次のパスから", + "server returned":"サーバからの返答" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/ko-kr.js b/service-webapp/src/main/webapp/swagger-ui/lang/ko-kr.js new file mode 100755 index 000000000..03c7626d7 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/ko-kr.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"경고:폐기예정됨", + "Implementation Notes":"구현 노트", + "Response Class":"응답 클래스", + "Status":"상태", + "Parameters":"매개변수들", + "Parameter":"매개변수", + "Value":"값", + "Description":"설명", + "Parameter Type":"매개변수 타입", + "Data Type":"데이터 타입", + "Response Messages":"응답 메세지", + "HTTP Status Code":"HTTP 상태 코드", + "Reason":"원인", + "Response Model":"응답 모델", + "Request URL":"요청 URL", + "Response Body":"응답 본문", + "Response Code":"응답 코드", + "Response Headers":"응답 헤더", + "Hide Response":"응답 숨기기", + "Headers":"헤더", + "Try it out!":"써보기!", + "Show/Hide":"보이기/숨기기", + "List Operations":"목록 작업", + "Expand Operations":"전개 작업", + "Raw":"원본", + "can't parse JSON. Raw result":"JSON을 파싱할수 없음. 원본결과:", + "Model Schema":"모델 스키마", + "Model":"모델", + "apply":"적용", + "Username":"사용자 이름", + "Password":"암호", + "Terms of service":"이용약관", + "Created by":"작성자", + "See more at":"추가정보:", + "Contact the developer":"개발자에게 문의", + "api version":"api버전", + "Response Content Type":"응답Content Type", + "fetching resource":"리소스 가져오기", + "fetching resource list":"리소스 목록 가져오기", + "Explore":"탐색", + "Show Swagger Petstore Example Apis":"Swagger Petstore 예제 보기", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"서버로부터 읽어들일수 없습니다. access-control-origin 설정이 올바르지 않을수 있습니다.", + "Please specify the protocol for":"다음을 위한 프로토콜을 정하세요", + "Can't read swagger JSON from":"swagger JSON 을 다음으로 부터 읽을수 없습니다", + "Finished Loading Resource Information. Rendering Swagger UI":"리소스 정보 불러오기 완료. Swagger UI 랜더링", + "Unable to read api":"api를 읽을 수 없습니다.", + "from path":"다음 경로로 부터", + "server returned":"서버 응답함." +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/pl.js b/service-webapp/src/main/webapp/swagger-ui/lang/pl.js new file mode 100755 index 000000000..ce41e9179 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/pl.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Uwaga: Wycofane", + "Implementation Notes":"Uwagi Implementacji", + "Response Class":"Klasa Odpowiedzi", + "Status":"Status", + "Parameters":"Parametry", + "Parameter":"Parametr", + "Value":"Wartość", + "Description":"Opis", + "Parameter Type":"Typ Parametru", + "Data Type":"Typ Danych", + "Response Messages":"Wiadomości Odpowiedzi", + "HTTP Status Code":"Kod Statusu HTTP", + "Reason":"Przyczyna", + "Response Model":"Model Odpowiedzi", + "Request URL":"URL Wywołania", + "Response Body":"Treść Odpowiedzi", + "Response Code":"Kod Odpowiedzi", + "Response Headers":"Nagłówki Odpowiedzi", + "Hide Response":"Ukryj Odpowiedź", + "Headers":"Nagłówki", + "Try it out!":"Wypróbuj!", + "Show/Hide":"Pokaż/Ukryj", + "List Operations":"Lista Operacji", + "Expand Operations":"Rozwiń Operacje", + "Raw":"Nieprzetworzone", + "can't parse JSON. Raw result":"nie można przetworzyć pliku JSON. Nieprzetworzone dane", + "Model Schema":"Schemat Modelu", + "Model":"Model", + "apply":"użyj", + "Username":"Nazwa użytkownika", + "Password":"Hasło", + "Terms of service":"Warunki używania", + "Created by":"Utworzone przez", + "See more at":"Zobacz więcej na", + "Contact the developer":"Kontakt z deweloperem", + "api version":"wersja api", + "Response Content Type":"Typ Zasobu Odpowiedzi", + "fetching resource":"ładowanie zasobu", + "fetching resource list":"ładowanie listy zasobów", + "Explore":"Eksploruj", + "Show Swagger Petstore Example Apis":"Pokaż Przykładowe Api Swagger Petstore", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.", + "Please specify the protocol for":"Proszę podać protokół dla", + "Can't read swagger JSON from":"Nie można odczytać swagger JSON z", + "Finished Loading Resource Information. Rendering Swagger UI":"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI", + "Unable to read api":"Nie można odczytać api", + "from path":"ze ścieżki", + "server returned":"serwer zwrócił" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/pt.js b/service-webapp/src/main/webapp/swagger-ui/lang/pt.js new file mode 100755 index 000000000..f2e7c13d4 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/pt.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Aviso: Depreciado", + "Implementation Notes":"Notas de Implementação", + "Response Class":"Classe de resposta", + "Status":"Status", + "Parameters":"Parâmetros", + "Parameter":"Parâmetro", + "Value":"Valor", + "Description":"Descrição", + "Parameter Type":"Tipo de parâmetro", + "Data Type":"Tipo de dados", + "Response Messages":"Mensagens de resposta", + "HTTP Status Code":"Código de status HTTP", + "Reason":"Razão", + "Response Model":"Modelo resposta", + "Request URL":"URL requisição", + "Response Body":"Corpo da resposta", + "Response Code":"Código da resposta", + "Response Headers":"Cabeçalho da resposta", + "Headers":"Cabeçalhos", + "Hide Response":"Esconder resposta", + "Try it out!":"Tente agora!", + "Show/Hide":"Mostrar/Esconder", + "List Operations":"Listar operações", + "Expand Operations":"Expandir operações", + "Raw":"Cru", + "can't parse JSON. Raw result":"Falha ao analisar JSON. Resulto cru", + "Model Schema":"Modelo esquema", + "Model":"Modelo", + "apply":"Aplicar", + "Username":"Usuário", + "Password":"Senha", + "Terms of service":"Termos do serviço", + "Created by":"Criado por", + "See more at":"Veja mais em", + "Contact the developer":"Contate o desenvolvedor", + "api version":"Versão api", + "Response Content Type":"Tipo de conteúdo da resposta", + "fetching resource":"busca recurso", + "fetching resource list":"buscando lista de recursos", + "Explore":"Explorar", + "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin", + "Please specify the protocol for":"Por favor especifique o protocolo", + "Can't read swagger JSON from":"Não é possível ler o JSON Swagger de", + "Finished Loading Resource Information. Rendering Swagger UI":"Carregar informação de recurso finalizada. Renderizando Swagger UI", + "Unable to read api":"Não foi possível ler api", + "from path":"do caminho", + "server returned":"servidor retornou" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/ru.js b/service-webapp/src/main/webapp/swagger-ui/lang/ru.js new file mode 100755 index 000000000..592744e95 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/ru.js @@ -0,0 +1,56 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Предупреждение: Устарело", + "Implementation Notes":"Заметки", + "Response Class":"Пример ответа", + "Status":"Статус", + "Parameters":"Параметры", + "Parameter":"Параметр", + "Value":"Значение", + "Description":"Описание", + "Parameter Type":"Тип параметра", + "Data Type":"Тип данных", + "HTTP Status Code":"HTTP код", + "Reason":"Причина", + "Response Model":"Структура ответа", + "Request URL":"URL запроса", + "Response Body":"Тело ответа", + "Response Code":"HTTP код ответа", + "Response Headers":"Заголовки ответа", + "Hide Response":"Спрятать ответ", + "Headers":"Заголовки", + "Response Messages":"Что может прийти в ответ", + "Try it out!":"Попробовать!", + "Show/Hide":"Показать/Скрыть", + "List Operations":"Операции кратко", + "Expand Operations":"Операции подробно", + "Raw":"В сыром виде", + "can't parse JSON. Raw result":"Не удается распарсить ответ:", + "Example Value":"Пример", + "Model Schema":"Структура", + "Model":"Описание", + "Click to set as parameter value":"Нажмите, чтобы испльзовать в качестве значения параметра", + "apply":"применить", + "Username":"Имя пользователя", + "Password":"Пароль", + "Terms of service":"Условия использования", + "Created by":"Разработано", + "See more at":"Еще тут", + "Contact the developer":"Связаться с разработчиком", + "api version":"Версия API", + "Response Content Type":"Content Type ответа", + "Parameter content type:":"Content Type параметра:", + "fetching resource":"Получение ресурса", + "fetching resource list":"Получение ресурсов", + "Explore":"Показать", + "Show Swagger Petstore Example Apis":"Показать примеры АПИ", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа", + "Please specify the protocol for":"Пожалуйста, укажите протокол для", + "Can't read swagger JSON from":"Не получается прочитать swagger json из", + "Finished Loading Resource Information. Rendering Swagger UI":"Загрузка информации о ресурсах завершена. Рендерим", + "Unable to read api":"Не удалось прочитать api", + "from path":"по адресу", + "server returned":"сервер сказал" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/tr.js b/service-webapp/src/main/webapp/swagger-ui/lang/tr.js new file mode 100755 index 000000000..16426a9c3 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/tr.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"Uyarı: Deprecated", + "Implementation Notes":"Gerçekleştirim Notları", + "Response Class":"Dönen Sınıf", + "Status":"Statü", + "Parameters":"Parametreler", + "Parameter":"Parametre", + "Value":"Değer", + "Description":"Açıklama", + "Parameter Type":"Parametre Tipi", + "Data Type":"Veri Tipi", + "Response Messages":"Dönüş Mesajı", + "HTTP Status Code":"HTTP Statü Kodu", + "Reason":"Gerekçe", + "Response Model":"Dönüş Modeli", + "Request URL":"İstek URL", + "Response Body":"Dönüş İçeriği", + "Response Code":"Dönüş Kodu", + "Response Headers":"Dönüş Üst Bilgileri", + "Hide Response":"Dönüşü Gizle", + "Headers":"Üst Bilgiler", + "Try it out!":"Dene!", + "Show/Hide":"Göster/Gizle", + "List Operations":"Operasyonları Listele", + "Expand Operations":"Operasyonları Aç", + "Raw":"Ham", + "can't parse JSON. Raw result":"JSON çözümlenemiyor. Ham sonuç", + "Model Schema":"Model Şema", + "Model":"Model", + "apply":"uygula", + "Username":"Kullanıcı Adı", + "Password":"Parola", + "Terms of service":"Servis şartları", + "Created by":"Oluşturan", + "See more at":"Daha fazlası için", + "Contact the developer":"Geliştirici ile İletişime Geçin", + "api version":"api versiyon", + "Response Content Type":"Dönüş İçerik Tipi", + "fetching resource":"kaynak getiriliyor", + "fetching resource list":"kaynak listesi getiriliyor", + "Explore":"Keşfet", + "Show Swagger Petstore Example Apis":"Swagger Petstore Örnek Api'yi Gör", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.", + "Please specify the protocol for":"Lütfen istenen adres için protokol belirtiniz", + "Can't read swagger JSON from":"Swagger JSON bu kaynaktan okunamıyor", + "Finished Loading Resource Information. Rendering Swagger UI":"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor", + "Unable to read api":"api okunamadı", + "from path":"yoldan", + "server returned":"sunucuya dönüldü" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/translator.js b/service-webapp/src/main/webapp/swagger-ui/lang/translator.js new file mode 100755 index 000000000..ffb879f9a --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/translator.js @@ -0,0 +1,39 @@ +'use strict'; + +/** + * Translator for documentation pages. + * + * To enable translation you should include one of language-files in your index.html + * after . + * For example - + * + * If you wish to translate some new texts you should do two things: + * 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too. + * 2. Mark that text it templates this way New Phrase or . + * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate. + * + */ +window.SwaggerTranslator = { + + _words:[], + + translate: function(sel) { + var $this = this; + sel = sel || '[data-sw-translate]'; + + $(sel).each(function() { + $(this).html($this._tryTranslate($(this).html())); + + $(this).val($this._tryTranslate($(this).val())); + $(this).attr('title', $this._tryTranslate($(this).attr('title'))); + }); + }, + + _tryTranslate: function(word) { + return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; + }, + + learn: function(wordsMap) { + this._words = wordsMap; + } +}; diff --git a/service-webapp/src/main/webapp/swagger-ui/lang/zh-cn.js b/service-webapp/src/main/webapp/swagger-ui/lang/zh-cn.js new file mode 100755 index 000000000..570319ba1 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lang/zh-cn.js @@ -0,0 +1,53 @@ +'use strict'; + +/* jshint quotmark: double */ +window.SwaggerTranslator.learn({ + "Warning: Deprecated":"警告:已过时", + "Implementation Notes":"实现备注", + "Response Class":"响应类", + "Status":"状态", + "Parameters":"参数", + "Parameter":"参数", + "Value":"值", + "Description":"描述", + "Parameter Type":"参数类型", + "Data Type":"数据类型", + "Response Messages":"响应消息", + "HTTP Status Code":"HTTP状态码", + "Reason":"原因", + "Response Model":"响应模型", + "Request URL":"请求URL", + "Response Body":"响应体", + "Response Code":"响应码", + "Response Headers":"响应头", + "Hide Response":"隐藏响应", + "Headers":"头", + "Try it out!":"试一下!", + "Show/Hide":"显示/隐藏", + "List Operations":"显示操作", + "Expand Operations":"展开操作", + "Raw":"原始", + "can't parse JSON. Raw result":"无法解析JSON. 原始结果", + "Model Schema":"模型架构", + "Model":"模型", + "apply":"应用", + "Username":"用户名", + "Password":"密码", + "Terms of service":"服务条款", + "Created by":"创建者", + "See more at":"查看更多:", + "Contact the developer":"联系开发者", + "api version":"api版本", + "Response Content Type":"响应Content Type", + "fetching resource":"正在获取资源", + "fetching resource list":"正在获取资源列表", + "Explore":"浏览", + "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", + "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", + "Please specify the protocol for":"请指定协议:", + "Can't read swagger JSON from":"无法读取swagger JSON于", + "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", + "Unable to read api":"无法读取api", + "from path":"从路径", + "server returned":"服务器返回" +}); diff --git a/service-webapp/src/main/webapp/swagger-ui/lib/backbone-min.js b/service-webapp/src/main/webapp/swagger-ui/lib/backbone-min.js new file mode 100755 index 000000000..a3f544be6 --- /dev/null +++ b/service-webapp/src/main/webapp/swagger-ui/lib/backbone-min.js @@ -0,0 +1,15 @@ +// Backbone.js 1.1.2 + +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('