You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by up...@apache.org on 2019/03/27 23:47:14 UTC

[geode] branch develop updated: GEODE-6557: Moving geode-web classes to geode-web

This is an automated email from the ASF dual-hosted git repository.

upthewaterspout pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new afb3a4f   GEODE-6557: Moving geode-web classes to geode-web
afb3a4f is described below

commit afb3a4fe093673ef4c166dceb8bb9f3122fe0a62
Author: Dan Smith <ds...@pivotal.io>
AuthorDate: Wed Mar 27 16:46:50 2019 -0700

     GEODE-6557: Moving geode-web classes to geode-web
    
    Moving the classes in geode-core that really should be in geode-web.
    This removes optional dependencies from geode-core and puts these
    classes where their tests are.
    
    As part of this change, SerializableObjectHttpMessageConverter is now
    duplicated in geode-web as ServerSerializableObjectHttpMessageConverter.
    This class was previously put in both the geode-web.war file and the
    gfsh-dependencies by creating a separate jar with all of the geode-web
    classes as part of the geode-core build.
    
    This class is used by both gfsh and geode-web. But geode-web needs its
    own copy so that the class is loaded by the geode-web war classloader in
    order to ensure the class is linked with the spring-web classes coming
    from the geode-web classloader.
---
 geode-assembly/build.gradle                        |  5 --
 .../integrationTest/resources/assembly_content.txt |  1 -
 geode-core/build.gradle                            | 23 +----
 ...lizableObjectHttpMessageConverterJUnitTest.java |  0
 geode-pulse/build.gradle                           |  2 -
 geode-web/build.gradle                             | 93 +++++++-------------
 .../ShellCommandsControllerProcessCommandTest.java |  0
 .../controllers/AbstractCommandsController.java    |  0
 .../web/controllers/ShellCommandsController.java   |  0
 .../support/LoginHandlerInterceptor.java           |  0
 ...rverSerializableObjectHttpMessageConverter.java | 99 ++++++++++++++++++++++
 .../internal/web/io/MultipartFileAdapter.java      |  0
 .../web/io/MultipartFileResourceAdapter.java       |  0
 .../management/internal/web/util/UriUtils.java     |  0
 .../src/main/webapp/WEB-INF/geode-mgmt-servlet.xml |  2 +-
 geode-web/src/test/resources/expected-pom.xml      | 63 +++++---------
 16 files changed, 152 insertions(+), 136 deletions(-)

