Hi,

Is it possible to ensure meecrowave.junit.classloaderlock.off is true by
default - at least for junit5, don't think anybody will start using junit4
now.
Not having the default classloader has several impacts in some apps and you
likely want to ensure you run with apploader for 100% of apps today.

it is also a bug and antipattern to use that with mono flavor so minimum is
to not enable it by default IMHO.

Romain Manni-Bucau
@rmannibucau <https://twitter.com/rmannibucau> |  Blog
<https://rmannibucau.metawerx.net/> | Old Blog
<http://rmannibucau.wordpress.com> | Github <https://github.com/rmannibucau> |
LinkedIn <https://www.linkedin.com/in/rmannibucau> | Book
<https://www.packtpub.com/application-development/java-ee-8-high-performance>


---------- Forwarded message ---------
De : <strub...@apache.org>
Date: lun. 21 oct. 2024 à 17:36
Subject: (openwebbeans-meecrowave) branch main updated: MEECROWAVE-342 fix
junit5 test setup
To: comm...@openwebbeans.apache.org <comm...@openwebbeans.apache.org>


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

struberg pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/openwebbeans-meecrowave.git


The following commit(s) were added to refs/heads/main by this push:
     new 7eb9fee  MEECROWAVE-342 fix junit5 test setup
7eb9fee is described below

commit 7eb9fee055ad4861e8478a7982172813902d80d2
Author: Mark Struberg <strub...@apache.org>
AuthorDate: Mon Oct 21 17:34:21 2024 +0200

    MEECROWAVE-342 fix junit5 test setup

    Now tests will use a 'fresh' ClassLoader for each non-mono test setup.
    That way it's possible to run them in parallel.
    I've also added a few tests and did split the surefire runs into 2
parts.
    One for running the junit4 related tests, the other one for the junit 5
tests.
---
 meecrowave-core/pom.xml                            |   4 +
 meecrowave-junit/pom.xml                           |  38 ++++++
 .../apache/meecrowave/junit5/BaseLifecycle.java    |  19 ++-
 .../meecrowave/junit5/MeecrowaveExtension.java     | 144
+++++++++++++++------
 ...est.java => PerClass2MeecrowaveConfigTest.java} |  72 ++++-------
 .../junit5/PerClassMeecrowaveConfigTest.java       |  36 ++++++
 .../org/apache/meecrowave/junit5/ScopesTest.java   |   2 +-
 .../junit5/bean/SomeCommonInterface.java           |  24 ++++
 meecrowave-specs-api/pom.xml                       |   3 +-
 9 files changed, 255 insertions(+), 87 deletions(-)

diff --git a/meecrowave-core/pom.xml b/meecrowave-core/pom.xml
index 2a6591b..6b7a02d 100644
--- a/meecrowave-core/pom.xml
+++ b/meecrowave-core/pom.xml
@@ -434,8 +434,10 @@
               <shadedClassifierName>runner</shadedClassifierName>
               <shadedArtifactAttached>true</shadedArtifactAttached>

 <createDependencyReducedPom>false</createDependencyReducedPom>
+<!--

 <dependencyReducedPomLocation>${project.build.directory}/reduced-pom-bundle.xml
               </dependencyReducedPomLocation>
+-->
               <transformers>
                 <transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                   <mainClass>org.apache.meecrowave.runner.Cli</mainClass>
@@ -487,6 +489,7 @@
             <configuration>
               <shadedClassifierName>runner-light</shadedClassifierName>
               <shadedArtifactAttached>true</shadedArtifactAttached>
+
<createDependencyReducedPom>false</createDependencyReducedPom>

 
<dependencyReducedPomLocation>${project.build.directory}/reduced-pom-bundle-light.xml
               </dependencyReducedPomLocation>
               <transformers>
@@ -553,6 +556,7 @@
             <configuration>
               <shadedClassifierName>servlet</shadedClassifierName>
               <shadedArtifactAttached>true</shadedArtifactAttached>
+
<createDependencyReducedPom>false</createDependencyReducedPom>

 
<dependencyReducedPomLocation>${project.build.directory}/reduced-pom-bundle-servlet.xml
               </dependencyReducedPomLocation>
               <transformers>
