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

joerghoh pushed a commit to branch SLING-11924-prevent-rr-serialization
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-jacksonexporter.git

commit e47008dd1af6fcfcfd47648e6d0e7365092b30d7
Author: Joerg Hoh <[email protected]>
AuthorDate: Sun Jul 2 15:06:19 2023 +0200

    SLING-11924 disallow the serialization of a ResourceResolver
---
 pom.xml                                            |  10 +-
 .../impl/IgnoringResourceResolverMixin.java        |  65 +++++
 .../impl/ResourceResolverModuleProvider.java       |  63 +++++
 .../impl/WarningResourceResolverMixin.java         |  73 +++++
 .../JacksonExporterLimitSerializationTest.java     | 311 +++++++++++++++++++++
 .../impl/example/PojoWithResourceResolver.java     |  36 +++
 .../jacksonexporter/impl/util/LogCapture.java      |  69 +++++
 7 files changed, 625 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 64872f9..1285634 100644
--- a/pom.xml
+++ b/pom.xml
@@ -152,10 +152,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.testing.logging-mock</artifactId>
-            <version>2.0.0</version>
+            <artifactId>org.apache.sling.testing.sling-mock.junit5</artifactId>
+            <version>3.2.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+           <groupId>ch.qos.logback</groupId>
+           <artifactId>logback-classic</artifactId>
+           <version>1.2.3</version>
+           <scope>test</scope>
+       </dependency>
 
     </dependencies>
 
diff --git 
a/src/main/java/org/apache/sling/models/jacksonexporter/impl/IgnoringResourceResolverMixin.java
 
b/src/main/java/org/apache/sling/models/jacksonexporter/impl/IgnoringResourceResolverMixin.java
new file mode 100644
index 0000000..c7f56e4
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/models/jacksonexporter/impl/IgnoringResourceResolverMixin.java
@@ -0,0 +1,65 @@
+/*
+ * 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.sling.models.jacksonexporter.impl;
+
+import java.io.IOException;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreType;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+/**
+ * This mixin disables the serialization of the ResourceResolver; if it's 
still requested, a ERROR log message is written.
+ *
+ */
+
+@JsonIgnoreType
+public abstract interface IgnoringResourceResolverMixin extends 
ResourceResolver {
+    
+    public static final String MESSAGE = "The serialization of a 
ResourceResolver was rejected, because the JacksonExporter servlet "
+            + "is configured to do so. Please review your Sling Model 
implementation class(es) and remove any public reference to a "
+            + "ResourceResolver."; 
+
+    public static final Logger LOG = 
LoggerFactory.getLogger(JacksonExporter.class);
+    
+    
+    @JsonSerialize(using=JustLoggingSerializer.class)
+    @Override
+    boolean isLive();
+    
+
+    
+    public class JustLoggingSerializer extends JsonSerializer<Boolean> {
+
+        @Override
+        public void serialize(Boolean value, JsonGenerator jgen, 
SerializerProvider provider)
+                throws IOException, JsonProcessingException {
+            LOG.warn(MESSAGE);
+        }
+        
+    }
+    
+}
diff --git 
a/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceResolverModuleProvider.java
 
