diff --git a/pom.xml b/pom.xml index 1051780a12b821c39e376e7d356b0318edcad219..a293d00a56ec8465fa9cc318780501dd19bb4244 100644 --- a/pom.xml +++ b/pom.xml @@ -64,13 +64,13 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <targetJdk>1.8</targetJdk> - <org.springframework.version>3.2.15.RELEASE</org.springframework.version> + <org.springframework.version>3.2.16.RELEASE</org.springframework.version> <org.hibernate.version>4.3.11.Final</org.hibernate.version> <com.sun.jersey.version>1.17.1</com.sun.jersey.version> <jackson.version>1.9.2</jackson.version> - <org.mysql.version>5.1.37</org.mysql.version> - <org.postgresql.version>9.4-1206-jdbc42</org.postgresql.version> - <org.infinispan.version>6.0.2.Final</org.infinispan.version> + <org.mysql.version>5.1.38</org.mysql.version> + <org.postgresql.version>9.4.1207</org.postgresql.version> + <org.infinispan.version>7.2.3.Final</org.infinispan.version> <lucene.version>4.8.0</lucene.version> <version.selenium>2.47.1</version.selenium> <activemq.version>5.11.1</activemq.version> @@ -2091,11 +2091,11 @@ <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId> <version>2.9.1</version> - <!-- fxdiff: FXOLAT-243 prevents duplicate --> + <!-- prevents duplicate --> <exclusions> <exclusion> <groupId>xerces</groupId> - <artifactId>xmlParserAPIs</artifactId> + <artifactId>xmlParserAPIs</artifactId> </exclusion> </exclusions> </dependency> diff --git a/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacher.java b/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacher.java index 7feae325fcb41926a2130ee23d7342097a9fd130..5361f8a737d18ff77246fa9f7319744a70e7507e 100644 --- a/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacher.java +++ b/src/main/java/org/olat/core/util/cache/infinispan/InfinispanCacher.java @@ -70,7 +70,7 @@ public class InfinispanCacher implements Cacher { private void createInfinispanConfiguration(String cacheName) { Configuration conf = cacheManager.getCacheConfiguration(cacheName); if(conf == null) { - int maxEntries = 10000; + long maxEntries = 10000; long maxIdle = 900000l; ConfigurationBuilder builder = new ConfigurationBuilder(); diff --git a/src/main/java/org/olat/dispatcher/AuthenticatedDispatcher.java b/src/main/java/org/olat/dispatcher/AuthenticatedDispatcher.java index c20801732bb025387ed4d410e275afd68fed349c..66d68b7dab262ec49d3d39e178184bc3d6e6170a 100644 --- a/src/main/java/org/olat/dispatcher/AuthenticatedDispatcher.java +++ b/src/main/java/org/olat/dispatcher/AuthenticatedDispatcher.java @@ -225,7 +225,7 @@ public class AuthenticatedDispatcher implements Dispatcher { String[] split = restPart.split("/"); if(restPart.startsWith("repo/go")) { - businessPath = convertJumpInURL(restPart, ureq); + businessPath = convertJumpInURL(ureq); processBusinessPath(businessPath, ureq, usess); } else if (split.length > 0 && split.length % 2 == 0) { businessPath = BusinessControlFactory.getInstance().formatFromURI(restPart); @@ -265,7 +265,7 @@ public class AuthenticatedDispatcher implements Dispatcher { * @param ureq * @return */ - private String convertJumpInURL(String requestPart, UserRequest ureq) { + private String convertJumpInURL(UserRequest ureq) { String repoId = ureq.getParameter("rid"); String businessPath = "[RepositoryEntry:" + repoId + "]"; String par = ureq.getParameter("par"); diff --git a/src/main/java/org/olat/repository/RepositoryEntry.java b/src/main/java/org/olat/repository/RepositoryEntry.java index 800e03a70345c03c1ba2671e4c96009de75740a5..810bef7a54b20c603d36d5a37e59a479cf00b2c0 100644 --- a/src/main/java/org/olat/repository/RepositoryEntry.java +++ b/src/main/java/org/olat/repository/RepositoryEntry.java @@ -70,8 +70,10 @@ import org.olat.resource.OLATResourceImpl; @NamedQuery(name="getRepositoryEntryRoleAndDefaults", query="select membership.role, relGroup.defaultGroup from repositoryentry as v inner join v.groups as relGroup inner join relGroup.group as baseGroup inner join baseGroup.members as membership where v.key=:repoKey and membership.identity.key=:identityKey"), @NamedQuery(name="filterRepositoryEntryMembership", query="select v.key, membership.identity.key from repositoryentry as v inner join v.groups as relGroup inner join relGroup.group as baseGroup inner join baseGroup.members as membership on membership.role in ('owner','coach','participant') where membership.identity.key=:identityKey and v.key in (:repositoryEntryKey)"), @NamedQuery(name="loadRepositoryEntryByKey", query="select v from repositoryentry as v inner join fetch v.olatResource as ores inner join fetch v.statistics as statistics left join fetch v.lifecycle as lifecycle where v.key = :repoKey"), - @NamedQuery(name="loadRepositoryEntryByResourceKey", query="select v from repositoryentry as v inner join fetch v.olatResource as ores inner join fetch v.statistics as statistics left join fetch v.lifecycle as lifecycle where ores.key = :resourceKey") - + @NamedQuery(name="loadRepositoryEntryByResourceKey", query="select v from repositoryentry as v inner join fetch v.olatResource as ores inner join fetch v.statistics as statistics left join fetch v.lifecycle as lifecycle where ores.key = :resourceKey"), + @NamedQuery(name="getDisplayNameByOlatResourceRedId", query="select v.displayname from repositoryentry v inner join v.olatResource as ores where ores.resId=:resid"), + @NamedQuery(name="getDisplayNameByRepositoryEntryKey", query="select v.displayname from repositoryentry v where v.key=:reKey") + }) public class RepositoryEntry implements CreateInfo, Persistable , RepositoryEntryRef, ModifiedInfo, OLATResourceable { diff --git a/src/main/java/org/olat/repository/RepositoryManager.java b/src/main/java/org/olat/repository/RepositoryManager.java index 825d41963d336d9fa4d408c0122623c630e18966..77df0561f7ad4cc19a8ac9f9d9af7bbe4e0e8a55 100644 --- a/src/main/java/org/olat/repository/RepositoryManager.java +++ b/src/main/java/org/olat/repository/RepositoryManager.java @@ -394,14 +394,10 @@ public class RepositoryManager extends BasicManager { * @return the repositoryentry displayname or null if not found */ public String lookupDisplayNameByOLATResourceableId(Long resId) { - StringBuilder sb = new StringBuilder(); - sb.append("select v.displayname from ").append(RepositoryEntry.class.getName()).append(" v ") - .append(" inner join v.olatResource as ores") - .append(" where ores.resId=:resid"); - List<String> displaynames = dbInstance.getCurrentEntityManager() - .createQuery(sb.toString(), String.class) + .createNamedQuery("getDisplayNameByOlatResourceRedId", String.class) .setParameter("resid", resId) + .setHint("org.hibernate.cacheable", Boolean.TRUE) .getResultList(); if (displaynames.size() > 1) throw new AssertException("Repository lookup returned zero or more than one result: " + displaynames.size()); @@ -416,12 +412,8 @@ public class RepositoryManager extends BasicManager { * @return the repositoryentry displayname or null if not found */ public String lookupDisplayName(Long reId) { - StringBuilder sb = new StringBuilder(); - sb.append("select v.displayname from ").append(RepositoryEntry.class.getName()).append(" v ") - .append(" where v.key=:reKey"); - List<String> displaynames = dbInstance.getCurrentEntityManager() - .createQuery(sb.toString(), String.class) + .createNamedQuery("getDisplayNameByRepositoryEntryKey", String.class) .setParameter("reKey", reId) .setHint("org.hibernate.cacheable", Boolean.TRUE) .getResultList(); diff --git a/src/main/resources/infinispan-config.xml b/src/main/resources/infinispan-config.xml index 03c5e7e7c943bf9f376c476c0e848b776a4f084a..4cadf5f04a84e3982487a0244585bc806970d0cf 100644 --- a/src/main/resources/infinispan-config.xml +++ b/src/main/resources/infinispan-config.xml @@ -1,177 +1,143 @@ <?xml version="1.0" encoding="UTF-8"?> <infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns="urn:infinispan:config:6.0" - xsi:schemaLocation="urn:infinispan:config:6.0 http://www.infinispan.org/schemas/infinispan-config-6.0.xsd"> - <global> - <globalJmxStatistics allowDuplicateDomains="true" /> - </global> + xmlns="urn:infinispan:config:7.2" + xsi:schemaLocation="urn:infinispan:config:7.2 http://www.infinispan.org/schemas/infinispan-config-7.2.xsd"> - <default> - <!-- Used to register JMX statistics in any available MBean server --> - <jmxStatistics enabled="true"/> - </default> - - <!-- Hibernate caches --> - <namedCache name="local-query"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="10000" strategy="LRU"/> - <expiration maxIdle="100000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="timestamps"> - <!-- - <clustering mode="replication"> - <async/> - </clustering> - --> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <!-- Don't ever evict modification timestamps --> - <eviction strategy="NONE"/> - <expiration wakeUpInterval="0"/> - <!-- Explicitly non transactional --> - <transaction transactionMode="NON_TRANSACTIONAL"/> - <!-- State transfer forces all replication calls to be synchronous, - so for calls to remain async, use a cluster cache loader instead --> - <persistence passivation="false"> - <!-- - <cluster remoteCallTimeout="20000" /> - --> - </persistence> - </namedCache> - - <!-- Our own caches --> - <namedCache name="MapperService@mapper"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="1000" strategy="LRU"/> - <expiration maxIdle="300000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="OpenMeetingsManager@session"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="1000" strategy="LRU"/> - <expiration maxIdle="900000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="UserSessionManager@usersession"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction strategy="NONE"/> - <expiration lifespan="-1" maxIdle="-1" wakeUpInterval="0"/> - <transaction transactionMode="TRANSACTIONAL" /> - </namedCache> - - <namedCache name="CalendarManager@calendar"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="500" strategy="LRU"/> - <expiration maxIdle="900000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="AssessmentManager@newpersisting"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="20000" strategy="LRU"/> - <expiration maxIdle="900000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="QTIHelper@QTI_xml_Documents"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="200" strategy="LIRS"/> - <expiration maxIdle="180000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="WebDAVManager@webdav"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="2013" strategy="LRU"/> - <expiration maxIdle="900000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="UserManager@username"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="20000" strategy="LIRS"/> - <expiration maxIdle="2700000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="UserManager@userfullname"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="20000" strategy="LIRS"/> - <expiration maxIdle="2700000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="Velocity@templates"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="7700" strategy="LRU"/> - <expiration lifespan="-1" maxIdle="-1" wakeUpInterval="0"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="LoginModule@blockafterfailedattempts"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="10000" strategy="LRU"/> - <expiration maxIdle="300000" lifespan="300000" wakeUpInterval="5000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> + <cache-container name="NonTransactionalCacheManager" default-cache="default"> + <jmx duplicate-domains="true" /> + <local-cache name="default" /> - <namedCache name="NotificationHelper@userPropertiesCache"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="2000" strategy="LRU"/> - <expiration maxIdle="120000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="GlossaryItemManager@glossary"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="50" strategy="LRU"/> - <expiration maxIdle="1800000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="WikiManager@wiki"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="50" strategy="LRU"/> - <expiration maxIdle="3600000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="CollaborationToolsFactory@tools"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="5000" strategy="LRU"/> - <expiration maxIdle="1800000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="CourseFactory@courses"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="500" strategy="LRU"/> - <expiration maxIdle="3600000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="ProjectBrokerManager@pb"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="50" strategy="LRU"/> - <expiration maxIdle="3600000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="FeedManager@feed"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="1000" strategy="LRU"/> - <expiration maxIdle="900000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> - - <namedCache name="Path@feed"> - <locking isolationLevel="READ_COMMITTED" concurrencyLevel="1000" lockAcquisitionTimeout="15000" useLockStriping="false"/> - <eviction maxEntries="1000" strategy="LRU"/> - <expiration maxIdle="900000" wakeUpInterval="15000"/> - <transaction transactionMode="NON_TRANSACTIONAL" /> - </namedCache> + <local-cache name="MapperService@mapper"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="10000" strategy="LRU" /> + <expiration max-idle="300000" interval="5000" /> + </local-cache> + <local-cache name="OpenMeetingsManager@session"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="10000" strategy="LRU" /> + <expiration max-idle="300000" interval="5000" /> + </local-cache> + + <local-cache name="UserSessionManager@usersession"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction strategy="NONE" /> + <expiration max-idle="-1" interval="5000" /> + </local-cache> + + <local-cache name="CalendarManager@calendar"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="500" strategy="LRU" /> + <expiration max-idle="900000" interval="5000" /> + </local-cache> + + <local-cache name="AssessmentManager@newpersisting"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="20000" strategy="LRU" /> + <expiration max-idle="900000" interval="5000" /> + </local-cache> + + <local-cache name="QTIHelper@QTI_xml_Documents"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="200" strategy="LRU" /> + <expiration max-idle="180000" interval="15000" /> + </local-cache> + + <local-cache name="WebDAVManager@webdav"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="2013" strategy="LRU" /> + <expiration max-idle="900000" interval="5000" /> + </local-cache> + + <local-cache name="UserManager@username"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="20000" strategy="LIRS" /> + <expiration max-idle="2700000" interval="15000" /> + </local-cache> + + <local-cache name="UserManager@userfullname"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="20000" strategy="LIRS" /> + <expiration max-idle="2700000" interval="15000" /> + </local-cache> + + <local-cache name="Velocity@templates"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="7700" strategy="LRU" /> + <expiration max-idle="-1" lifespan="-1" interval="-1" /> + </local-cache> + + <local-cache name="LoginModule@blockafterfailedattempts"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="10000" strategy="LRU" /> + <expiration max-idle="300000" lifespan="300000" interval="5000" /> + </local-cache> + + <local-cache name="NotificationHelper@userPropertiesCache"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="2000" strategy="LRU" /> + <expiration max-idle="120000" interval="15000" /> + </local-cache> + + <local-cache name="GlossaryItemManager@glossary"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="500" strategy="LRU" /> + <expiration max-idle="3600000" interval="15000" /> + </local-cache> + + <local-cache name="WikiManager@wiki"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="500" strategy="LRU" /> + <expiration max-idle="3600000" interval="15000" /> + </local-cache> + + <local-cache name="CollaborationToolsFactory@tools"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="5000" strategy="LRU" /> + <expiration max-idle="1800000" interval="15000" /> + </local-cache> + + <local-cache name="CourseFactory@courses"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="1000" strategy="LRU" /> + <expiration max-idle="3600000" interval="15000" /> + </local-cache> + + <local-cache name="ProjectBrokerManager@pb" > + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="100" strategy="LRU" /> + <expiration max-idle="3600000" interval="15000" /> + </local-cache> + + <local-cache name="FeedManager@feed"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="1000" strategy="LRU" /> + <expiration max-idle="900000" interval="15000" /> + </local-cache> + + <local-cache name="Path@feed"> + <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> + <transaction mode="NONE" auto-commit="true" /> + <eviction max-entries="1000" strategy="LRU" /> + <expiration max-idle="900000" interval="15000" /> + </local-cache> + </cache-container> </infinispan> \ No newline at end of file