DATAREST-1552 - Removed deprecated HAL Browser module.

This commit is contained in:
Oliver Drotbohm
2020-08-11 16:44:17 +02:00
parent c73a1c1576
commit be2fa72904
9 changed files with 1 additions and 759 deletions

View File

@@ -21,7 +21,6 @@
<module>spring-data-rest-core</module>
<module>spring-data-rest-webmvc</module>
<module>spring-data-rest-distribution</module>
<module>spring-data-rest-hal-browser</module>
<module>spring-data-rest-hal-explorer</module>
</modules>
@@ -77,7 +76,7 @@
<module>spring-data-rest-core</module>
<module>spring-data-rest-webmvc</module>
<module>spring-data-rest-distribution</module>
<module>spring-data-rest-hal-browser</module>
<module>spring-data-rest-hal-explorer</module>
<module>spring-data-rest-tests</module>
</modules>

View File

@@ -1,191 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-parent</artifactId>
<version>3.4.0-SNAPSHOT</version>
</parent>
<artifactId>spring-data-rest-hal-browser</artifactId>
<name>Spring Data REST - HAL Browser</name>
<properties>
<browser.version>ad9b865</browser.version>
<json-editor.version>0.7.21</json-editor.version>
<java-module-name>spring.data.rest.hal.browser</java-module-name>
<!--
Skip Javadoc as there's no public code to document
and this causes the Javadoc generation to fail otherwise.
-->
<maven.javadoc.skip>true</maven.javadoc.skip>
<project.root>${basedir}/..</project.root>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>hal-browser</artifactId>
<version>${browser.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>json-editor</artifactId>
<version>${json-editor.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>empty-javadoc-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>javadoc</classifier>
<classesDirectory>${basedir}/javadoc</classesDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-hal-browser</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.webjars</groupId>
<artifactId>hal-browser</artifactId>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/hal-browser</outputDirectory>
</configuration>
</execution>
<execution>
<id>unpack-json-editor</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.webjars</groupId>
<artifactId>json-editor</artifactId>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/json-editor</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<copy todir="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser">
<fileset dir="${project.build.directory}/hal-browser/META-INF/resources/webjars/hal-browser/${browser.version}" />
</copy>
<copy todir="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser/vendor/js">
<fileset dir="${project.build.directory}/json-editor/META-INF/resources/webjars/json-editor/${json-editor.version}" />
</copy>
<copy file="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser/browser.html"
tofile="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser/index.html" />
<replace file="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser/index.html">
<replacefilter>
<replacetoken><![CDATA[</body>]]></replacetoken>
<replacevalue><![CDATA[
<script id="dynamic-request-template" type="text/template">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Create/Update</h3>
</div>
<form class="non-safe" action="<%= href %>">
<div class="modal-body" style="padding-top: 0px">
<div id="jsoneditor"></div>
<div class="well well-small" style="padding-bottom: 0px;">
<div class="container-fluid">
<div class="row-fluid">
<div class="control-group">
<label class="control-label" style="display: inline-block; font-weight: bold;">Action:</label>
<input name="method" type="text" class="method controls" style="width: 98%" value="POST" />
<input name="url" type="text" class="url controls" style="width: 98%" value="<%= href %>" />
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Make Request</button>
</div>
</form>
</script>
<script src="vendor/js/jsoneditor.js"></script>
<script src="js/CustomPostForm.js"></script>
</body>]]>
</replacevalue>
</replacefilter>
</replace>
<replace file="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser/index.html">
<replacefilter>
<replacetoken>The HAL Browser</replacetoken>
<replacevalue>The HAL Browser (for Spring Data REST)</replacevalue>
</replacefilter>
</replace>
<delete file="${project.build.outputDirectory}/META-INF/spring-data-rest/hal-browser/browser.html" />
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2015-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.rest.webmvc.halbrowser;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponents;
/**
* Controller with a few convenience redirects to expose the HAL browser shipped as static content.
*
* @author Oliver Gierke
* @soundtrack Miles Davis - So what (Kind of blue)
*/
@Slf4j
@BasePathAwareController
class HalBrowser {
static final String BROWSER = "/browser";
static final String INDEX = "/index.html";
HalBrowser() {
LOG.warn("---");
LOG.warn(
"Spring Data REST HAL Browser is deprecated! Prefer the HAL Explorer (artifactId: spring-data-rest-hal-explorer)!");
LOG.warn("---");
}
/**
* Redirects requests to the API root asking for HTML to the HAL browser.
*
* @return
*/
@GetMapping(path = { "/", "" }, produces = MediaType.TEXT_HTML_VALUE)
View index(HttpServletRequest request) {
return getRedirectView(request, false);
}
/**
* Redirects to the actual {@code index.html}.
*
* @return
*/
@GetMapping(path = BROWSER)
View browser(HttpServletRequest request) {
return getRedirectView(request, request.getRequestURI().endsWith(BROWSER));
}
/**
* Returns the View to redirect to to access the HAL browser.
*
* @param request must not be {@literal null}.
* @param browserRelative
* @return
*/
private View getRedirectView(HttpServletRequest request, boolean browserRelative) {
ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequest(request);
UriComponents components = builder.build();
String path = components.getPath() == null ? "" : components.getPath();
if (!browserRelative) {
builder.path(BROWSER);
}
builder.path(INDEX);
builder.fragment(browserRelative ? path.substring(0, path.lastIndexOf(BROWSER)) : path);
return new RedirectView(builder.build().toUriString());
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.rest.webmvc.halbrowser;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.StaticResourceProvider;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
/**
* {@link StaticResourceProvider} to expose the HAL Browser WebJar content via a static resource route.
*
* @author Oliver Drotbohm
* @since 3.2
* @soundtrack Tedeschi Trucks Band - Signs, High Times (Signs)
*/
class HalBrowserConfiguration implements StaticResourceProvider {
/*
* (non-Javadoc)
* @see org.springframework.data.rest.webmvc.config.StaticResourceProvider#customizeResources(org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry, org.springframework.data.rest.core.config.RepositoryRestConfiguration)
*/
@Override
public void customizeResources(ResourceHandlerRegistry registry, RepositoryRestConfiguration configuration) {
String basePath = configuration.getBasePath().toString().concat(HalBrowser.BROWSER);
String rootLocation = "classpath:META-INF/spring-data-rest/hal-browser/";
registry.addResourceHandler(basePath.concat("/**")).addResourceLocations(rootLocation);
}
}

View File

@@ -1,203 +0,0 @@
/**
* Custom Backbone view that uses JSON Schema metadata to create pop-up dialog with actual field names instead of
* asking user to input raw JSON.
*
* NOTE: Because JSON Schema lists all properties, including those that are links, they have to be filtered out.
* Links have to be set via a PUT operation with the proper media type.
*
* @author Greg Turnquist
* @author Gregory Frank
* @since 2.4
* @see DATAREST-627, DATAREST-1077
*/
/* jshint strict: true */
/* globals HAL, Backbone, _, $, window, jqxhr */
'use strict';
var CustomPostForm = Backbone.View.extend({
initialize: function (opts) {
this.href = opts.href.split('{')[0];
this.vent = opts.vent;
_.bindAll(this, 'createNewResource');
},
events: {
'submit form': 'createNewResource'
},
className: 'modal fade',
/**
* Perform a POST/PUT operation on the resource.
*
* @param e
*/
createNewResource: function (e) {
e.preventDefault();
var self = this;
var opts = {
url: this.$('.url').val(),
headers: _.defaults({'Content-Type': 'application/json'}, HAL.client.getHeaders()),
method: this.$('.method').val(),
data: this.getNewResourceData()
};
HAL.client.request(opts).done(function (response) {
self.vent.trigger('response', {resource: response, jqxhr: jqxhr});
}).fail(function (e) {
self.vent.trigger('fail-response', {jqxhr: jqxhr});
}).always(function (e) {
self.vent.trigger('response-headers', {jqxhr: jqxhr});
window.location.hash = 'NON-GET:' + opts.url;
});
this.$el.modal('hide');
},
/**
* Draw the dialog after fetching the resource's JSON Schema metadata. If no metadata is available, use the
* fallback editor.
*
* @param opts
* @returns {CustomPostForm}
*/
render: function (opts) {
var self = this;
try {
HAL.client.request({
method: 'HEAD',
headers: HAL.client.getHeaders(),
url: this.href
}).done(function (message, text, jqXHR) {
self.$el.html(self.template({href: self.href}));
try {
var hal = self.w3cLinksToHalLinks(jqXHR.getResponseHeader('Link'));
HAL.client.request({
method: 'GET',
url: hal._links.profile.href,
headers: _.defaults({'Accept': 'application/schema+json'}, HAL.client.getHeaders())
}).done(function (schema) {
self.loadJsonEditor(schema);
});
} catch (e) {
self.loadFallbackEditor();
}
self.$el.modal();
});
} catch (e) {
self.loadFallbackEditor();
self.$el.modal();
}
return this;
},
/**
* Load the JSON Schema-driven editor.
*
* @see https://github.com/jdorn/json-editor
*/
loadJsonEditor: function (schema) {
var self = this;
/**
* Remove URI-based fields since this dialog doesn't handle relationships.
*/
Object.keys(schema.properties).forEach(function (property) {
if (schema.properties[property].hasOwnProperty('format') &&
schema.properties[property].format === 'uri') {
delete schema.properties[property];
}
});
/**
* See https://github.com/jdorn/json-editor#options for more customizing options.
*/
this.editor = new window.JSONEditor(this.$('#jsoneditor')[0], {
theme: 'bootstrap2',
schema: schema,
disable_collapse: true,
disable_edit_json: true,
disable_properties: true
});
this.getNewResourceData = function() {
return JSON.stringify(self.editor.getValue());
}
},
/**
* Load fallback editor that doesn't depend on any form of metadata.
*/
loadFallbackEditor: function () {
var editor = this.$('#jsoneditor');
editor.append($('<h4>' + this.href + '</h4>'));
var inputBox = $('<textarea name="body" class="body" style="height: 200px">{\n}</textarea>');
editor.append(inputBox);
this.getNewResourceData = function() {
return inputBox.val();
}
},
/**
* Convert a W3C link header into a HAL-based set of _links.
*
* e.g.
* <http://localhost:8080/persons>; rel="persons",<http://localhost:8080/profile/persons>; rel="profile"
* to
* {
* _links: {
* persons: {
* href: http://localhost:8080/persons
* },
* profile: {
* href: http://localhost:8080/profile/persons
* }
* }
* }
*
* @param linkHeader - HTTP Response header containing a list of W3C compliant links with rels.
* @see https://www.w3.org/wiki/LinkHeader
*/
w3cLinksToHalLinks: function (linkHeader) {
var w3cLinks = linkHeader.split(',');
var halLinks = {_links: {}};
w3cLinks.forEach(function (w3cLink) {
var parts = w3cLink.split(';');
var hrefWrappedWithBrackets = parts[0];
var href = hrefWrappedWithBrackets.slice(1, parts[0].length - 1);
var w3cRel = parts[1];
var relWrappedWithQuotes = w3cRel.split('=')[1];
var rel = relWrappedWithQuotes.slice(1, relWrappedWithQuotes.length - 1);
halLinks._links[rel] = { "href": href };
});
return halLinks;
},
/**
* Look up the HTML template.
*/
template: _.template($('#dynamic-request-template').html())
});
/**
* Inject the form into the HAL Browser.
*/
HAL.customPostForm = CustomPostForm;

View File

@@ -1 +0,0 @@
org.springframework.data.rest.webmvc.config.StaticResourceProvider=org.springframework.data.rest.webmvc.halbrowser.HalBrowserConfiguration

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2015-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.rest.webmvc.halbrowser;
import static org.hamcrest.CoreMatchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* Integration tests for {@link HalBrowser}.
*
* @author Oliver Gierke
* @soundtrack Miles Davis - Blue in green (Kind of blue)
*/
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class HalBrowserIntegrationTests {
static final String BASE_PATH = "/api";
static final String BROWSER_INDEX = "/browser/index.html";
static final String TARGET = BASE_PATH.concat(BROWSER_INDEX).concat("#").concat(BASE_PATH);
@Configuration
@EnableWebMvc
static class TestConfiguration extends RepositoryRestMvcConfiguration {
public TestConfiguration(ApplicationContext context, ObjectFactory<ConversionService> conversionService) {
super(context, conversionService);
}
@Bean
RepositoryRestConfigurer configExtension() {
return RepositoryRestConfigurer.withConfig(config -> config.setBasePath(BASE_PATH));
}
}
@Autowired WebApplicationContext context;
MockMvc mvc;
@Before
public void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).//
defaultRequest(get(BASE_PATH).accept(MediaType.TEXT_HTML)).build();
}
@Test // DATAREST-293
public void exposesJsonUnderApiRootByDefault() throws Exception {
mvc.perform(get(BASE_PATH).accept(MediaType.ALL)).//
andExpect(status().isOk()).//
andExpect(header().string(HttpHeaders.CONTENT_TYPE, startsWith(MediaTypes.HAL_JSON.toString())));
}
@Test // DATAREST-293
public void redirectsToBrowserForApiRootAndHtml() throws Exception {
mvc.perform(get(BASE_PATH).accept(MediaType.TEXT_HTML)).//
andExpect(status().isFound()).//
andExpect(header().string(HttpHeaders.LOCATION, endsWith(TARGET)));
}
@Test // DATAREST-293
public void forwardsBrowserToIndexHtml() throws Exception {
mvc.perform(get(BASE_PATH.concat("/browser"))).//
andExpect(status().isFound()).//
andExpect(header().string(HttpHeaders.LOCATION, endsWith(TARGET)));
}
@Test // DATAREST-293
public void exposesHalBrowser() throws Exception {
mvc.perform(get(BASE_PATH.concat("/browser/index.html"))).//
andExpect(status().isOk()).//
andExpect(content().string(containsString("The HAL Browser")));
}
@Test // DATAREST-293
public void retrunsApiIfHtmlIsNotExplicitlyListed() throws Exception {
mvc.perform(get(BASE_PATH).accept(MediaType.APPLICATION_JSON, MediaType.ALL)).//
andExpect(status().isOk()).//
andExpect(header().string(HttpHeaders.CONTENT_TYPE, startsWith(MediaType.APPLICATION_JSON_VALUE)));
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright 2015-2020 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.rest.webmvc.halbrowser;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;
import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.filter.ForwardedHeaderFilter;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractView;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Unit tests for {@link HalBrowser}.
*
* @author Oliver Gierke
* @author Mark Paluch
* @soundtrack Nils Wülker - Homeless Diamond (feat. Lauren Flynn)
*/
public class HalBrowserUnitTests {
@Test // DATAREST-565, DATAREST-720
public void createsContextRelativeRedirectForBrowser() throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/context");
request.setContextPath("/context");
View view = new HalBrowser().browser(request);
assertThat(view).isInstanceOf(RedirectView.class);
((AbstractView) view).render(Collections.<String, Object> emptyMap(), request, response);
UriComponents components = UriComponentsBuilder.fromUriString(response.getHeader(HttpHeaders.LOCATION)).build();
assertThat(components.getPath()).startsWith("/context");
assertThat(components.getFragment()).isEqualTo("/context");
}
@Test // DATAREST-1264
public void producesProxyRelativeRedirectIfNecessary() throws ServletException, IOException {
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/browser");
request.addHeader("X-Forwarded-Host", "somehost");
request.addHeader("X-Forwarded-Port", "4711");
request.addHeader("X-Forwarded-Proto", "https");
request.addHeader("X-Forwarded-Prefix", "/prefix");
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
filter.doFilter(request, response, (req, resp) -> {
View view = new HalBrowser().browser((HttpServletRequest) req);
assertThat(view).isInstanceOf(RedirectView.class);
String url = ((RedirectView) view).getUrl();
assertThat(url).startsWith("https://somehost:4711/prefix").endsWith("/prefix");
});
}
}

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="warn" />
<root level="warn">
<appender-ref ref="console" />
</root>
</configuration>