b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceResolverModuleProvider.java
new file mode 100644
index 0000000..63dd4d6
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/models/jacksonexporter/impl/ResourceResolverModuleProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.sling.models.jacksonexporter.impl;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.jacksonexporter.ModuleProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+@Component(service = ModuleProvider.class)
+@Designate(ocd = ResourceResolverModuleProvider.Config.class)
+public class ResourceResolverModuleProvider implements ModuleProvider {
+    
+    @ObjectClassDefinition(name = "Apache Sling Models Jackson Exporter - 
ResourceResolver support",
+            description = "Provider of a Jackson Module which enables/disables 
support for serializing a ResourceResolver")
+    static @interface Config {
+
+        @AttributeDefinition(name ="disable serialization of the 
ResourceResolver",
+                description = "if enabled, ResourceResolver instances are not 
longer deserialized as JSON")
+        boolean disable_serialization() default false;
+
+    }
+    
+    SimpleModule moduleInstance;
+    
+    
+    @Activate
+    private void activate(Config config) {
+        this.moduleInstance = new SimpleModule();
+        if (config.disable_serialization()) {
+            moduleInstance.setMixInAnnotation(ResourceResolver.class, 
IgnoringResourceResolverMixin.class);
+        } else {
+            moduleInstance.setMixInAnnotation(ResourceResolver.class, 
WarningResourceResolverMixin.class);
+        }
+    }
+    
+
+    @Override
+    public Module getModule() {
+        return moduleInstance;
+    }
+
+}
diff --git 
a/src/main/java/org/apache/sling/models/jacksonexporter/impl/WarningResourceResolverMixin.java
 
b/src/main/java/org/apache/sling/models/jacksonexporter/impl/WarningResourceResolverMixin.java
new file mode 100644
index 0000000..3a6cc2c
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/models/jacksonexporter/impl/WarningResourceResolverMixin.java
@@ -0,0 +1,73 @@
+/*
+ * 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.sling.models.jacksonexporter.impl;
+
+import java.io.IOException;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+
+/**
+ * This mixin exports data which Jackson would export by default (so not 
change in the default behaviour),
+ * but prints a warning whenever it does that.
+ */
+
+
+@JsonAutoDetect
+public interface WarningResourceResolverMixin extends ResourceResolver {
+    
+    public static final String MESSAGE = "A ResourceResolver is serialized 
with all its private fields containing "
+            + "implementation details you should not disclose. Please review 
your Sling Model implementation(s) and remove "
+            + "all public accessors to a ResourceResolver.";
+    
+    public static final Logger LOG = 
LoggerFactory.getLogger(JacksonExporter.class);
+    
+    
+
+    // This method is explicitly mentioned so we provide a custom serializer 
which prints the warning
+    
+    @Override
+    @JsonSerialize(using=WarningBooleanSerializer.class)
+    boolean isLive();
+    
+    
+    
+    public class WarningBooleanSerializer extends JsonSerializer<Boolean> {
+
+        @Override
+        public void serialize(Boolean value, JsonGenerator jgen, 
SerializerProvider provider)
+                throws IOException, JsonProcessingException {
+            LOG.warn(MESSAGE);
+            jgen.writeObject(value);
+            
+        }
+        
+    }
+    
+    
+}
diff --git 
a/src/test/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporterLimitSerializationTest.java
 
b/src/test/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporterLimitSerializationTest.java
new file mode 100644
index 0000000..fff0582
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/models/jacksonexporter/impl/JacksonExporterLimitSerializationTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.sling.models.jacksonexporter.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.factory.ExportException;
+import 
org.apache.sling.models.jacksonexporter.impl.example.PojoWithResourceResolver;
+import org.apache.sling.models.jacksonexporter.impl.util.LogCapture;
+import org.apache.sling.testing.mock.osgi.junit5.OsgiContext;
+import org.apache.sling.testing.mock.osgi.junit5.OsgiContextExtension;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import ch.qos.logback.classic.Level;
+
+@ExtendWith(OsgiContextExtension.class)
+public class JacksonExporterLimitSerializationTest {
+
+    private OsgiContext context = new OsgiContext();
+    
+    
+    
+    @Test
+    public void testWarnLogWhenSerializingResourceResolver() throws 
ExportException {
+        
+        LogCapture capture = new 
LogCapture(JacksonExporter.class.getName(),false);
+        
+        PojoWithResourceResolver pojo = new PojoWithResourceResolver("text", 
new EmptyResourceResolver());
+        
+        context.registerInjectActivateService(new 
ResourceResolverModuleProvider());
+        JacksonExporter underTest = 
context.registerInjectActivateService(JacksonExporter.class);
+        Map<String,String> options = Collections.emptyMap();
+
+        String expectedJson = "{\"msg\":\"text\",\"resolver\":{";
+        assertTrue(underTest.export(pojo, String.class, 
options).contains(expectedJson));
+        assertTrue(capture.anyMatch(event -> {
+           return 
event.getFormattedMessage().equals(WarningResourceResolverMixin.MESSAGE) && 
+                   event.getLevel().equals(Level.WARN);
+        }));
+    }
+    
+    
+    @Test
+    public void testNotSerializingResourceResolverWhenDisabled() throws 
ExportException {
+        
+        LogCapture capture = new 
LogCapture(IgnoringResourceResolverMixin.class.getName(),false);        
+        PojoWithResourceResolver pojo = new 
PojoWithResourceResolver("text",new EmptyResourceResolver());
+        
+        Map<String,Object> config = 
Collections.singletonMap("disable.serialization", "true");
+        context.registerInjectActivateService(new 
ResourceResolverModuleProvider(),config);
+        
+        
+        JacksonExporter underTest = 
context.registerInjectActivateService(JacksonExporter.class);
+        Map<String,String> options = Collections.emptyMap();
+
+        String expectedJson = "{\"msg\":\"text\"}";
+        assertEquals(expectedJson, underTest.export(pojo, String.class, 
options));
+//        assertTrue(capture.anyMatch(p -> 
p.getFormattedMessage().contains(IgnoringResourceResolverMixin.MESSAGE)));
+    }
+    
+    
+    /**
+     * A very simple ResourceResolver implementation which does not lead to 
any issues with any mocking framework
+     * when trying to export it with Jackson.
+     */
+    public class EmptyResourceResolver implements ResourceResolver {
+        
+//        public static final String SERIALIZED_STRING = 
"\"resolver\":{\"live\":false,\"userID\":null,\"searchPath\":null,\"propertyMap\":null,\"userID\":false}";
+        
+
+        @Override
+        public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource resolve(HttpServletRequest request, String absPath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource resolve(String absPath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource resolve(HttpServletRequest request) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String map(String resourcePath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String map(HttpServletRequest request, String resourcePath) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource getResource(String path) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource getResource(Resource base, String path) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String[] getSearchPath() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<Resource> listChildren(Resource parent) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource getParent(Resource child) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterable<Resource> getChildren(Resource parent) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<Resource> findResources(String query, String language) 
{
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<Map<String, Object>> queryResources(String query, 
String language) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean hasChildren(Resource resource) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public ResourceResolver clone(Map<String, Object> authenticationInfo) 
throws LoginException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean isLive() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public void close() {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public String getUserID() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Iterator<String> getAttributeNames() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Object getAttribute(String name) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void delete(Resource resource) throws PersistenceException {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public Resource create(Resource parent, String name, Map<String, 
Object> properties)
+                throws PersistenceException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void revert() {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public void commit() throws PersistenceException {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public boolean hasChanges() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public String getParentResourceType(Resource resource) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public String getParentResourceType(String resourceType) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean isResourceType(Resource resource, String resourceType) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public void refresh() {
+            // TODO Auto-generated method stub
+            
+        }
+
+        @Override
+        public Resource copy(String srcAbsPath, String destAbsPath) throws 
PersistenceException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Resource move(String srcAbsPath, String destAbsPath) throws 
PersistenceException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public boolean orderBefore(@NotNull Resource parent, @NotNull String 
name,
+                @Nullable String followingSiblingName)
+                throws UnsupportedOperationException, PersistenceException, 
IllegalArgumentException {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public @NotNull Map<String, Object> getPropertyMap() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+        
+    }
+    
+    
+}
diff --git 
a/src/test/java/org/apache/sling/models/jacksonexporter/impl/example/PojoWithResourceResolver.java
 
b/src/test/java/org/apache/sling/models/jacksonexporter/impl/example/PojoWithResourceResolver.java
new file mode 100644
index 0000000..3e544b7
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/models/jacksonexporter/impl/example/PojoWithResourceResolver.java
@@ -0,0 +1,36 @@
+/*
+ * 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.sling.models.jacksonexporter.impl.example;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class PojoWithResourceResolver {
+    
+    
+    public String msg;
+    public ResourceResolver resolver;
+    
+    public PojoWithResourceResolver (String msg, ResourceResolver resolver) {
+        this.msg = msg;
+        this.resolver = resolver;
+    }
+    
+    
+
+}
diff --git 
a/src/test/java/org/apache/sling/models/jacksonexporter/impl/util/LogCapture.java
 
b/src/test/java/org/apache/sling/models/jacksonexporter/impl/util/LogCapture.java
new file mode 100644
index 0000000..be328b0
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/models/jacksonexporter/impl/util/LogCapture.java
@@ -0,0 +1,69 @@
+/*
+ * 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.sling.models.jacksonexporter.impl.util;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.function.Predicate;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+
+public class LogCapture  extends ListAppender<ILoggingEvent> implements 
Closeable {
+    private final boolean verboseFailure;
+
+    /** Setup the capture and start it */
+    public LogCapture(String loggerName, boolean verboseFailure) {
+        this.verboseFailure = verboseFailure;
+        Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
+        logger.setLevel(Level.ALL);
+        setContext((LoggerContext) LoggerFactory.getILoggerFactory());
+        logger.addAppender(this);
+        start();
+    }
+
+    public boolean anyMatch(Predicate<ILoggingEvent> p) {
+        return this.list.stream().anyMatch(p);
+    }
+
+    public void assertMessage(String message) {
+        assertTrue(anyMatch(event -> {
+            return event.getFormattedMessage().equals(message);
+        }));
+    }
+    
+    
+    
+    
+
+    @Override
+    public void close() throws IOException {
+        stop();
+    }
+    
+    
+    
+}

Reply via email to