diff --git a/geode-assembly/build.gradle b/geode-assembly/build.gradle
index fe99b6f..972eaea 100755
--- a/geode-assembly/build.gradle
+++ b/geode-assembly/build.gradle
@@ -334,9 +334,6 @@ task configureGfshDepsJar(dependsOn: configurations.gfshDependencies.dependencie
   inputs.files {
     configureDepsJar
   }
-  inputs.files {
-    project(':geode-core').webJar
-  }
 
   def output = project.buildDir.toPath().resolve('reports').resolve('gfsh_deps_jar_cp.txt')
   outputs.file {
@@ -344,7 +341,6 @@ task configureGfshDepsJar(dependsOn: configurations.gfshDependencies.dependencie
   }
   doLast {
     def classpath = configureDepsJar.outputs.files.singleFile.text + ' ' +
-        project(':geode-core').webJar.archiveName + ' ' +
         configurations.gfshDependencies.collect { it.getName() }.flatten().join(' ')
     output.write(classpath)
   }
@@ -502,7 +498,6 @@ distributions {
 
         //These tasks are included as closures (wrapped in {}) because gradle may evaluate
         //this CopySpec before it evaluates the geode-core build file.
-        from { project(':geode-core').webJar }
         from { project(':geode-core').raJar }
         from { project(':geode-core').jcaJar }
 
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index b613d12..9f284c5 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -913,7 +913,6 @@ lib/geode-protobuf-0.0.0.jar
 lib/geode-protobuf-messages-0.0.0.jar
 lib/geode-rebalancer-0.0.0.jar
 lib/geode-wan-0.0.0.jar
-lib/geode-web-0.0.0.jar
 lib/gfsh-dependencies.jar
 lib/grumpy-core-0.2.2.jar
 lib/httpclient-4.5.6.jar
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index db20f2a..74b95a3 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -112,7 +112,6 @@ jar {
   from sourceSets.main.output
   from sourceSets.jca.output
 
-  exclude 'org/apache/geode/management/internal/web/**'
   exclude 'org/apache/geode/internal/i18n/StringIdResourceBundle_ja.txt'
   exclude 'org/apache/geode/admin/doc-files/ds4_0.dtd'
 
@@ -125,13 +124,6 @@ jar {
 
 jar.dependsOn(createVersionPropertiesFile)
 
-task webJar(type: Jar, dependsOn: classes) {
-  description 'Assembles the jar archive containing the gemfire management web classes.'
-  from sourceSets.main.output
-  baseName 'geode-web'
-  include 'org/apache/geode/management/internal/web/**'
-}
-
 task raJar(type: Jar, dependsOn: classes) {
   description 'Assembles the jar archive that contains the JCA classes'
   from sourceSets.jca.output
@@ -300,19 +292,8 @@ dependencies {
   implementation('org.eclipse.jetty:jetty-server')
 
 
-  //Spring webmvc is used by the REST management API
-  compileOnly('org.springframework:spring-webmvc') {
-    exclude module: 'aopalliance'
-    exclude module: 'spring-aop'
-    ext.optional = true
-  }
-  //Spring webmvc is used by the REST management API
-  testCompile('org.springframework:spring-webmvc') {
-    exclude module: 'aopalliance'
-    exclude module: 'spring-aop'
-    ext.optional = true
-  }
-  //Spring core is used by the REST API, and the gfsh cli
+
+  //Spring core is used by the the gfsh cli
   implementation('org.springframework:spring-core') {
     ext.optional = true
   }
diff --git a/geode-web/src/test/java/org/apache/geode/management/internal/web/http/converter/SerializableObjectHttpMessageConverterJUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/web/http/converter/SerializableObjectHttpMessageConverterJUnitTest.java
similarity index 100%
rename from geode-web/src/test/java/org/apache/geode/management/internal/web/http/converter/SerializableObjectHttpMessageConverterJUnitTest.java
rename to geode-core/src/test/java/org/apache/geode/management/internal/web/http/converter/SerializableObjectHttpMessageConverterJUnitTest.java
diff --git a/geode-pulse/build.gradle b/geode-pulse/build.gradle
index aab5709..5cd6760 100644
--- a/geode-pulse/build.gradle
+++ b/geode-pulse/build.gradle
@@ -160,9 +160,7 @@ artifacts {
 war {
   duplicatesStrategy = DuplicatesStrategy.EXCLUDE
   classpath configurations.runtimeClasspath
-  classpath project(':geode-core').webJar.archivePath
 }
-war.dependsOn(':geode-core:webJar')
 uiTest.dependsOn war
 uiTest.finalizedBy rootProject.combineReports
 rootProject.combineReports.mustRunAfter uiTest
diff --git a/geode-web/build.gradle b/geode-web/build.gradle
index c24aa6a..0f430e6 100644
--- a/geode-web/build.gradle
+++ b/geode-web/build.gradle
@@ -21,77 +21,45 @@ apply from: "${project.projectDir}/../gradle/publish.gradle"
 
 
 dependencies {
-  compile(platform(project(':boms:geode-all-bom')))
-  compileOnly('javax.servlet:javax.servlet-api')
-  testCompile('javax.servlet:javax.servlet-api')
-  compileOnly(project(path: ':geode-core', configuration: 'classesOutput'))
+  api(platform(project(':boms:geode-all-bom')))
 
-  runtimeOnly('org.springframework:spring-aspects') {
-    exclude module: 'aspectjweaver'
-    exclude module: 'aopalliance'
-    exclude module: 'spring-core'
-  }
-  runtimeOnly('org.springframework:spring-oxm') {
-    exclude module: 'commons-logging'
-    exclude module: 'spring-beans'
-    exclude module: 'spring-core'
-  }
-  runtimeOnly('org.springframework:spring-webmvc') {
-    exclude module: 'aopalliance'
-    exclude module: 'aspectjweaver'
-    exclude module: 'spring-core'
-  }
-  runtimeOnly('commons-fileupload:commons-fileupload') {
-    exclude module: 'commons-io'
+  //Note - marking these deps as providedCompile means that we will exclude them and all of their
+  //transitive dependencies from geode-web.war. Since geode-web.war is ony run within
+  //a container that has geode-core on the classpath, is prevents duplicating jars. geode-core
+  //in particular should not be duplicated in the war.
+  providedCompile(project(path: ':geode-core')) {
+    //spring-web is excluded from the providedCompile configuration to ensure
+    //that it is included as part of the war. spring-web must be loaded with the war's
+    //classloader to interoperate correctly with it's optional dependences that are included
+    //in this war, in particular commons-fileupload.
+    exclude module: 'spring-web'
   }
+  providedCompile(platform(project(':boms:geode-all-bom')))
+  providedCompile('javax.servlet:javax.servlet-api')
+  providedCompile('org.apache.logging.log4j:log4j-api')
   
-  testCompile(project(':geode-core'))
-  testCompile(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  // have to use output since we exclude the dependent classes from jar :(
-  testCompile(project(path: ':geode-core', configuration: 'classesOutput'))
-  testCompile(project(':geode-junit')) {
-    exclude module: 'geode-core'
-  }
-  testCompile('org.springframework:spring-test')
-  testCompile('org.springframework:spring-webmvc') {
-    exclude module: 'aopalliance'
-    exclude module: 'spring-aop'
-  }
+  implementation('org.springframework:spring-webmvc')
 
-
-  integrationTestCompile(project(':geode-dunit'));
-
-  integrationTestRuntime(files(war.destinationDir))
-  integrationTestRuntime('org.springframework:spring-webmvc') {
-    exclude module: 'aopalliance'
-    exclude module: 'spring-aop'
+  runtimeOnly('org.springframework:spring-aspects') {
+    exclude module: 'aspectjweaver'
   }
+  runtimeOnly('org.springframework:spring-oxm')
+  runtimeOnly('commons-fileupload:commons-fileupload')
 
+  testImplementation(project(':geode-junit'))
+  testImplementation('org.springframework:spring-test')
 
-  distributedTestCompile(project(':geode-common'))
-  distributedTestCompile(project(':geode-dunit'))
-  distributedTestCompile('pl.pragmatists:JUnitParams')
+  integrationTestImplementation(project(':geode-dunit'));
 
-  distributedTestRuntime(files(war.destinationDir))
-  distributedTestRuntime('org.springframework:spring-webmvc') {
-    exclude module: 'aopalliance'
-    exclude module: 'spring-aop'
-  }
+  integrationTestRuntimeOnly(files(war.destinationDir))
 
-  upgradeTestCompile(project(':geode-dunit')) {
-    exclude module: 'geode-core'
-  }
-}
+  distributedTestImplementation(project(':geode-common'))
+  distributedTestImplementation(project(':geode-dunit'))
+  distributedTestImplementation('pl.pragmatists:JUnitParams')
+
+  distributedTestRuntimeOnly(files(war.destinationDir))
 
-//Remove the gradle output directories from the eclipse classpath. These
-//are added by the classesOutput configuration, above. Unfortunately, using
-//minusConfigurations does not work here, because it removes the entire
-//geode-core project
-eclipse.classpath.file {
-  whenMerged { classpath ->
-    classpath.entries.removeAll { entry -> entry.path.contains('geode-core/build') }
+  upgradeTestImplementation(project(':geode-dunit')) {
   }
 }
 
@@ -99,8 +67,5 @@ distributedTest.dependsOn(war)
 integrationTest.dependsOn(war)
 
 war {
-  dependsOn ':geode-core:webJar'
   duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-  classpath configurations.runtimeClasspath
-  classpath project(':geode-core').webJar.archivePath
 }
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/web/controllers/ShellCommandsControllerProcessCommandTest.java b/geode-web/src/integrationTest/java/org/apache/geode/management/internal/web/controllers/ShellCommandsControllerProcessCommandTest.java
similarity index 100%
rename from geode-core/src/integrationTest/java/org/apache/geode/management/internal/web/controllers/ShellCommandsControllerProcessCommandTest.java
rename to geode-web/src/integrationTest/java/org/apache/geode/management/internal/web/controllers/ShellCommandsControllerProcessCommandTest.java
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java
similarity index 100%
rename from geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java
rename to geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/AbstractCommandsController.java
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java
similarity index 100%
rename from geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java
rename to geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/ShellCommandsController.java
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/support/LoginHandlerInterceptor.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/support/LoginHandlerInterceptor.java
similarity index 100%
rename from geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/support/LoginHandlerInterceptor.java
rename to geode-web/src/main/java/org/apache/geode/management/internal/web/controllers/support/LoginHandlerInterceptor.java
diff --git a/geode-web/src/main/java/org/apache/geode/management/internal/web/http/converter/ServerSerializableObjectHttpMessageConverter.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/http/converter/ServerSerializableObjectHttpMessageConverter.java
new file mode 100644
index 0000000..bf3b574
--- /dev/null
+++ b/geode-web/src/main/java/org/apache/geode/management/internal/web/http/converter/ServerSerializableObjectHttpMessageConverter.java
@@ -0,0 +1,99 @@
+/*
+ * 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.geode.management.internal.web.http.converter;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.AbstractHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.util.StreamUtils;
+
+import org.apache.geode.internal.util.IOUtils;
+
+/**
+ * The ServerSerializableObjectHttpMessageConverter class is a Spring HttpMessageConverter for
+ * converting
+ * bytes streams to/from Serializable Objects.
+ *
+ * This class is the same as {@link ServerSerializableObjectHttpMessageConverter}. However, to
+ * avoid classloader issues due to the fact that geode-web.war needs to use this converter
+ * as well as geode-core (because the converter is used by gfsh), this class is duplicated in
+ * geode-web. This ensures that the version used by the war will implement
+ * {@link HttpMessageConverter}
+ * from the geode-web war's classloader rather than geode-core's classloader and interoperate
+ * with other classes loaded in the geode-web war's classloader.
+ * <p/>
+ *
+ * @see Serializable
+ * @see HttpInputMessage
+ * @see HttpMessage
+ * @see HttpOutputMessage
+ * @see MediaType
+ * @see AbstractHttpMessageConverter
+ * @since GemFire 8.0
+ */
+@SuppressWarnings("unused")
+public class ServerSerializableObjectHttpMessageConverter
+    extends AbstractHttpMessageConverter<Serializable> {
+
+  public ServerSerializableObjectHttpMessageConverter() {
+    super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL);
+  }
+
+  @Override
+  protected boolean supports(final Class<?> type) {
+    if (logger.isTraceEnabled()) {
+      logger.trace(String.format("%1$s.supports(%2$s)", getClass().getName(), type.getName()),
+          new Throwable());
+    }
+
+    return (type != null && Serializable.class.isAssignableFrom(type));
+  }
+
+  @Override
+  protected Serializable readInternal(final Class<? extends Serializable> type,
+      final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
+    try {
+      ClassLoader classLoader = type.getClassLoader();
+      return type.cast(IOUtils.deserializeObject(IOUtils.toByteArray(inputMessage.getBody()),
+          classLoader != null ? classLoader : getClass().getClassLoader()));
+    } catch (ClassNotFoundException e) {
+      throw new HttpMessageNotReadableException(
+          String.format("Unable to convert the HTTP message body into an Object of type (%1$s)",
+              type.getName()),
+          e);
+    }
+  }
+
+  protected void setContentLength(final HttpMessage message, final byte[] messageBody) {
+    message.getHeaders().setContentLength(messageBody.length);
+  }
+
+  @Override
+  protected void writeInternal(final Serializable serializableObject,
+      final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
+    final byte[] messageBody = IOUtils.serializeObject(serializableObject);
+    setContentLength(outputMessage, messageBody);
+    StreamUtils.copy(messageBody, outputMessage.getBody());
+  }
+
+}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileAdapter.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileAdapter.java
similarity index 100%
rename from geode-core/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileAdapter.java
rename to geode-web/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileAdapter.java
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileResourceAdapter.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileResourceAdapter.java
similarity index 100%
rename from geode-core/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileResourceAdapter.java
rename to geode-web/src/main/java/org/apache/geode/management/internal/web/io/MultipartFileResourceAdapter.java
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/util/UriUtils.java b/geode-web/src/main/java/org/apache/geode/management/internal/web/util/UriUtils.java
similarity index 100%
rename from geode-core/src/main/java/org/apache/geode/management/internal/web/util/UriUtils.java
rename to geode-web/src/main/java/org/apache/geode/management/internal/web/util/UriUtils.java
diff --git a/geode-web/src/main/webapp/WEB-INF/geode-mgmt-servlet.xml b/geode-web/src/main/webapp/WEB-INF/geode-mgmt-servlet.xml
index a2a06b6..cd1c796 100644
--- a/geode-web/src/main/webapp/WEB-INF/geode-mgmt-servlet.xml
+++ b/geode-web/src/main/webapp/WEB-INF/geode-mgmt-servlet.xml
@@ -48,7 +48,7 @@ limitations under the License.
       <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
       <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
       <bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
-      <bean class="org.apache.geode.management.internal.web.http.converter.SerializableObjectHttpMessageConverter"/>
+      <bean class="org.apache.geode.management.internal.web.http.converter.ServerSerializableObjectHttpMessageConverter"/>
     </mvc:message-converters>
   </mvc:annotation-driven>
 
diff --git a/geode-web/src/test/resources/expected-pom.xml b/geode-web/src/test/resources/expected-pom.xml
index 3595675..cf144ea 100644
--- a/geode-web/src/test/resources/expected-pom.xml
+++ b/geode-web/src/test/resources/expected-pom.xml
@@ -47,72 +47,51 @@
   </dependencyManagement>
   <dependencies>
     <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-aspects</artifactId>
-      <scope>runtime</scope>
+      <groupId>org.apache.geode</groupId>
+      <artifactId>geode-core</artifactId>
+      <scope>compile</scope>
       <exclusions>
         <exclusion>
-          <artifactId>spring-core</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-        <exclusion>
-          <artifactId>aopalliance</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-        <exclusion>
-          <artifactId>aspectjweaver</artifactId>
+          <artifactId>spring-web</artifactId>
           <groupId>*</groupId>
         </exclusion>
       </exclusions>
     </dependency>
     <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
       <groupId>org.springframework</groupId>
-      <artifactId>spring-oxm</artifactId>
+      <artifactId>spring-webmvc</artifactId>
       <scope>runtime</scope>
-      <exclusions>
-        <exclusion>
-          <artifactId>spring-beans</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-        <exclusion>
-          <artifactId>spring-core</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-        <exclusion>
-          <artifactId>commons-logging</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-      </exclusions>
     </dependency>
     <dependency>
       <groupId>org.springframework</groupId>
-      <artifactId>spring-webmvc</artifactId>
+      <artifactId>spring-aspects</artifactId>
       <scope>runtime</scope>
       <exclusions>
         <exclusion>
-          <artifactId>spring-core</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-        <exclusion>
-          <artifactId>aopalliance</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-        <exclusion>
           <artifactId>aspectjweaver</artifactId>
           <groupId>*</groupId>
         </exclusion>
       </exclusions>
     </dependency>
     <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-oxm</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
       <groupId>commons-fileupload</groupId>
       <artifactId>commons-fileupload</artifactId>
       <scope>runtime</scope>
-      <exclusions>
-        <exclusion>
-          <artifactId>commons-io</artifactId>
-          <groupId>*</groupId>
-        </exclusion>
-      </exclusions>
     </dependency>
   </dependencies>
 </project>