Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #2274 - Rationalisation of IoC #2277

Open
wants to merge 92 commits into
base: 2.0.0-SNAPSHOT
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
bf4be9b
#2274 IDomainDrivenTestCaseConfiguration: remove redundant accessors,…
homedirectory Jun 11, 2024
4e0e51c
#2274 TransactionalModule: remove redundant accessors
homedirectory Jun 11, 2024
cb735cf
#2274 Refactor TransactionalModule to be declarative and comprehensible
homedirectory Jun 11, 2024
6b21ba6
#2274 Make DefaultFilter a singleton
homedirectory Jun 11, 2024
c3ec1ee
#2274 [break] CommonEntityDao: use field injection for IFilter instea…
homedirectory Jun 12, 2024
aea8dec
#2274 CommonEntityDao: use method injection uniformly
homedirectory Jun 12, 2024
9ae2627
#2274 IdOnlyProxiedEntityTypeCache: depend on the interface, rather t…
homedirectory Jun 12, 2024
f3fe7b9
#2274 Inject EntityFetcher into CommonEntityDao
homedirectory Jun 12, 2024
0e52b1b
#2274 Enhance IUserProvider with getUsername()
homedirectory Jun 12, 2024
a77ef44
#2274 EntityContainerFetcher: Inject IUserProvider instead of String
homedirectory Jun 12, 2024
0f1671e
#2274 EntityContainerEnhancer: move parameterisation to method level
homedirectory Jun 12, 2024
b03f6d6
#2274 Eliminate usage of QueryExecutionContext in favor of direct dep…
homedirectory Jun 12, 2024
7658c91
#2274 Decouple QuerySourceInfoProvider from IDomainMetadata
homedirectory Jun 13, 2024
c7e555c
#2274 POM: add guice-assistedinject dependency
homedirectory Jun 13, 2024
c24ecd3
#2274 Decouple IDomainMetadata from entity batch insert operations
homedirectory Jun 13, 2024
358ed8b
#2274 Convert TableStructForBatchInsertion to a record
homedirectory Jun 13, 2024
79006d3
#2274 Decouple IDomainMetadata from EqlTable
homedirectory Jun 13, 2024
9f15068
#2274 Encapsulate custom Hibernate type mappings with a dedicated abs…
homedirectory Jun 13, 2024
0303f72
#2274 Adjust PersistentEntity annotation in a test entity
homedirectory Jun 13, 2024
6d7fe7a
#2274 HibernateTypeMappings: use a builder to facilitate customisation
homedirectory Jun 13, 2024
cf5dde9
#2274 Adjust MappingGenerationTest
homedirectory Jun 13, 2024
ebca212
Revert "#2274 [break] CommonEntityDao: use field injection for IFilte…
homedirectory Jun 14, 2024
5578d9b
#2274 [break] Remove Map of default Hibernate types from Guice module…
homedirectory Jun 14, 2024
2d19212
#2274 [break] Remove redundant 'domainTypes' parameter from Guice mod…
homedirectory Jun 14, 2024
62ef067
#2274 Introduce a separate Guice module for entity companion objects
homedirectory Jun 14, 2024
c798a84
#2274 Remove unused EntityFactory accessor from CommonFactoryModule
homedirectory Jun 14, 2024
eae7b12
#2274 [break] Decouple IDomainMetadata from DDL generation
homedirectory Jun 14, 2024
6c6baba
#2274 Decouple IDomainMetadata from DbVersion, introduce IDbVersionPr…
homedirectory Jun 14, 2024
74248d5
#2274 Use dependency injection for CommonEntityDao mixins
homedirectory Jun 14, 2024
0c0380a
#2274 Remove unused fields from CommonEntityDao, deprecate extraneous…
homedirectory Jun 14, 2024
887d0d3
#2274 [break] Remove 'dates' from CommonEntityDao
homedirectory Jun 14, 2024
9529038
#2274 Remove no longer relevant TODOs
homedirectory Jun 17, 2024
7299d7f
#2274 Add missing implementation of DdlGeneratorImpl.generateDatabase…
homedirectory Jun 17, 2024
131bdd3
#2274 Reintroduce `domainTypes` parameter in Guice module constructors
homedirectory Jun 17, 2024
d0c832b
#2274 Hide factory implementations behind interfaces for CommonEntity…
homedirectory Jun 17, 2024
89f21b6
#2274 Rename DefaultFilter -> NoDataFilter
homedirectory Jun 17, 2024
b66ecab
#2274 Hide EntityFetcher implementation behind an interface
homedirectory Jun 17, 2024
8598253
#2274 Remove unnecessary `throws` declarations from Guice module cons…
homedirectory Jun 18, 2024
fdeb005
#2274 Minor optimisation of DdlGenerator implementation
homedirectory Jun 18, 2024
f0d82de
#2274 Remove unused field from EntityBatchInsertOperation
homedirectory Jun 20, 2024
13da3d1
#2274 Reduce visibility of EntityBatchInsertTables
homedirectory Jun 20, 2024
d4b9549
#2274 Minor code style improvements
homedirectory Jun 20, 2024
fd61336
#2274 Replace explicit binding of ICompanionObjectFinder with Impleme…
homedirectory Jun 20, 2024
0b17e22
#2274 Make EntityFetcher a singleton
homedirectory Jun 20, 2024
97ae8ee
#2274 Relocate a static method
homedirectory Jun 20, 2024
bb026d5
#2274 Hide EntityContainerFetcher implementation behind an interface
homedirectory Jun 20, 2024
f42e25c
#2274 Make the default implementation of EntityContainerFetcher a sin…
homedirectory Jun 20, 2024
5d3ee51
#2274 Documentation improvements
homedirectory Jun 20, 2024
020a4ba
Synced with base branch Issue-#2258.
01es Jun 22, 2024
47b8358
#2280 Create a benchmark for entity companion instantiation
homedirectory Jun 26, 2024
938be54
Merge branch 'Issue-#2280' into Issue-#2274
homedirectory Jun 26, 2024
82242a5
#2274 Post-merge: adjust Guice module constructor
homedirectory Jun 26, 2024
5f30b79
#2274 Introduce utility type Lazy for lazy computations
homedirectory Jun 26, 2024
6d6bb38
#2274 Tests for Lazy
homedirectory Jun 26, 2024
71bfb5b
#2274 CommonEntityDao: use Lazy for DeleteOperations and PersistentEn…
homedirectory Jun 26, 2024
da8611e
#2274 Minor improvements to error messages
homedirectory Jun 26, 2024
3a32041
#2274 Embrace immutable lists
homedirectory Jun 26, 2024
06e86e4
#2274 [break] Remove Class<IFilter> parameter from Guice module const…
homedirectory Jun 26, 2024
a002339
#2274 Allow SessionCache annotation target fields and methods
homedirectory Jun 26, 2024
9eac99a
#2274 Clear up SessionCache bindings in Guice modules
homedirectory Jun 27, 2024
3308064
#2280 Replace H2 by PostgreSQL
homedirectory Jun 27, 2024
925090d
#2274 [break] Guice modules: remove constructor parameter used to bin…
homedirectory Jun 27, 2024
723a11e
#2274 [break] Guice modules: remove constructor parameter used to bin…
homedirectory Jun 27, 2024
e547ed8
#2274 Remove redundant class SerialisationClassProvider
homedirectory Jun 27, 2024
f25b6d1
#2274 Refactor EntityModule so it doesn't require an Injector to be set
homedirectory Jun 27, 2024
e5d464a
#2274 Move AuthorisationInterceptor binding to a separate module
homedirectory Jun 27, 2024
64b0335
#2274 EntityFactory to have a final Injector
homedirectory Jun 27, 2024
cb76250
#2274 Delegate injection of Injector into IMetaPropertyFactory to Guice
homedirectory Jun 27, 2024
908ac91
#2274 Rationalise EntityModuleWithPropertyFactory
homedirectory Jun 27, 2024
f2cf9e5
#2274 AbstractMetaPropertyFactory: replace constructor by method inje…
homedirectory Jun 27, 2024
08b930f
#2274 Tidy up javadoc
homedirectory Jun 27, 2024
0743df4
#2274 Replace Singleton in Guice DSL by the Singleton annotation
homedirectory Jun 28, 2024
6e3b428
#2274 Tidy up a Guice module for benchmarking
homedirectory Jun 28, 2024
971cac6
#2274 Extract no-synthetic-method matcher into a reusable utility class
homedirectory Jun 28, 2024
6013d5f
#2274 Don't intercept synthetic methods for authorisation
homedirectory Jun 28, 2024
3aa9ce9
#2274 Test web app server Guice module: remove constructor parameters…
homedirectory Jun 28, 2024
b546543
#2274 Explain why BasicWebServerModule has multiple constructors
homedirectory Jun 28, 2024
24d2817
#2274 More efficient computation of cache keys for proxied types
homedirectory Jul 31, 2024
dca5289
#2274 Add a general predicate to identify id-only proxy types
homedirectory Aug 2, 2024
5527269
Merge branch 'Issue-#2258' into Issue-#2274
homedirectory Aug 5, 2024
f073b4c
Merge branch 'Issue-#2258' into Issue-#2274
homedirectory Aug 7, 2024
c1e3dad
#2274 Tidy up DomainMetadataBuilder by removing redundant operations
homedirectory Aug 7, 2024
3fd391c
Merge branch 'Issue-#2258' into Issue-#2274
homedirectory Aug 22, 2024
7b59f2a
#2274 Post-merge adjustments
homedirectory Aug 22, 2024
9d2aec5
#2274 Add DbVersion parameter to sql() method for stage 3 structures
homedirectory Aug 22, 2024
6528e83
#2274 Merge branch '2.0.0-SNAPSHOT' + conflict resolutions.
01es Sep 6, 2024
8c7a0e0
Merge branch '2.0.0-SNAPSHOT' into Issue-#2274
01es Sep 6, 2024
601b261
Merge branch '2.0.0-SNAPSHOT' into Issue-#2274
01es Sep 6, 2024
f66e1bb
#2274 Cleanup the test properties file.
01es Sep 7, 2024
99d262c
Merge branch '2.0.0-SNAPSHOT' into Issue-#2274
01es Sep 7, 2024
f8f2484
#2274 Refreshed Javadoc.
01es Sep 8, 2024
2bdabec
#2274 Removed Boolean from mappings.
01es Sep 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions platform-benchmark/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${posgresql.version}</version>
<scope>runtime</scope>
</dependency>