diff --git a/meecrowave-junit/pom.xml b/meecrowave-junit/pom.xml
index 758375a..b9caf30 100644
--- a/meecrowave-junit/pom.xml
+++ b/meecrowave-junit/pom.xml
@@ -81,6 +81,44 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <parallel>all</parallel>
+          <threadCount>4</threadCount>
+          <threadCountClasses>3</threadCountClasses>
+        </configuration>
+
+        <executions>
+          <execution>
+            <!-- disable default execution -->
+            <id>default-test</id>
+            <configuration>
+              <skip>true</skip>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>mwjunit4</id>
+            <phase>test</phase>
+            <goals><goal>test</goal></goals>
+            <configuration>
+              <includes>
+                <inlude>org/apache/meecrowave/junit/*Test.class</inlude>
+              </includes>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>mwjunit5</id>
+            <phase>test</phase>
+            <goals><goal>test</goal></goals>
+            <configuration>
+              <includes>
+                <inlude>org/apache/meecrowave/junit5/*Test.class</inlude>
+              </includes>
+            </configuration>
+          </execution>
+        </executions>
+
         <dependencies>
           <dependency>
             <groupId>org.junit.platform</groupId>
diff --git
a/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/BaseLifecycle.java
b/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/BaseLifecycle.java
index 0a07140..c9ab009 100644
---
a/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/BaseLifecycle.java
+++
b/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/BaseLifecycle.java
@@ -22,10 +22,12 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.util.stream.Stream;

+import org.apache.meecrowave.internal.ClassLoaderLock;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.extension.ExtensionContext;

 abstract class BaseLifecycle {
+
     boolean isPerClass(final ExtensionContext context) {
         return context.getTestInstanceLifecycle()
                 .map(it -> it.equals(TestInstance.Lifecycle.PER_CLASS))
@@ -41,7 +43,7 @@ abstract class BaseLifecycle {
                 .orElse(state);
     }

-    private static LifecyleState invoke(final Object test, final Class<?
extends Annotation> marker) {
+    private LifecyleState invoke(final Object test, final Class<? extends
Annotation> marker) {
         Class<?> type = test.getClass();
         while (type != Object.class) {
             Stream.of(type.getDeclaredMethods())
@@ -63,7 +65,19 @@ abstract class BaseLifecycle {
         return new LifecyleState(true, test);
     }

-    static class LifecyleState {
+
+    protected void doUnlockContext(final boolean unlocked) {
+        if (!unlocked) {
+            ClassLoaderLock.LOCK.unlock();
+        }
+    }
+
+    protected void doLockContext() {
+        ClassLoaderLock.LOCK.lock();
+    }
+
+
+    class LifecyleState {
         private final boolean injected;
         private final Object instance;

@@ -76,4 +90,5 @@ abstract class BaseLifecycle {
             invoke(instance, AfterLastTest.class);
         }
     }
+
 }
diff --git
a/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/MeecrowaveExtension.java
b/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/MeecrowaveExtension.java
index fe4dd87..a5f1be3 100644
---
a/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/MeecrowaveExtension.java
+++
b/meecrowave-junit/src/main/java/org/apache/meecrowave/junit5/MeecrowaveExtension.java
@@ -20,6 +20,7 @@ package org.apache.meecrowave.junit5;

 import org.apache.meecrowave.Meecrowave;
 import org.apache.meecrowave.configuration.Configuration;
+import org.apache.meecrowave.internal.ClassLoaderLock;
 import org.apache.meecrowave.testing.Injector;
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.AfterEachCallback;
@@ -42,6 +43,10 @@ public class MeecrowaveExtension extends BaseLifecycle

     private static final ExtensionContext.Namespace NAMESPACE =
ExtensionContext.Namespace.create(MeecrowaveExtension.class.getName());

+    private ClassLoader meecrowaveCL;
+    private ClassLoader oldCl;
+
+
     private final ScopesExtension scopes = new ScopesExtension() {
         @Override
         protected Optional<Class<? extends Annotation>[]> getScopes(final
ExtensionContext context) {
@@ -52,6 +57,13 @@ public class MeecrowaveExtension extends BaseLifecycle
         }
     };

+    protected ClassLoader createMwClassLoader() {
+        if (meecrowaveCL == null) {
+            meecrowaveCL = ClassLoaderLock.getUsableContainerLoader();
+        }
+        return meecrowaveCL;
+    }
+
     @Override
     public void beforeAll(final ExtensionContext context) {
         if (isPerClass(context)) {
@@ -65,7 +77,20 @@ public class MeecrowaveExtension extends BaseLifecycle
         ofNullable(store.get(LifecyleState.class, LifecyleState.class))
                 .ifPresent(s -> s.afterLastTest(context));
         if (isPerClass(context)) {
-            store.get(Meecrowave.class, Meecrowave.class).close();
+            ClassLoader oldClTmp = null;
+            try {
+                if (meecrowaveCL != null) {
+                    oldClTmp =
Thread.currentThread().getContextClassLoader();
+
Thread.currentThread().setContextClassLoader(meecrowaveCL);
+                }
+
+                store.get(Meecrowave.class, Meecrowave.class).close();
+            }
+            finally {
+                if (oldClTmp != null) {
+                    Thread.currentThread().setContextClassLoader(oldClTmp);
+                }
+            }
         }
     }

@@ -74,63 +99,104 @@ public class MeecrowaveExtension extends BaseLifecycle
         if (!isPerClass(context)) {
             doStart(context);
         }
+
+        if (meecrowaveCL != null) {
+            oldCl = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(meecrowaveCL);
+        }
     }

     @Override
     public void afterEach(final ExtensionContext context) {
-        if (!isPerClass(context)) {
-            doRelease(context);
-            context.getStore(NAMESPACE).get(Meecrowave.class,
Meecrowave.class).close();
+        ClassLoader oldClTmp = null;
+        try {
+            if (!isPerClass(context)) {
+                if (meecrowaveCL != null) {
+                    oldClTmp =
Thread.currentThread().getContextClassLoader();
+
Thread.currentThread().setContextClassLoader(meecrowaveCL);
+                }
+                doRelease(context);
+                context.getStore(NAMESPACE).get(Meecrowave.class,
Meecrowave.class).close();
+            }
+        }
+        finally {
+            if (oldCl != null) {
+                Thread.currentThread().setContextClassLoader(oldCl);
+            }
+            else if (oldClTmp != null) {
+                Thread.currentThread().setContextClassLoader(oldClTmp);
+            }
         }
     }

     private void doStart(final ExtensionContext context) {
-        final Meecrowave.Builder builder = new Meecrowave.Builder();
-        final MeecrowaveConfig config = findConfig(context);
-        final String ctx;
-        if (config != null) {
-            ctx = config.context();
-
-            for (final Method method :
MeecrowaveConfig.class.getMethods()) {
-                if (MeecrowaveConfig.class != method.getDeclaringClass()) {
-                    continue;
-                }
+        final Thread thread = Thread.currentThread();
+        ClassLoader oldClTmp = thread.getContextClassLoader();
+        boolean unlocked = false;
+        doLockContext();
+
+        try {
+            ClassLoader newCl = createMwClassLoader();
+            if (newCl != null) {
+                thread.setContextClassLoader(newCl);
+            }

-                try {
-                    final Object value = method.invoke(config);
+            final Meecrowave.Builder builder = new Meecrowave.Builder();
+            final MeecrowaveConfig config = findConfig(context);
+            final String ctx;
+            if (config != null) {
+                ctx = config.context();

-                    final Field configField =
Configuration.class.getDeclaredField(method.getName());
-                    if (!configField.isAccessible()) {
-                        configField.setAccessible(true);
+                for (final Method method :
MeecrowaveConfig.class.getMethods()) {
+                    if (MeecrowaveConfig.class !=
method.getDeclaringClass()) {
+                        continue;
                     }

-                    if (value != null && (!String.class.isInstance(value)
|| !value.toString().isEmpty())) {
+                    try {
+                        final Object value = method.invoke(config);
+
+                        final Field configField =
Configuration.class.getDeclaredField(method.getName());
                         if (!configField.isAccessible()) {
                             configField.setAccessible(true);
                         }
-                        configField.set(builder, File.class ==
configField.getType() ? /*we use string instead */new
File(value.toString()) : value);
+
+                        if (value != null &&
(!String.class.isInstance(value) || !value.toString().isEmpty())) {
+                            if (!configField.isAccessible()) {
+                                configField.setAccessible(true);
+                            }
+                            configField.set(builder, File.class ==
configField.getType() ? /*we use string instead */new
File(value.toString()) : value);
+                        }
+                    }
+                    catch (final NoSuchFieldException nsfe) {
+                        // ignored
+                    }
+                    catch (final Exception e) {
+                        throw new IllegalStateException(e);
                     }
