Author: tripod
Date: Fri Mar 7 00:22:21 2014
New Revision: 1575104
URL: http://svn.apache.org/r1575104
Log:
@trivial add support for unit based time config
Modified:
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/ConfigurationParametersTest.java
Modified:
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java?rev=1575104&r1=1575103&r2=1575104&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java
(original)
+++
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfig.java
Fri Mar 7 00:22:21 2014
@@ -60,15 +60,15 @@ public class DefaultSyncConfig {
/**
* @see DefaultSyncConfig.User#getExpirationTime()
*/
- public static final long PARAM_USER_EXPIRATION_TIME_DEFAULT = 3600*1000;
+ public static final String PARAM_USER_EXPIRATION_TIME_DEFAULT = "1h";
/**
* @see DefaultSyncConfig.User#getExpirationTime()
*/
@Property(
label = "User Expiration Time",
- description = "Duration in milliseconds until a synced user gets
expired.",
- longValue = PARAM_USER_EXPIRATION_TIME_DEFAULT
+ description = "Duration until a synced user gets expired (eg. '1h
30m' or '1d').",
+ value = PARAM_USER_EXPIRATION_TIME_DEFAULT
)
public static final String PARAM_USER_EXPIRATION_TIME =
"user.expirationTime";
@@ -123,15 +123,15 @@ public class DefaultSyncConfig {
/**
* @see DefaultSyncConfig.User#getMembershipExpirationTime()
*/
- public static final long PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT =
3600*1000;
+ public static final String PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT =
"1h";
/**
* @see DefaultSyncConfig.User#getMembershipExpirationTime()
*/
@Property(
label = "User Membership Expiration",
- description = "Time in milliseconds after which membership
expires.",
- longValue = PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT
+ description = "Time after which membership expires (eg. '1h 30m'
or '1d').",
+ value = PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT
)
public static final String PARAM_USER_MEMBERSHIP_EXPIRATION_TIME =
"user.membershipExpTime";
@@ -156,15 +156,15 @@ public class DefaultSyncConfig {
/**
* @see DefaultSyncConfig.Group#getExpirationTime()
*/
- public static final long PARAM_GROUP_EXPIRATION_TIME_DEFAULT =
24*3600*1000;
+ public static final String PARAM_GROUP_EXPIRATION_TIME_DEFAULT = "1d";
/**
* @see DefaultSyncConfig.Group#getExpirationTime()
*/
@Property(
label = "Group Expiration Time",
- description = "Duration in milliseconds until a synced group
expires.",
- longValue = PARAM_GROUP_EXPIRATION_TIME_DEFAULT
+ description = "Duration until a synced group expires (eg. '1h 30m'
or '1d').",
+ value = PARAM_GROUP_EXPIRATION_TIME_DEFAULT
)
public static final String PARAM_GROUP_EXPIRATION_TIME =
"group.expirationTime";
@@ -404,16 +404,22 @@ public class DefaultSyncConfig {
.setName(params.getConfigValue(PARAM_NAME,
PARAM_NAME_DEFAULT));
cfg.user()
-
.setMembershipExpirationTime(params.getConfigValue(PARAM_USER_MEMBERSHIP_EXPIRATION_TIME,
PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT))
+ .setMembershipExpirationTime(
+
ConfigurationParameters.Milliseconds.of(params.getConfigValue(PARAM_USER_MEMBERSHIP_EXPIRATION_TIME,
PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT)).value
+ )
.setMembershipNestingDepth(params.getConfigValue(PARAM_USER_MEMBERSHIP_NESTING_DEPTH,
PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT))
-
.setExpirationTime(params.getConfigValue(PARAM_USER_EXPIRATION_TIME,
PARAM_USER_EXPIRATION_TIME_DEFAULT))
+ .setExpirationTime(
+
ConfigurationParameters.Milliseconds.of(params.getConfigValue(PARAM_USER_EXPIRATION_TIME,
PARAM_USER_EXPIRATION_TIME_DEFAULT)).value
+ )
.setPathPrefix(params.getConfigValue(PARAM_USER_PATH_PREFIX,
PARAM_USER_PATH_PREFIX_DEFAULT))
.setAutoMembership(params.getConfigValue(PARAM_USER_AUTO_MEMBERSHIP,
PARAM_USER_AUTO_MEMBERSHIP_DEFAULT))
.setPropertyMapping(createMapping(
params.getConfigValue(PARAM_USER_PROPERTY_MAPPING,
PARAM_USER_PROPERTY_MAPPING_DEFAULT)));
cfg.group()
-
.setExpirationTime(params.getConfigValue(PARAM_GROUP_EXPIRATION_TIME,
PARAM_GROUP_EXPIRATION_TIME_DEFAULT))
+ .setExpirationTime(
+
ConfigurationParameters.Milliseconds.of(params.getConfigValue(PARAM_GROUP_EXPIRATION_TIME,
PARAM_GROUP_EXPIRATION_TIME_DEFAULT)).value
+ )
.setPathPrefix(params.getConfigValue(PARAM_GROUP_PATH_PREFIX,
PARAM_GROUP_PATH_PREFIX_DEFAULT))
.setAutoMembership(params.getConfigValue(PARAM_GROUP_AUTO_MEMBERSHIP,
PARAM_GROUP_AUTO_MEMBERSHIP_DEFAULT))
.setPropertyMapping(createMapping(
Modified:
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java?rev=1575104&r1=1575103&r2=1575104&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
(original)
+++
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapIdentityProvider.java
Fri Mar 7 00:22:21 2014
@@ -46,7 +46,6 @@ import org.apache.directory.api.ldap.mod
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapConnectionPool;
-import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.NoVerificationTrustManager;
import org.apache.directory.ldap.client.api.PoolableLdapConnectionFactory;
import org.apache.felix.scr.annotations.Activate;
@@ -291,7 +290,7 @@ public class LdapIdentityProvider implem
SearchRequest req = new SearchRequestImpl();
req.setScope(SearchScope.SUBTREE);
req.addAttributes(SchemaConstants.ALL_USER_ATTRIBUTES);
- req.setTimeLimit(config.getSearchTimeout());
+ req.setTimeLimit((int) config.getSearchTimeout());
req.setBase(new Dn(idConfig.getBaseDN()));
req.setFilter(searchFilter);
@@ -437,7 +436,7 @@ public class LdapIdentityProvider implem
SearchRequest req = new SearchRequestImpl();
req.setScope(SearchScope.SUBTREE);
req.addAttributes(SchemaConstants.NO_ATTRIBUTE);
- req.setTimeLimit(config.getSearchTimeout());
+ req.setTimeLimit((int) config.getSearchTimeout());
req.setBase(new Dn(config.getGroupConfig().getBaseDN()));
req.setFilter(searchFilter);
Modified:
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java?rev=1575104&r1=1575103&r2=1575104&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
(original)
+++
jackrabbit/oak/trunk/oak-auth-ldap/src/main/java/org/apache/jackrabbit/oak/security/authentication/ldap/impl/LdapProviderConfig.java
Fri Mar 7 00:22:21 2014
@@ -159,15 +159,15 @@ public class LdapProviderConfig {
/**
* @see #getSearchTimeout()
*/
- public static final int PARAM_SEARCH_TIMEOUT_DEFAULT = 60000;
+ public static final String PARAM_SEARCH_TIMEOUT_DEFAULT = "60s";
/**
* @see #getSearchTimeout()
*/
@Property(
label = "Search Timeout",
- description = "Time in milliseconds until a search times out.",
- intValue = PARAM_SEARCH_TIMEOUT_DEFAULT
+ description = "Time in until a search times out (eg: '1s' or '1m
30s').",
+ value = PARAM_SEARCH_TIMEOUT_DEFAULT
)
public static final String PARAM_SEARCH_TIMEOUT = "searchTimeout";
@@ -523,7 +523,7 @@ public class LdapProviderConfig {
.setNoCertCheck(params.getConfigValue(PARAM_NO_CERT_CHECK,
PARAM_NO_CERT_CHECK_DEFAULT))
.setBindDN(params.getConfigValue(PARAM_BIND_DN,
PARAM_BIND_DN_DEFAULT))
.setBindPassword(params.getConfigValue(PARAM_BIND_PASSWORD,
PARAM_BIND_PASSWORD_DEFAULT))
- .setSearchTimeout(params.getConfigValue(PARAM_SEARCH_TIMEOUT,
PARAM_SEARCH_TIMEOUT_DEFAULT))
+
.setSearchTimeout(ConfigurationParameters.Milliseconds.of(params.getConfigValue(PARAM_SEARCH_TIMEOUT,
PARAM_SEARCH_TIMEOUT_DEFAULT)).value)
.setGroupMemberAttribute(params.getConfigValue(PARAM_GROUP_MEMBER_ATTRIBUTE,
PARAM_GROUP_MEMBER_ATTRIBUTE_DEFAULT));
cfg.getUserConfig()
@@ -559,7 +559,7 @@ public class LdapProviderConfig {
private String bindPassword = PARAM_BIND_PASSWORD_DEFAULT;
- private int searchTimeout = PARAM_SEARCH_TIMEOUT_DEFAULT;
+ private long searchTimeout =
ConfigurationParameters.Milliseconds.of(PARAM_SEARCH_TIMEOUT_DEFAULT).value;
private String groupMemberAttribute = PARAM_GROUP_MEMBER_ATTRIBUTE;
@@ -762,7 +762,7 @@ public class LdapProviderConfig {
*
* @return the timeout in milliseconds.
*/
- public int getSearchTimeout() {
+ public long getSearchTimeout() {
return searchTimeout;
}
@@ -770,10 +770,10 @@ public class LdapProviderConfig {
* Sets the search timeout.
* @param searchTimeout the timeout in milliseconds
* @return {@code this}
- * @see #setSearchTimeout(int)
+ * @see #getSearchTimeout()
*/
@Nonnull
- public LdapProviderConfig setSearchTimeout(int searchTimeout) {
+ public LdapProviderConfig setSearchTimeout(long searchTimeout) {
this.searchTimeout = searchTimeout;
return this;
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java?rev=1575104&r1=1575103&r2=1575104&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/ConfigurationParameters.java
Fri Mar 7 00:22:21 2014
@@ -24,6 +24,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -229,6 +231,9 @@ public final class ConfigurationParamete
return (T) configProperty;
} else if (clazz == String.class) {
return (T) str;
+ } else if (clazz == Milliseconds.class) {
+ Milliseconds ret = Milliseconds.of(str);
+ return (T) ret == null ? defaultValue : (T) ret;
} else if (clazz == Integer.class || clazz == int.class) {
return (T) Integer.valueOf(str);
} else if (clazz == Long.class || clazz == long.class) {
@@ -350,4 +355,98 @@ public final class ConfigurationParamete
public Set<Entry<String,Object>> entrySet() {
return options.entrySet();
}
+
+ /**
+ * Helper class for configuration parameters that denote a "duration",
such as a timeout or expiration time.
+ */
+ public static final class Milliseconds {
+
+ private static final Pattern pattern =
Pattern.compile("(\\d+)(\\.\\d+)?(ms|s|m|h|d)?");
+
+ public static final Milliseconds NULL = new Milliseconds(0);
+
+ public static final Milliseconds FOREVER = new
Milliseconds(Long.MAX_VALUE);
+
+ public static final Milliseconds NEVER = new Milliseconds(-1);
+
+ public final long value;
+
+ private Milliseconds(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns a new milliseconds object from the given long value.
+ * @param value the value
+ * @return the milliseconds object
+ */
+ public static Milliseconds of(long value) {
+ if (value == 0) {
+ return NULL;
+ } else if (value == Long.MAX_VALUE) {
+ return FOREVER;
+ } else if (value < 0) {
+ return NEVER;
+ } else {
+ return new Milliseconds(value);
+ }
+ }
+
+ /**
+ * Parses a value string into a duration. the String has the following
format:
+ * <xmp>
+ * format:= (value [ unit ])+;
+ * value:= float value;
+ * unit: "ms" | "s" | "m" | "h" | "d";
+ * </xmp>
+ *
+ * Example:
+ * <xmp>
+ * "100", "100ms" : 100 milliseconds
+ * "1s 50ms": 1050 milliseconds
+ * "1.5d": 1 1/2 days == 36 hours.
+ * </xmp>
+ *
+ * @param str the string to parse
+ * @return the new Milliseconds object or null.
+ */
+ @CheckForNull
+ public static Milliseconds of(@Nullable String str) {
+ if (str == null) {
+ return null;
+ }
+ Matcher m = pattern.matcher(str);
+ long current = -1;
+ while (m.find()) {
+ String number = m.group(1);
+ String decimal = m.group(2);
+ if (decimal != null) {
+ number+=decimal;
+ }
+ String unit = m.group(3);
+ double value = Double.valueOf(number);
+ if ("s".equals(unit)) {
+ value*= 1000.0;
+ } else if ("m".equals(unit)) {
+ value*= 60*1000.0;
+ } else if ("h".equals(unit)) {
+ value*= 60*60*1000.0;
+ } else if ("d".equals(unit)) {
+ value*= 24*60*60*1000.0;
+ }
+ current += value;
+ }
+ return current < 0 ? null : new Milliseconds(current + 1);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || !(o == null || getClass() != o.getClass()) &&
value == ((Milliseconds) o).value;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (value ^ (value >>> 32));
+ }
+ }
}
\ No newline at end of file
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/ConfigurationParametersTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/ConfigurationParametersTest.java?rev=1575104&r1=1575103&r2=1575104&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/ConfigurationParametersTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/ConfigurationParametersTest.java
Fri Mar 7 00:22:21 2014
@@ -143,8 +143,10 @@ public class ConfigurationParametersTest
Map<String,Object> m = new HashMap<String, Object>();
m.put("TEST", testObject);
m.put("String", "1000");
- m.put("Int2", new Integer(1000));
+ m.put("Int2", 1000);
m.put("Int3", 1000);
+ m.put("time0", "1s");
+ m.put("time1", 1000);
ConfigurationParameters options = ConfigurationParameters.of(m);
assertEquals(testObject, options.getConfigValue("TEST", testObject));
@@ -152,26 +154,29 @@ public class ConfigurationParametersTest
assertTrue(1000 == options.getConfigValue("String", 10, int.class));
assertTrue(1000 == options.getConfigValue("String", 10));
- assertEquals(int1000, options.getConfigValue("String", new
Integer(10)));
- assertEquals(new Long(1000), options.getConfigValue("String", new
Long(10)));
+ assertEquals(int1000, options.getConfigValue("String", 10));
+ assertEquals(new Long(1000), options.getConfigValue("String", 10l));
assertEquals("1000", options.getConfigValue("String", "10"));
- assertEquals(int1000, options.getConfigValue("Int2", new Integer(10)));
+ assertEquals(int1000, options.getConfigValue("Int2", 10));
assertEquals("1000", options.getConfigValue("Int2", "1000"));
- assertEquals(int1000, options.getConfigValue("Int3", new Integer(10)));
+ assertEquals(int1000, options.getConfigValue("Int3", 10));
assertEquals("1000", options.getConfigValue("Int3", "1000"));
+
+ assertEquals(ConfigurationParameters.Milliseconds.of(1000),
options.getConfigValue("time0", ConfigurationParameters.Milliseconds.NULL));
+ assertEquals(ConfigurationParameters.Milliseconds.of(1000),
options.getConfigValue("time1", ConfigurationParameters.Milliseconds.NULL));
}
@Test
public void testConversion2() {
TestObject testObject = new TestObject("t");
- Integer int1000 = new Integer(1000);
+ Integer int1000 = 1000;
Map<String,Object> m = new HashMap<String, Object>();
m.put("TEST", testObject);
m.put("String", "1000");
- m.put("Int2", new Integer(1000));
+ m.put("Int2", 1000);
m.put("Int3", 1000);
ConfigurationParameters options = ConfigurationParameters.of(m);
@@ -190,21 +195,21 @@ public class ConfigurationParametersTest
assertEquals("1000", options.getConfigValue("String", null, null));
assertEquals("1000", options.getConfigValue("String", null,
String.class));
- assertEquals(int1000, options.getConfigValue("String", new
Integer(10), null));
- assertEquals(int1000, options.getConfigValue("String", new
Integer(10), Integer.class));
+ assertEquals(int1000, options.getConfigValue("String", 10, null));
+ assertEquals(int1000, options.getConfigValue("String", 10,
Integer.class));
- assertEquals(new Long(1000), options.getConfigValue("String", new
Long(10), null));
- assertEquals(new Long(1000), options.getConfigValue("String", new
Long(10), Long.class));
+ assertEquals(new Long(1000), options.getConfigValue("String", 10l,
null));
+ assertEquals(new Long(1000), options.getConfigValue("String", 10l,
Long.class));
assertEquals("1000", options.getConfigValue("String", "10", null));
assertEquals("1000", options.getConfigValue("String", "10",
String.class));
assertEquals(int1000, options.getConfigValue("Int2", null, null));
- assertEquals(int1000, options.getConfigValue("Int2", new Integer(10),
null));
+ assertEquals(int1000, options.getConfigValue("Int2", 10, null));
assertEquals("1000", options.getConfigValue("Int2", "1000", null));
assertEquals(1000, options.getConfigValue("Int3", null, null));
- assertEquals(int1000, options.getConfigValue("Int3", new Integer(10),
null));
+ assertEquals(int1000, options.getConfigValue("Int3", 10, null));
assertEquals("1000", options.getConfigValue("Int3", "1000", null));
}
@@ -213,10 +218,10 @@ public class ConfigurationParametersTest
Map<String, Object> map = new HashMap<String, Object>();
map.put("string", "v");
map.put("obj", new TestObject("test"));
- map.put("int", new Integer(10));
+ map.put("int", 10);
ConfigurationParameters options = ConfigurationParameters.of(map);
- Map<String, Class> impossible = new HashMap();
+ Map<String, Class> impossible = new HashMap<String, Class>();
impossible.put("string", TestObject.class);
impossible.put("string", Integer.class);
impossible.put("string", Calendar.class);
@@ -274,13 +279,25 @@ public class ConfigurationParametersTest
}
public boolean equals(Object object) {
- if (object == this) {
- return true;
- }
- if (object instanceof TestObject) {
- return name.equals(((TestObject) object).name);
- }
- return false;
+ return object == this || object instanceof TestObject &&
name.equals(((TestObject) object).name);
}
}
+
+ @Test
+ public void testDurationParser() {
+ assertNull(ConfigurationParameters.Milliseconds.of(""));
+ assertNull(ConfigurationParameters.Milliseconds.of(null));
+ assertEquals(1, ConfigurationParameters.Milliseconds.of("1").value);
+ assertEquals(1, ConfigurationParameters.Milliseconds.of("1ms").value);
+ assertEquals(1, ConfigurationParameters.Milliseconds.of("
1ms").value);
+ assertEquals(1, ConfigurationParameters.Milliseconds.of(" 1ms
").value);
+ assertEquals(1, ConfigurationParameters.Milliseconds.of(" 1ms
foobar").value);
+ assertEquals(1000,
ConfigurationParameters.Milliseconds.of("1s").value);
+ assertEquals(1500,
ConfigurationParameters.Milliseconds.of("1.5s").value);
+ assertEquals(1500, ConfigurationParameters.Milliseconds.of("1s
500ms").value);
+ assertEquals(60*1000,
ConfigurationParameters.Milliseconds.of("1m").value);
+ assertEquals(90*1000,
ConfigurationParameters.Milliseconds.of("1m30s").value);
+ assertEquals(60*60*1000+90*1000,
ConfigurationParameters.Milliseconds.of("1h1m30s").value);
+ assertEquals(36*60*60*1000 + 60*60*1000+90*1000,
ConfigurationParameters.Milliseconds.of("1.5d1h1m30s").value);
+ }
}
\ No newline at end of file