</dependencies>
<build>
<resources>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ua.com.fielden.companion;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.util.Modules;
import ua.com.fielden.platform.entity.proxy.IIdOnlyProxiedEntityTypeCache;
import ua.com.fielden.platform.ioc.BasicWebServerModule;
import ua.com.fielden.platform.security.annotations.SessionCache;
import ua.com.fielden.platform.security.annotations.SessionHashingKey;
import ua.com.fielden.platform.security.annotations.TrustedDeviceSessionDuration;
import ua.com.fielden.platform.security.annotations.UntrustedDeviceSessionDuration;
import ua.com.fielden.platform.security.session.UserSession;
import ua.com.fielden.platform.security.user.IUserProvider;
import ua.com.fielden.platform.security.user.impl.ThreadLocalUserProvider;
import ua.com.fielden.platform.serialisation.api.impl.IdOnlyProxiedEntityTypeCacheForTests;

import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

class BenchmarkModule extends BasicWebServerModule {

public static Module newBenchmarkModule(final Properties props) {
return Modules.override(new BenchmarkModule(props))
.with(new AbstractModule() {
@Override
protected void configure() {
// override by a test version because the main one breaks due to an error related to class loaders
bind(IIdOnlyProxiedEntityTypeCache.class).to(IdOnlyProxiedEntityTypeCacheForTests.class);
}
});
}

private BenchmarkModule(final Properties props) {
super(List::of,
List.of(),
props);
}

@Override
protected void configure() {
super.configure();

bindConstant().annotatedWith(SessionHashingKey.class).to("This is a hasing key, which is used to hash session data for a test server.");
bindConstant().annotatedWith(TrustedDeviceSessionDuration.class).to(60 * 24 * 3); // three days
bindConstant().annotatedWith(UntrustedDeviceSessionDuration.class).to(2); // five minutes

bind(IUserProvider.class).to(ThreadLocalUserProvider.class);
}

@Provides
@Singleton
@SessionCache Cache<String, UserSession> provideSessionCache() {
return CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ua.com.fielden.companion;

import com.google.inject.Injector;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import ua.com.fielden.platform.basic.config.Workflows;
import ua.com.fielden.platform.ioc.ApplicationInjectorFactory;
import ua.com.fielden.platform.ioc.NewUserNotifierMockBindingModule;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

import static org.openjdk.jmh.annotations.Threads.MAX;
import static ua.com.fielden.companion.BenchmarkModule.newBenchmarkModule;

/**
* <h3> Running this benchmark </h3>
*
* The following command should be used to run this benchmark, assuming the current working directory is this module (platform-benchmark).
* <pre>
java -jar target/benchmarks.jar \
-p propertiesFile="src/main/resources/CompanionInstantiationBenchmark.properties" \
-prof gc \
"ua.com.fielden.companion.CompanionInstantiationBenchmark"
</pre>
*/
@Fork(value = 3)
@Warmup(iterations = 2)
@Measurement(iterations = 5)
@Threads(MAX)
@State(Scope.Benchmark)
public class CompanionInstantiationBenchmark {

// required for correct initialisation of Guice modules
@Param("") String propertiesFile;

private Injector injector;

@Setup(Level.Trial)
public void setup() throws IOException {
if (!Files.isReadable(Path.of(propertiesFile))) {
throw new IllegalStateException("Can't read file: %s".formatted(propertiesFile));
}

final var properties = new Properties();
try (final var in = new FileInputStream(propertiesFile)) {
properties.load(in);
}

injector = new ApplicationInjectorFactory(Workflows.development)
.add(newBenchmarkModule(properties))
.add(new NewUserNotifierMockBindingModule())
.getInjector();

// Need to initialise singleton dependencies of companions. Can't enable eager singletons for the whole injector
// because stuff starts breaking due to incomplete module configuration. Therefore, manually instantiata a companion
// here to force initialisation of singletons.
injector.getInstance(Entity1Dao.class);
}

private <T> T getInstance(final Class<T> type) {
return injector.getInstance(type);
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Measurement(iterations = 20, time = 5)
public void lightCo(final Blackhole blackhole) {
for (int i = 0; i < 1000; i++) {
blackhole.consume(getInstance(Entity1Dao.class));
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Measurement(iterations = 20, time = 5)
public void heavyCo(final Blackhole blackhole) {
for (int i = 0; i < 1000; i++) {
blackhole.consume(getInstance(Entity2Dao.class));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ua.com.fielden.companion;

import ua.com.fielden.platform.entity.AbstractEntity;
import ua.com.fielden.platform.entity.annotation.CompanionObject;
import ua.com.fielden.platform.entity.annotation.KeyType;
import ua.com.fielden.platform.entity.annotation.MapEntityTo;

@CompanionObject(Entity1Co.class)
@KeyType(String.class)
@MapEntityTo
class Entity1 extends AbstractEntity<String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ua.com.fielden.companion;

import ua.com.fielden.platform.dao.IEntityDao;

interface Entity1Co extends IEntityDao<Entity1> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ua.com.fielden.companion;

import com.google.inject.Inject;
import ua.com.fielden.platform.dao.CommonEntityDao;
import ua.com.fielden.platform.entity.annotation.EntityType;
import ua.com.fielden.platform.entity.query.IFilter;

@EntityType(Entity1.class)
class Entity1Dao extends CommonEntityDao<Entity1> implements Entity1Co {

@Inject
public Entity1Dao(final IFilter filter) {
super(filter);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ua.com.fielden.companion;

import ua.com.fielden.platform.entity.AbstractEntity;
import ua.com.fielden.platform.entity.annotation.CompanionObject;
import ua.com.fielden.platform.entity.annotation.KeyType;
import ua.com.fielden.platform.entity.annotation.MapEntityTo;

@CompanionObject(Entity2Co.class)
@KeyType(String.class)
@MapEntityTo
class Entity2 extends AbstractEntity<String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ua.com.fielden.companion;

import ua.com.fielden.platform.dao.IEntityDao;

interface Entity2Co extends IEntityDao<Entity2> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ua.com.fielden.companion;

import com.google.inject.Inject;
import ua.com.fielden.platform.dao.CommonEntityDao;
import ua.com.fielden.platform.dao.IEntityAggregatesOperations;
import ua.com.fielden.platform.entity.annotation.EntityType;
import ua.com.fielden.platform.entity.query.IFilter;
import ua.com.fielden.platform.security.user.IUser;
import ua.com.fielden.platform.security.user.IUserProvider;
import ua.com.fielden.platform.utils.IDates;

@EntityType(Entity2.class)
class Entity2Dao extends CommonEntityDao<Entity2> implements Entity2Co {

private final IUserProvider userProvider;
private final IEntityAggregatesOperations aggregatesOps;
private final IUser userCo;
private final IDates dates;

@Inject
public Entity2Dao(final IFilter filter,
final IUserProvider userProvider,
final IEntityAggregatesOperations aggregatesOps,
final IUser userCo,
final IDates dates) {
super(filter);
this.userProvider = userProvider;
this.aggregatesOps = aggregatesOps;
this.userCo = userCo;
this.dates = dates;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import jakarta.inject.Singleton;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

Expand All @@ -15,6 +16,7 @@
* @author TG Team
*
*/
@Singleton
class DatesForBenchmarking implements IDates {
private DateTime now;
private Supplier<DateTime> timeSupplier;
Expand Down Expand Up @@ -86,4 +88,4 @@ public int startOfWeek() {
return 1;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
package ua.com.fielden.deserialisation;

import com.google.inject.Injector;
import com.google.inject.Scopes;
import com.google.inject.name.Names;

import ua.com.fielden.platform.basic.config.IApplicationDomainProvider;
import ua.com.fielden.platform.entity.factory.DefaultCompanionObjectFinderImpl;
import ua.com.fielden.platform.entity.factory.EntityFactory;
import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder;
import ua.com.fielden.platform.entity.factory.IMetaPropertyFactory;
import ua.com.fielden.platform.entity.ioc.EntityModule;
import ua.com.fielden.platform.entity.meta.AbstractMetaPropertyFactory;
import ua.com.fielden.platform.entity.meta.DomainMetaPropertyConfig;
import ua.com.fielden.platform.entity.validation.DomainValidationConfig;
import ua.com.fielden.platform.entity.validation.HappyValidator;
import ua.com.fielden.platform.entity.validation.IBeforeChangeEventHandler;
import ua.com.fielden.platform.entity.validation.annotation.EntityExists;
import ua.com.fielden.platform.utils.IDates;
import ua.com.fielden.platform.utils.IUniversalConstants;
import ua.com.fielden.platform.web.test.config.ApplicationDomain;
Expand All @@ -29,15 +17,7 @@
*/
class EntityModuleWithPropertyFactoryForBenchmarking extends EntityModule {

protected final EntityFactory entityFactory;

public EntityModuleWithPropertyFactoryForBenchmarking() {
entityFactory = new EntityFactory() {
};
}

private final DomainValidationConfig domainValidationConfig = new DomainValidationConfig();
private final DomainMetaPropertyConfig domainMetaPropertyConfig = new DomainMetaPropertyConfig();
public EntityModuleWithPropertyFactoryForBenchmarking() {}

/**
*
Expand All @@ -46,43 +26,18 @@ public EntityModuleWithPropertyFactoryForBenchmarking() {
@Override
protected void configure() {
super.configure();
bind(EntityFactory.class).toInstance(entityFactory);
//////////////////////////////////////////////
//////////// bind property factory ///////////
//////////////////////////////////////////////
bind(IMetaPropertyFactory.class).toInstance(new AbstractMetaPropertyFactory(domainValidationConfig, domainMetaPropertyConfig, new DatesForBenchmarking()) {

@Override
protected IBeforeChangeEventHandler createEntityExists(final EntityExists anotation) {
return new HappyValidator();
}

});
bind(IMetaPropertyFactory.class).to(MetaPropertyFactoryForBenchmarking.class);

bindConstant().annotatedWith(Names.named("app.name")).to("Unit Tests");
bindConstant().annotatedWith(Names.named("email.smtp")).to("192.168.1.8");
bindConstant().annotatedWith(Names.named("email.fromAddress")).to("[email protected]");

bind(IApplicationDomainProvider.class).to(ApplicationDomain.class);
bind(ICompanionObjectFinder.class).to(DefaultCompanionObjectFinderImpl.class).in(Scopes.SINGLETON);
bind(IDates.class).to(DatesForBenchmarking.class).in(Scopes.SINGLETON);
bind(IUniversalConstants.class).to(UniversalConstantsForBenchmarking.class).in(Scopes.SINGLETON);
}

public DomainValidationConfig getDomainValidationConfig() {
return domainValidationConfig;
}

public DomainMetaPropertyConfig getDomainMetaPropertyConfig() {
return domainMetaPropertyConfig;
}

@Override
public void setInjector(final Injector injector) {
super.setInjector(injector);
entityFactory.setInjector(injector);
final IMetaPropertyFactory mfp = injector.getInstance(IMetaPropertyFactory.class);
mfp.setInjector(injector);
bind(IDates.class).to(DatesForBenchmarking.class);
bind(IUniversalConstants.class).to(UniversalConstantsForBenchmarking.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ua.com.fielden.deserialisation;

import com.google.inject.Inject;
import jakarta.inject.Singleton;
import ua.com.fielden.platform.entity.meta.AbstractMetaPropertyFactory;
import ua.com.fielden.platform.entity.validation.HappyValidator;
import ua.com.fielden.platform.entity.validation.IBeforeChangeEventHandler;
import ua.com.fielden.platform.entity.validation.annotation.EntityExists;

@Singleton
final class MetaPropertyFactoryForBenchmarking extends AbstractMetaPropertyFactory {

@Inject
public MetaPropertyFactoryForBenchmarking() {}

@Override
protected IBeforeChangeEventHandler createEntityExists(final EntityExists anotation) {
return new HappyValidator();
}

}
Loading