-                } catch (final NoSuchFieldException nsfe) {
-                    // ignored
-                } catch (final Exception e) {
-                    throw new IllegalStateException(e);
                 }
-            }

-            if (builder.getHttpPort() < 0) {
-                builder.randomHttpPort();
+                if (builder.getHttpPort() < 0) {
+                    builder.randomHttpPort();
+                }
+            }
+            else {
+                ctx = "";
+            }
+            final ExtensionContext.Store store =
context.getStore(NAMESPACE);
+            final Meecrowave meecrowave = new Meecrowave(builder);
+            store.put(Meecrowave.class, meecrowave);
+            store.put(Meecrowave.Builder.class, builder);
+            meecrowave.bake(ctx);
+
+            doInject(context);
+            store.put(LifecyleState.class, onInjection(context, null));
+        }
+        finally {
+            doUnlockContext(unlocked);
+            if (oldClTmp != null) {
+                thread.setContextClassLoader(oldClTmp);
             }
-        } else {
-            ctx = "";
         }
-        final ExtensionContext.Store store = context.getStore(NAMESPACE);
-        final Meecrowave meecrowave = new Meecrowave(builder);
-        store.put(Meecrowave.class, meecrowave);
-        store.put(Meecrowave.Builder.class, builder);
-        meecrowave.bake(ctx);
-
-        doInject(context);
-        store.put(LifecyleState.class, onInjection(context, null));
     }

     private MeecrowaveConfig findConfig(final ExtensionContext context) {
@@ -144,7 +210,7 @@ public class MeecrowaveExtension extends BaseLifecycle
     private void doRelease(final ExtensionContext context) {

 ofNullable(context.getStore(NAMESPACE).get(CreationalContext.class,
CreationalContext.class))
                 .ifPresent(CreationalContext::release);
-        scopes.beforeEach(context);
+        scopes.afterEach(context);
     }

     private void doInject(final ExtensionContext context) {
diff --git
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClass2MeecrowaveConfigTest.java
similarity index 58%
copy from
meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
copy to
meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClass2MeecrowaveConfigTest.java
index a2d38ce..610bebb 100644
---
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
+++
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClass2MeecrowaveConfigTest.java
@@ -18,75 +18,59 @@
  */
 package org.apache.meecrowave.junit5;

-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;

 import org.apache.meecrowave.Meecrowave;
-import org.apache.meecrowave.io.IO;
+import org.apache.meecrowave.junit5.bean.SomeCommonInterface;
 import org.apache.meecrowave.testing.ConfigurationInject;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;

+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
+
 @TestInstance(PER_CLASS)
-@MeecrowaveConfig(scanningPackageIncludes =
"org.apache.meecrowave.junit5.PerClassMeecrowaveConfigTest")
-class PerClassMeecrowaveConfigTest {
+@MeecrowaveConfig(scanningPackageIncludes =
"org.apache.meecrowave.junit5.PerClass2MeecrowaveConfigTest")
+class PerClass2MeecrowaveConfigTest {
     @ConfigurationInject
     private Meecrowave.Builder config;

-    @Inject
-    private Bean bean;

-    private static Bean instance;
+    private @Inject SomeCommonInterface bigOtherOracle;

     @Test
-    void m1() {
-        doTest();
+    public void testBeanPickup() throws Exception {
+        assertEquals(43, bigOtherOracle.meaningOfLife());
+        Thread.sleep(50L);
     }

     @Test
-    void m2() {
-        doTest();
+    public void testBeanPickup2() throws Exception {
+        assertEquals(43, bigOtherOracle.meaningOfLife());
+        Thread.sleep(50L);
     }

-    private void doTest() {
-        if (instance == null) {
-            first();
-        } else {
-            second();
-        }
+    @Test
+    public void testBeanPickup3() throws Exception {
+        assertEquals(43, bigOtherOracle.meaningOfLife());
+        Thread.sleep(50L);
     }

-    private void first() {
-        assertEquals("ok", bean.get());
-        instance = bean;
+    @Test
+    public void testBeanPickup4() throws Exception {
+        assertEquals(43, bigOtherOracle.meaningOfLife());
+        Thread.sleep(50L);
     }

-    private void second() {
-        assertSame(instance, bean);
-    }

-    private String slurp(final URL url) {
-        try (final InputStream is = url.openStream()) {
-            return IO.toString(is);
-        } catch (final IOException e) {
-            fail(e.getMessage());
-        }
-        return null;
-    }

     @ApplicationScoped
-    public static class Bean {
-        String get() {
-            return "ok";
+    public static class MyCommonImpl2 implements SomeCommonInterface {
+
+        @Override
+        public int meaningOfLife() {
+            return 43;
         }
     }
 }
diff --git
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
index a2d38ce..65806ea 100644
---
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
+++
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/PerClassMeecrowaveConfigTest.java
@@ -32,6 +32,7 @@ import jakarta.inject.Inject;

 import org.apache.meecrowave.Meecrowave;
 import org.apache.meecrowave.io.IO;
+import org.apache.meecrowave.junit5.bean.SomeCommonInterface;
 import org.apache.meecrowave.testing.ConfigurationInject;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
@@ -45,6 +46,8 @@ class PerClassMeecrowaveConfigTest {
     @Inject
     private Bean bean;

+    private @Inject SomeCommonInterface bigOracle;
+
     private static Bean instance;

     @Test
@@ -57,6 +60,30 @@ class PerClassMeecrowaveConfigTest {
         doTest();
     }

+    @Test
+    public void testBeanPickup() throws Exception {
+        assertEquals(42, bigOracle.meaningOfLife());
+        Thread.sleep(500L);
+    }
+
+    @Test
+    public void testBeanPickup2() throws Exception {
+        assertEquals(42, bigOracle.meaningOfLife());
+        Thread.sleep(50L);
+    }
+
+    @Test
+    public void testBeanPickup3() throws Exception {
+        assertEquals(42, bigOracle.meaningOfLife());
+        Thread.sleep(50L);
+    }
+
+    @Test
+    public void testBeanPickup4() throws Exception {
+        assertEquals(42, bigOracle.meaningOfLife());
+        Thread.sleep(50L);
+    }
+
     private void doTest() {
         if (instance == null) {
             first();
@@ -89,4 +116,13 @@ class PerClassMeecrowaveConfigTest {
             return "ok";
         }
     }
+
+    @ApplicationScoped
+    public static class MyCommonImpl1 implements SomeCommonInterface {
+
+        @Override
+        public int meaningOfLife() {
+            return 42;
+        }
+    }
 }
diff --git
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/ScopesTest.java
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/ScopesTest.java
index 1b1c9f4..0b84387 100644
---
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/ScopesTest.java
+++
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/ScopesTest.java
@@ -27,7 +27,7 @@ import jakarta.enterprise.context.spi.Context;
 import org.apache.webbeans.config.WebBeansContext;
 import org.junit.jupiter.api.Test;

-@MeecrowaveConfig
+@MonoMeecrowaveConfig
 @Scopes(scopes = RequestScoped.class)
 class ScopesTest {
     @Test
diff --git
a/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/bean/SomeCommonInterface.java
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/bean/SomeCommonInterface.java
new file mode 100644
index 0000000..aafe3fa
--- /dev/null
+++
b/meecrowave-junit/src/test/java/org/apache/meecrowave/junit5/bean/SomeCommonInterface.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.meecrowave.junit5.bean;
+
+/**
+ * Common interface to check whether Beans are really only picked up per
class
+ */
+public interface SomeCommonInterface {
+    int meaningOfLife();
+}
diff --git a/meecrowave-specs-api/pom.xml b/meecrowave-specs-api/pom.xml
index 473fb49..1874aa5 100644
--- a/meecrowave-specs-api/pom.xml
+++ b/meecrowave-specs-api/pom.xml
@@ -18,12 +18,13 @@
     under the License.
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0"; xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"; xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
   <parent>
     <artifactId>meecrowave</artifactId>
     <groupId>org.apache.meecrowave</groupId>
     <version>2.0.0-SNAPSHOT</version>
   </parent>
-  <modelVersion>4.0.0</modelVersion>

   <artifactId>meecrowave-specs-api</artifactId>
   <name>Meecrowave :: Specs API</name>

Reply via email to