diff --git a/core/camel-api/pom.xml b/core/camel-api/pom.xml index 7029c8a471394..04ae6a2f25b5f 100644 --- a/core/camel-api/pom.xml +++ b/core/camel-api/pom.xml @@ -56,6 +56,12 @@ compile + + org.junit.jupiter + junit-jupiter + test + + diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java b/core/camel-api/src/main/java/org/apache/camel/Exchange.java index fc54ffd4cdada..950c76f0daa89 100644 --- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java +++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java @@ -16,7 +16,11 @@ */ package org.apache.camel; +import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import org.apache.camel.clock.Clock; import org.apache.camel.spi.Metadata; @@ -412,6 +416,75 @@ public interface Exchange extends VariableAware { */ T getProperty(String name, Object defaultValue, Class type); + /** + * Returns a property associated with this exchange as an Optional type. + * + * @param name the exchange property name + * @param type the type of the property + * @return the optional value of the given property. + */ + default Optional getPropertyAsOptional(String name, Class type) { + return Optional.ofNullable(getProperty(name, type)); + } + + /** + * Returns a property associated with this exchange as a List. Check by the name and specifying the type required + * for the List items. + * + * @param name the exchange property name + * @param type the type of the List items + * @return a List with items of the given type or null if there is no property for the given name, the + * backed object is not Iterable or any of the items is not of the given type. + */ + default List getPropertyAsList(String name, Class elementType) { + Object value = getProperty(name, Object.class); + return ExchangeSupport.getObjectAsList(value, elementType); + } + + /** + * Returns a property associated with this exchange as a Collection. Check by the name and specifying the type + * required for the Collection items. + * + * @param name the exchange property name + * @param type the type of the Collection items + * @return a Collection with items of the given type or null if there is no property for the given + * name, the backed object is not Iterable or any of the items is not of the given type. + */ + default Collection getPropertyAsCollection(String name, Class elementType) { + return getPropertyAsList(name, elementType); + } + + /** + * Returns a property associated with this exchange as a Set. Check by the name and specifying the type required for + * the Set items. + * + * @param name the exchange property name + * @param type the type of the Set items + * @return a Set with items of the given type or null if there is no property for the given name, the + * backed object is not Iterable or any of the items is not of the given type. + */ + default Set getPropertyAsSet(String name, Class elementType) { + List list = getPropertyAsList(name, elementType); + if (list == null) { + return null; + } + return Set.copyOf(getPropertyAsList(name, elementType)); + } + + /** + * Returns a property associated with this exchange as a Map. Check by the name and specifying the type required for + * the Map key anv value items. + * + * @param name the exchange property name + * @param type the type of the Map items + * @return a Map with items of the given type or null if there is no property for the given name, the + * backed object is not a Map or any of the key/value for each items is not of the given type. + */ + default Map getPropertyAsMap(String name, Class keyType, Class valueType) { + Object value = getProperty(name, Object.class); + return ExchangeSupport.getObjectAsMap(value, keyType, valueType); + } + /** * Sets a property on the exchange * @@ -501,6 +574,76 @@ public interface Exchange extends VariableAware { */ T getVariable(String name, Object defaultValue, Class type); + /** + * Returns an optional variable by name and specifying the type required + * + * @param name the variable name. Can be prefixed with repo-id:name to lookup the variable from a specific + * repository. If no repo-id is provided, then variables will be from the current exchange. + * @param type the type of the variable + * @return the value of the given variable as an Optional type. + */ + default Optional getVariableAsOptional(String name, Class type) { + return Optional.ofNullable(getVariable(name, type)); + } + + /** + * Returns a property associated with this variable as a List. Check by the key and specifying the type required for + * the List items. + * + * @param name the variable name + * @param type the type of the List items + * @return a List with items of the given type or null if there is no variable for the given name, the + * backed object is not Iterable or any of the items is not of the given type. + */ + default List getVariableAsList(String name, Class elementType) { + Object value = getVariable(name, Object.class); + return ExchangeSupport.getObjectAsList(value, elementType); + } + + /** + * Returns a property associated with this variable as a Collection. Check by the key and specifying the type + * required for the Collection items. + * + * @param name the variable name + * @param type the type of the Collection items + * @return a Collection with items of the given type or null if there is no variable for the given + * name, the backed object is not Iterable or any of the items is not of the given type. + */ + default Collection getVariableAsCollection(String name, Class elementType) { + return getVariableAsList(name, elementType); + } + + /** + * Returns a property associated with this variable as a Set. Check by the key and specifying the type required for + * the Set items. + * + * @param name the variable name + * @param type the type of the Set items + * @return a Set with items of the given type or null if there is no variable for the given name, the + * backed object is not Iterable or any of the items is not of the given type. + */ + default Set getVariableAsSet(String name, Class elementType) { + List list = getVariableAsList(name, elementType); + if (list == null) { + return null; + } + return Set.copyOf(list); + } + + /** + * Returns a property associated with this variable as a Map. Check by the key and specifying the type required for + * the Map key anv value items. + * + * @param name the variable name + * @param type the type of the Map items + * @return a Map with items of the given type or null if there is no variable for the given name, the + * backed object is not a Map or any of the key/value for each items is not of the given type. + */ + default Map getVariableAsMap(String name, Class keyType, Class valueType) { + Object value = getVariable(name, Object.class); + return ExchangeSupport.getObjectAsMap(value, keyType, valueType); + } + /** * Sets a variable on the exchange * diff --git a/core/camel-api/src/main/java/org/apache/camel/ExchangeSupport.java b/core/camel-api/src/main/java/org/apache/camel/ExchangeSupport.java new file mode 100644 index 0000000000000..677d02617ec3a --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/ExchangeSupport.java @@ -0,0 +1,74 @@ +/* + * 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.camel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * ExchangeSupport is an internal class used to support Exchange default methods. + */ +public class ExchangeSupport { + + protected static List getObjectAsList(Object value, Class elementType) { + if (value == null || !(value instanceof Iterable iterable)) { + return null; + } + + List result = new ArrayList<>(); + for (Object element : iterable) { + if (!elementType.isInstance(element)) { + return null; + } + result.add(elementType.cast(element)); + } + + return List.copyOf(result); + } + + protected static Map getObjectAsMap(Object value, Class keyType, Class valueType) { + if (value == null || !(value instanceof Map rawMap)) { + return null; + } + + Map result = new HashMap<>(); + for (Map.Entry entry : rawMap.entrySet()) { + Object rawKey = entry.getKey(); + Object rawValue = entry.getValue(); + + if (rawKey == null || !keyType.isInstance(rawKey)) { + return null; + } + + if (!valueType.isInstance(rawValue)) { + return null; + } + + try { + result.put( + keyType.cast(rawKey), + valueType.cast(rawValue)); + } catch (ClassCastException cce) { + return null; + } + } + + return Map.copyOf(result); + } +} diff --git a/core/camel-api/src/test/java/org/apache/camel/ExchangeSupportTest.java b/core/camel-api/src/test/java/org/apache/camel/ExchangeSupportTest.java new file mode 100644 index 0000000000000..61598be814cc9 --- /dev/null +++ b/core/camel-api/src/test/java/org/apache/camel/ExchangeSupportTest.java @@ -0,0 +1,170 @@ +/* + * 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.camel; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class ExchangeSupportTest { + + @Test + public void getPropertyShouldReturnNullOnMissingProperty() { + Exchange myExchange = new MockExchange(); + myExchange.setProperty("hello", "world"); + + assertNull(myExchange.getProperty("missing", String.class)); + assertNull(myExchange.getPropertyAsCollection("missing", String.class)); + assertNull(myExchange.getPropertyAsList("missing", String.class)); + assertNull(myExchange.getPropertyAsMap("missing", String.class, String.class)); + assertNull(myExchange.getPropertyAsSet("missing", String.class)); + // Optional must not be null, but empty + assertNotNull(myExchange.getPropertyAsOptional("missing", String.class)); + assertFalse(myExchange.getPropertyAsOptional("missing", String.class).isPresent()); + } + + @Test + public void getPropertyShouldReturnNullOnClassCastException() { + Exchange myExchange = new MockExchange(); + myExchange.setProperty("hello", "world"); + + assertNull(myExchange.getProperty("hello", Integer.class)); + assertNull(myExchange.getPropertyAsCollection("hello", Integer.class)); + assertNull(myExchange.getPropertyAsList("hello", Integer.class)); + assertNull(myExchange.getPropertyAsMap("hello", Integer.class, Integer.class)); + assertNull(myExchange.getPropertyAsSet("hello", Integer.class)); + // Optional must not be null, but empty + assertNotNull(myExchange.getPropertyAsOptional("hello", Integer.class)); + assertFalse(myExchange.getPropertyAsOptional("hello", Integer.class).isPresent()); + } + + @Test + public void getPropertyShouldReturnIterableProperTypes() { + Exchange myExchange = new MockExchange(); + List myList = List.of(1, 2); + Set mySet = Set.of("first", "second"); + Map myMap = Map.of("firstKey", 1, "secondKey", 2); + Collection myCollection = List.of("a", "b"); + + myExchange.setProperty("helloList", myList); + myExchange.setProperty("helloSet", mySet); + myExchange.setProperty("helloMap", myMap); + myExchange.setProperty("helloCollection", myCollection); + + assertEquals(myList, myExchange.getPropertyAsList("helloList", Integer.class)); + assertEquals(mySet, myExchange.getPropertyAsSet("helloSet", String.class)); + assertEquals(myMap, myExchange.getPropertyAsMap("helloMap", String.class, Integer.class)); + assertEquals(myCollection, myExchange.getPropertyAsCollection("helloCollection", String.class)); + } + + @Test + public void getPropertyShouldReturnNullOnWrongCastItemTypes() { + Exchange myExchange = new MockExchange(); + List myList = List.of(1, 2); + Set mySet = Set.of("first", "second"); + Map myMap = Map.of("firstKey", 1, "secondKey", 2); + Collection myCollection = List.of("a", "b"); + + myExchange.setProperty("helloList", myList); + myExchange.setProperty("helloSet", mySet); + myExchange.setProperty("helloMap", myMap); + myExchange.setProperty("helloCollection", myCollection); + + assertNull(myExchange.getPropertyAsList("helloList", String.class), "Expected Integer items"); + assertNull(myExchange.getPropertyAsSet("helloSet", Integer.class), "Expected String items"); + assertNull(myExchange.getPropertyAsMap("helloMap", Integer.class, String.class), "Expected String/Integer key-value"); + assertNull(myExchange.getPropertyAsMap("helloMap", String.class, String.class), "Expected String/Integer key-value"); + assertNull(myExchange.getPropertyAsCollection("helloCollection", Integer.class), "Expected String items"); + } + + @Test + public void getVariableShouldReturnNullOnMissingProperty() { + Exchange myExchange = new MockExchange(); + myExchange.setVariable("hello", "world"); + + assertNull(myExchange.getVariable("missing", String.class)); + assertNull(myExchange.getVariableAsCollection("missing", String.class)); + assertNull(myExchange.getVariableAsList("missing", String.class)); + assertNull(myExchange.getVariableAsMap("missing", String.class, String.class)); + assertNull(myExchange.getVariableAsSet("missing", String.class)); + // Optional must not be null, but empty + assertNotNull(myExchange.getVariableAsOptional("missing", String.class)); + assertFalse(myExchange.getVariableAsOptional("missing", String.class).isPresent()); + } + + @Test + public void getVariableShouldReturnNullOnClassCastException() { + Exchange myExchange = new MockExchange(); + myExchange.setVariable("hello", "world"); + + assertNull(myExchange.getVariable("hello", Integer.class)); + assertNull(myExchange.getVariableAsCollection("hello", Integer.class)); + assertNull(myExchange.getVariableAsList("hello", Integer.class)); + assertNull(myExchange.getVariableAsMap("hello", Integer.class, Integer.class)); + assertNull(myExchange.getVariableAsSet("hello", Integer.class)); + // Optional must not be null, but empty + assertNotNull(myExchange.getVariableAsOptional("hello", Integer.class)); + assertFalse(myExchange.getVariableAsOptional("hello", Integer.class).isPresent()); + } + + @Test + public void getVariableShouldReturnIterableProperTypes() { + Exchange myExchange = new MockExchange(); + List myList = List.of(1, 2); + Set mySet = Set.of("first", "second"); + Map myMap = Map.of("firstKey", 1, "secondKey", 2); + Collection myCollection = List.of("a", "b"); + + myExchange.setVariable("helloList", myList); + myExchange.setVariable("helloSet", mySet); + myExchange.setVariable("helloMap", myMap); + myExchange.setVariable("helloCollection", myCollection); + + assertEquals(myList, myExchange.getVariableAsList("helloList", Integer.class)); + assertEquals(mySet, myExchange.getVariableAsSet("helloSet", String.class)); + assertEquals(myMap, myExchange.getVariableAsMap("helloMap", String.class, Integer.class)); + assertEquals(myCollection, myExchange.getVariableAsCollection("helloCollection", String.class)); + } + + @Test + public void getVariableShouldReturnNullOnWrongCastItemTypes() { + Exchange myExchange = new MockExchange(); + List myList = List.of(1, 2); + Set mySet = Set.of("first", "second"); + Map myMap = Map.of("firstKey", 1, "secondKey", 2); + Collection myCollection = List.of("a", "b"); + + myExchange.setVariable("helloList", myList); + myExchange.setVariable("helloSet", mySet); + myExchange.setVariable("helloMap", myMap); + myExchange.setVariable("helloCollection", myCollection); + + assertNull(myExchange.getVariableAsList("helloList", String.class), "Expected Integer items"); + assertNull(myExchange.getVariableAsSet("helloSet", Integer.class), "Expected String items"); + assertNull(myExchange.getVariableAsMap("helloMap", Integer.class, String.class), "Expected String/Integer key-value"); + assertNull(myExchange.getVariableAsMap("helloMap", String.class, String.class), "Expected String/Integer key-value"); + assertNull(myExchange.getVariableAsCollection("helloCollection", Integer.class), "Expected String items"); + } +} diff --git a/core/camel-api/src/test/java/org/apache/camel/MockExchange.java b/core/camel-api/src/test/java/org/apache/camel/MockExchange.java new file mode 100644 index 0000000000000..72d0a4832b026 --- /dev/null +++ b/core/camel-api/src/test/java/org/apache/camel/MockExchange.java @@ -0,0 +1,336 @@ +/* + * 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.camel; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.clock.Clock; +import org.apache.camel.spi.UnitOfWork; + +public class MockExchange implements Exchange { + + private Map properties; + private Map variables; + + public MockExchange() { + this.properties = new HashMap<>(); + this.variables = new HashMap<>(); + } + + @Override + public ExchangePattern getPattern() { + throw new UnsupportedOperationException("Unimplemented method 'getPattern'"); + } + + @Override + public void setPattern(ExchangePattern pattern) { + throw new UnsupportedOperationException("Unimplemented method 'setPattern'"); + } + + @Override + public Object getProperty(ExchangePropertyKey key) { + throw new UnsupportedOperationException("Unimplemented method 'getProperty'"); + } + + @Override + public T getProperty(ExchangePropertyKey key, Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getProperty'"); + } + + @Override + public T getProperty(ExchangePropertyKey key, Object defaultValue, Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getProperty'"); + } + + @Override + public void setProperty(ExchangePropertyKey key, Object value) { + throw new UnsupportedOperationException("Unimplemented method 'setProperty'"); + } + + @Override + public Object removeProperty(ExchangePropertyKey key) { + throw new UnsupportedOperationException("Unimplemented method 'removeProperty'"); + } + + @Override + public Object getProperty(String name) { + return this.properties.get(name); + } + + @Override + public T getProperty(String name, Class type) { + Object value = getProperty(name); + if (value == null) { + return null; + } + try { + return type.cast(value); + } catch (ClassCastException cce) { + return null; + } + } + + @Override + public T getProperty(String name, Object defaultValue, Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getProperty'"); + } + + @Override + public void setProperty(String name, Object value) { + this.properties.put(name, value); + } + + @Override + public Object removeProperty(String name) { + throw new UnsupportedOperationException("Unimplemented method 'removeProperty'"); + } + + @Override + public boolean removeProperties(String pattern) { + throw new UnsupportedOperationException("Unimplemented method 'removeProperties'"); + } + + @Override + public boolean removeProperties(String pattern, String... excludePatterns) { + throw new UnsupportedOperationException("Unimplemented method 'removeProperties'"); + } + + @Override + public Map getProperties() { + throw new UnsupportedOperationException("Unimplemented method 'getProperties'"); + } + + @Override + public Map getAllProperties() { + throw new UnsupportedOperationException("Unimplemented method 'getAllProperties'"); + } + + @Override + public boolean hasProperties() { + throw new UnsupportedOperationException("Unimplemented method 'hasProperties'"); + } + + @Override + public Object getVariable(String name) { + return this.variables.get(name); + } + + @Override + public T getVariable(String name, Class type) { + Object value = getVariable(name); + if (value == null) { + return null; + } + try { + return type.cast(value); + } catch (ClassCastException cce) { + return null; + } + } + + @Override + public T getVariable(String name, Object defaultValue, Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getVariable'"); + } + + @Override + public void setVariable(String name, Object value) { + this.variables.put(name, value); + } + + @Override + public Object removeVariable(String name) { + throw new UnsupportedOperationException("Unimplemented method 'removeVariable'"); + } + + @Override + public Map getVariables() { + throw new UnsupportedOperationException("Unimplemented method 'getVariables'"); + } + + @Override + public boolean hasVariables() { + throw new UnsupportedOperationException("Unimplemented method 'hasVariables'"); + } + + @Override + public Message getIn() { + throw new UnsupportedOperationException("Unimplemented method 'getIn'"); + } + + @Override + public Message getMessage() { + throw new UnsupportedOperationException("Unimplemented method 'getMessage'"); + } + + @Override + public T getMessage(Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getMessage'"); + } + + @Override + public void setMessage(Message message) { + throw new UnsupportedOperationException("Unimplemented method 'setMessage'"); + } + + @Override + public T getIn(Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getIn'"); + } + + @Override + public void setIn(Message in) { + throw new UnsupportedOperationException("Unimplemented method 'setIn'"); + } + + @Override + @Deprecated + public Message getOut() { + throw new UnsupportedOperationException("Unimplemented method 'getOut'"); + } + + @Override + @Deprecated + public T getOut(Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getOut'"); + } + + @Override + @Deprecated + public boolean hasOut() { + throw new UnsupportedOperationException("Unimplemented method 'hasOut'"); + } + + @Override + @Deprecated + public void setOut(Message out) { + throw new UnsupportedOperationException("Unimplemented method 'setOut'"); + } + + @Override + public Exception getException() { + throw new UnsupportedOperationException("Unimplemented method 'getException'"); + } + + @Override + public T getException(Class type) { + throw new UnsupportedOperationException("Unimplemented method 'getException'"); + } + + @Override + public void setException(Throwable t) { + throw new UnsupportedOperationException("Unimplemented method 'setException'"); + } + + @Override + public boolean isFailed() { + throw new UnsupportedOperationException("Unimplemented method 'isFailed'"); + } + + @Override + public boolean isTransacted() { + throw new UnsupportedOperationException("Unimplemented method 'isTransacted'"); + } + + @Override + public boolean isRouteStop() { + throw new UnsupportedOperationException("Unimplemented method 'isRouteStop'"); + } + + @Override + public void setRouteStop(boolean routeStop) { + throw new UnsupportedOperationException("Unimplemented method 'setRouteStop'"); + } + + @Override + public boolean isExternalRedelivered() { + throw new UnsupportedOperationException("Unimplemented method 'isExternalRedelivered'"); + } + + @Override + public boolean isRollbackOnly() { + throw new UnsupportedOperationException("Unimplemented method 'isRollbackOnly'"); + } + + @Override + public void setRollbackOnly(boolean rollbackOnly) { + throw new UnsupportedOperationException("Unimplemented method 'setRollbackOnly'"); + } + + @Override + public boolean isRollbackOnlyLast() { + throw new UnsupportedOperationException("Unimplemented method 'isRollbackOnlyLast'"); + } + + @Override + public void setRollbackOnlyLast(boolean rollbackOnlyLast) { + throw new UnsupportedOperationException("Unimplemented method 'setRollbackOnlyLast'"); + } + + @Override + public CamelContext getContext() { + throw new UnsupportedOperationException("Unimplemented method 'getContext'"); + } + + @Override + public Exchange copy() { + throw new UnsupportedOperationException("Unimplemented method 'copy'"); + } + + @Override + public Endpoint getFromEndpoint() { + throw new UnsupportedOperationException("Unimplemented method 'getFromEndpoint'"); + } + + @Override + public String getFromRouteId() { + throw new UnsupportedOperationException("Unimplemented method 'getFromRouteId'"); + } + + @Override + public UnitOfWork getUnitOfWork() { + throw new UnsupportedOperationException("Unimplemented method 'getUnitOfWork'"); + } + + @Override + public String getExchangeId() { + throw new UnsupportedOperationException("Unimplemented method 'getExchangeId'"); + } + + @Override + public void setExchangeId(String id) { + throw new UnsupportedOperationException("Unimplemented method 'setExchangeId'"); + } + + @Override + @Deprecated + public long getCreated() { + throw new UnsupportedOperationException("Unimplemented method 'getCreated'"); + } + + @Override + public ExchangeExtension getExchangeExtension() { + throw new UnsupportedOperationException("Unimplemented method 'getExchangeExtension'"); + } + + @Override + public Clock getClock() { + throw new UnsupportedOperationException("Unimplemented method 'getClock'"); + } + +}