From 446717da97a283f777138c0289f080030c1123ce Mon Sep 17 00:00:00 2001 From: gkolokythas Date: Wed, 25 Sep 2019 13:43:17 +0300 Subject: [PATCH] Adds "Creation User" on DataRepository, ExternalDataset, Registry, Service. (Issue #168) --- .../eudat/data/entities/DataRepository.java | 29 +++++++---------- .../eudat/data/entities/ExternalDataset.java | 20 +++++++----- .../java/eu/eudat/data/entities/Registry.java | 29 ++++++----------- .../java/eu/eudat/data/entities/Service.java | 31 +++++++------------ .../eudat/controllers/DataRepositories.java | 2 +- .../eudat/controllers/ExternalDatasets.java | 2 +- .../java/eu/eudat/controllers/Registries.java | 2 +- .../java/eu/eudat/controllers/Services.java | 2 +- .../logic/managers/DataRepositoryManager.java | 6 ++-- .../managers/ExternalDatasetManager.java | 5 +-- .../eudat/logic/managers/RegistryManager.java | 6 ++-- .../eudat/logic/managers/ServiceManager.java | 6 ++-- .../datarepository/DataRepositoryModel.java | 8 ++--- .../externaldataset/ExternalDatasetModel.java | 2 ++ .../models/data/registries/RegistryModel.java | 8 ++--- .../models/data/services/ServiceModel.java | 2 ++ ...itory_ExternalDataset_Registry_Service.sql | 11 +++++++ 17 files changed, 80 insertions(+), 91 deletions(-) create mode 100644 dmp-db-scema/updates/04/Add_creationUser_on_DataRepository_ExternalDataset_Registry_Service.sql diff --git a/dmp-backend/data/src/main/java/eu/eudat/data/entities/DataRepository.java b/dmp-backend/data/src/main/java/eu/eudat/data/entities/DataRepository.java index 9559d0490..0d9d7a893 100644 --- a/dmp-backend/data/src/main/java/eu/eudat/data/entities/DataRepository.java +++ b/dmp-backend/data/src/main/java/eu/eudat/data/entities/DataRepository.java @@ -36,11 +36,9 @@ public class DataRepository implements Serializable, DataEntity datasetDataRepositories; - @Column(name = "\"Status\"", nullable = false) private Short status; @@ -50,41 +48,35 @@ public class DataRepository implements Serializable, DataEntity getDatasetDataRepositories() { return datasetDataRepositories; } - public void setDatasetDataRepositories(Set datasetDataRepositories) { this.datasetDataRepositories = datasetDataRepositories; } + public UserInfo getCreationUser() { + return creationUser; + } + public void setCreationUser(UserInfo creationUser) { + this.creationUser = creationUser; + } + @Override public void update(DataRepository entity) { diff --git a/dmp-backend/data/src/main/java/eu/eudat/data/entities/ExternalDataset.java b/dmp-backend/data/src/main/java/eu/eudat/data/entities/ExternalDataset.java index 51d5e474e..199bacff3 100644 --- a/dmp-backend/data/src/main/java/eu/eudat/data/entities/ExternalDataset.java +++ b/dmp-backend/data/src/main/java/eu/eudat/data/entities/ExternalDataset.java @@ -40,10 +40,14 @@ public class ExternalDataset implements DataEntity { @OneToMany(mappedBy = "externalDataset", cascade = CascadeType.ALL, orphanRemoval = true) private Set datasets; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "\"CreationUser\"", nullable = true) + private UserInfo creationUser; + + public UUID getId() { return id; } - public void setId(UUID id) { this.id = id; } @@ -51,7 +55,6 @@ public class ExternalDataset implements DataEntity { public String getLabel() { return label; } - public void setLabel(String label) { this.label = label; } @@ -59,7 +62,6 @@ public class ExternalDataset implements DataEntity { public String getAbbreviation() { return abbreviation; } - public void setAbbreviation(String abbreviation) { this.abbreviation = abbreviation; } @@ -67,7 +69,6 @@ public class ExternalDataset implements DataEntity { public String getReference() { return reference; } - public void setReference(String reference) { this.reference = reference; } @@ -75,7 +76,6 @@ public class ExternalDataset implements DataEntity { public Date getCreated() { return created; } - public void setCreated(Date created) { this.created = created; } @@ -83,7 +83,6 @@ public class ExternalDataset implements DataEntity { public Date getModified() { return modified; } - public void setModified(Date modified) { this.modified = modified; } @@ -91,16 +90,23 @@ public class ExternalDataset implements DataEntity { public Set getDatasets() { return datasets; } - public void setDatasets(Set datasets) { this.datasets = datasets; } + public UserInfo getCreationUser() { + return creationUser; + } + public void setCreationUser(UserInfo creationUser) { + this.creationUser = creationUser; + } + @Override public void update(ExternalDataset entity) { this.label = entity.getLabel(); this.abbreviation = entity.getAbbreviation(); this.modified = new Date(); + this.creationUser = entity.getCreationUser(); } @Override diff --git a/dmp-backend/data/src/main/java/eu/eudat/data/entities/Registry.java b/dmp-backend/data/src/main/java/eu/eudat/data/entities/Registry.java index dafef8ad0..6a40378cd 100644 --- a/dmp-backend/data/src/main/java/eu/eudat/data/entities/Registry.java +++ b/dmp-backend/data/src/main/java/eu/eudat/data/entities/Registry.java @@ -17,7 +17,6 @@ import java.util.UUID; @Table(name = "\"Registry\"") public class Registry implements DataEntity { - @Id @Column(name = "\"ID\"", updatable = false, nullable = false, columnDefinition = "BINARY(16)") private UUID id; @@ -38,7 +37,6 @@ public class Registry implements DataEntity { @Column(name = "\"Definition\"", columnDefinition = "xml", nullable = true) private String definition; - @OneToMany(fetch = FetchType.LAZY) @JoinTable(name = "\"DatasetRegistry\"", joinColumns = {@JoinColumn(name = "\"Registry\"", referencedColumnName = "\"ID\"")}, @@ -46,11 +44,9 @@ public class Registry implements DataEntity { ) private Set datasets; - @Column(name = "\"Status\"", nullable = false) private Short status; - @Column(name = "\"Created\"") @Convert(converter = DateToUTCConverter.class) private Date created = null; @@ -59,32 +55,28 @@ public class Registry implements DataEntity { @Convert(converter = DateToUTCConverter.class) private Date modified = new Date(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "\"CreationUser\"", nullable = true) + private UserInfo creationUser; + public Short getStatus() { return status; } - - public void setStatus(Short status) { this.status = status; } - public Date getCreated() { return created; } - - public void setCreated(Date created) { this.created = created; } - public Date getModified() { return modified; } - - public void setModified(Date modified) { this.modified = modified; } @@ -92,7 +84,6 @@ public class Registry implements DataEntity { public UUID getId() { return id; } - public void setId(UUID id) { this.id = id; } @@ -100,7 +91,6 @@ public class Registry implements DataEntity { public String getLabel() { return label; } - public void setLabel(String label) { this.label = label; } @@ -108,7 +98,6 @@ public class Registry implements DataEntity { public String getAbbreviation() { return abbreviation; } - public void setAbbreviation(String abbreviation) { this.abbreviation = abbreviation; } @@ -116,7 +105,6 @@ public class Registry implements DataEntity { public String getReference() { return reference; } - public void setReference(String reference) { this.reference = reference; } @@ -124,7 +112,6 @@ public class Registry implements DataEntity { public String getUri() { return uri; } - public void setUri(String uri) { this.uri = uri; } @@ -132,7 +119,6 @@ public class Registry implements DataEntity { public String getDefinition() { return definition; } - public void setDefinition(String definition) { this.definition = definition; } @@ -140,11 +126,16 @@ public class Registry implements DataEntity { public Set getDatasets() { return datasets; } - public void setDatasets(Set datasets) { this.datasets = datasets; } + public UserInfo getCreationUser() { + return creationUser; + } + public void setCreationUser(UserInfo creationUser) { + this.creationUser = creationUser; + } @Override public void update(Registry entity) { diff --git a/dmp-backend/data/src/main/java/eu/eudat/data/entities/Service.java b/dmp-backend/data/src/main/java/eu/eudat/data/entities/Service.java index 14ca76f60..c71977292 100644 --- a/dmp-backend/data/src/main/java/eu/eudat/data/entities/Service.java +++ b/dmp-backend/data/src/main/java/eu/eudat/data/entities/Service.java @@ -20,7 +20,6 @@ public class Service implements DataEntity { @Column(name = "\"ID\"", updatable = false, nullable = false, columnDefinition = "BINARY(16)") private UUID id; - @Column(name = "\"Label\"") private String label; @@ -37,15 +36,12 @@ public class Service implements DataEntity { @Column(name = "\"Definition\"", columnDefinition = "xml", nullable = false) private String definition; - @OneToMany(mappedBy = "service", cascade = CascadeType.ALL, orphanRemoval = true) private Set services; - @Column(name = "\"Status\"", nullable = false) private Short status; - @Column(name = "\"Created\"") @Convert(converter = DateToUTCConverter.class) private Date created = null; @@ -54,41 +50,35 @@ public class Service implements DataEntity { @Convert(converter = DateToUTCConverter.class) private Date modified = new Date(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "\"CreationUser\"", nullable = true) + private UserInfo creationUser; + public Short getStatus() { return status; } - - public void setStatus(Short status) { this.status = status; } - public Date getCreated() { return created; } - - public void setCreated(Date created) { this.created = created; } - public Date getModified() { return modified; } - - public void setModified(Date modified) { this.modified = modified; } - public UUID getId() { return id; } - public void setId(UUID id) { this.id = id; } @@ -96,7 +86,6 @@ public class Service implements DataEntity { public String getLabel() { return label; } - public void setLabel(String label) { this.label = label; } @@ -104,7 +93,6 @@ public class Service implements DataEntity { public String getAbbreviation() { return abbreviation; } - public void setAbbreviation(String abbreviation) { this.abbreviation = abbreviation; } @@ -112,7 +100,6 @@ public class Service implements DataEntity { public String getReference() { return reference; } - public void setReference(String reference) { this.reference = reference; } @@ -120,7 +107,6 @@ public class Service implements DataEntity { public String getUri() { return uri; } - public void setUri(String uri) { this.uri = uri; } @@ -128,7 +114,6 @@ public class Service implements DataEntity { public String getDefinition() { return definition; } - public void setDefinition(String definition) { this.definition = definition; } @@ -136,11 +121,17 @@ public class Service implements DataEntity { public Set getServices() { return services; } - public void setServices(Set services) { this.services = services; } + public UserInfo getCreationUser() { + return creationUser; + } + public void setCreationUser(UserInfo creationUser) { + this.creationUser = creationUser; + } + @Override public void update(Service entity) { diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/DataRepositories.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/DataRepositories.java index 68e0d87f6..783d26a83 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/DataRepositories.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/DataRepositories.java @@ -47,7 +47,7 @@ public class DataRepositories extends BaseController { @RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity> create(@RequestBody eu.eudat.models.data.datarepository.DataRepositoryModel dataRepositoryModel, Principal principal) throws Exception { - DataRepository dataRepository = this.dataRepositoryManager.create(dataRepositoryModel); + DataRepository dataRepository = this.dataRepositoryManager.create(dataRepositoryModel, principal); return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(dataRepository).status(ApiMessageCode.SUCCESS_MESSAGE)); } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/ExternalDatasets.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/ExternalDatasets.java index c118de055..040823492 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/ExternalDatasets.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/ExternalDatasets.java @@ -63,7 +63,7 @@ public class ExternalDatasets extends BaseController { @RequestMapping(method = RequestMethod.POST, value = {"/externaldatasets"}, consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity> create(@RequestBody eu.eudat.models.data.externaldataset.ExternalDatasetModel externalDatasetModel, Principal principal) throws Exception { - ExternalDataset externalDataset = this.externalDatasetManager.create(externalDatasetModel); + ExternalDataset externalDataset = this.externalDatasetManager.create(externalDatasetModel, principal); return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(externalDataset).status(ApiMessageCode.SUCCESS_MESSAGE)); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/Registries.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/Registries.java index 1134d4317..ead80898b 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/Registries.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/Registries.java @@ -45,7 +45,7 @@ public class Registries extends BaseController { @RequestMapping(method = RequestMethod.POST, value = {"/registries"}, consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity> create(@RequestBody RegistryModel registryModel, Principal principal) throws Exception { - Registry registry = this.registryManager.create(registryModel); + Registry registry = this.registryManager.create(registryModel, principal); return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(registry).status(ApiMessageCode.SUCCESS_MESSAGE)); } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/Services.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/Services.java index 36b2703cb..2a40b6f37 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/Services.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/Services.java @@ -45,7 +45,7 @@ public class Services extends BaseController { @RequestMapping(method = RequestMethod.POST, value = {"/services"}, consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity> create(@RequestBody ServiceModel serviceModel, Principal principal) throws Exception { - Service service = serviceManager.create(serviceModel); + Service service = serviceManager.create(serviceModel, principal); return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(service).status(ApiMessageCode.SUCCESS_MESSAGE)); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataRepositoryManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataRepositoryManager.java index 53d46bc1a..dc8a7b0f8 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataRepositoryManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataRepositoryManager.java @@ -1,9 +1,8 @@ package eu.eudat.logic.managers; import eu.eudat.data.entities.DataRepository; -import eu.eudat.data.entities.Researcher; import eu.eudat.logic.services.ApiContext; -import eu.eudat.models.data.datarepository.DataRepositoryModel; +import eu.eudat.models.data.security.Principal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -19,8 +18,9 @@ public class DataRepositoryManager { this.apiContext = apiContext; } - public DataRepository create(eu.eudat.models.data.datarepository.DataRepositoryModel dataRepositoryModel) throws Exception { + public DataRepository create(eu.eudat.models.data.datarepository.DataRepositoryModel dataRepositoryModel, Principal principal) throws Exception { DataRepository dataRepository = dataRepositoryModel.toDataModel(); + dataRepository.getCreationUser().setId(principal.getId()); return apiContext.getOperationsContext().getDatabaseRepository().getDataRepositoryDao().createOrUpdate(dataRepository); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ExternalDatasetManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ExternalDatasetManager.java index cab3d39b4..8c6417c20 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ExternalDatasetManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ExternalDatasetManager.java @@ -2,7 +2,6 @@ package eu.eudat.logic.managers; import eu.eudat.logic.builders.model.criteria.ExternalDatasetCriteriaBuilder; import eu.eudat.logic.builders.model.models.DataTableDataBuilder; -import eu.eudat.data.dao.entities.ExternalDatasetDao; import eu.eudat.data.entities.ExternalDataset; import eu.eudat.data.dao.criteria.ExternalDatasetCriteria; import eu.eudat.logic.services.operations.DatabaseRepository; @@ -12,6 +11,7 @@ import eu.eudat.models.data.helpers.common.DataTableData; import eu.eudat.logic.proxy.config.exceptions.HugeResultSet; import eu.eudat.logic.proxy.config.exceptions.NoURLFound; import eu.eudat.logic.proxy.fetching.RemoteFetcher; +import eu.eudat.models.data.security.Principal; import eu.eudat.queryable.QueryableList; import eu.eudat.logic.services.ApiContext; import org.springframework.beans.factory.annotation.Autowired; @@ -54,8 +54,9 @@ public class ExternalDatasetManager { return externalDatasetModel; } - public ExternalDataset create(eu.eudat.models.data.externaldataset.ExternalDatasetModel externalDatasetModel) throws Exception { + public ExternalDataset create(eu.eudat.models.data.externaldataset.ExternalDatasetModel externalDatasetModel, Principal principal) throws Exception { ExternalDataset externalDataset = externalDatasetModel.toDataModel(); + externalDataset.getCreationUser().setId(principal.getId()); return apiContext.getOperationsContext().getDatabaseRepository().getExternalDatasetDao().createOrUpdate(externalDataset); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/RegistryManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/RegistryManager.java index 33016f094..e31d08813 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/RegistryManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/RegistryManager.java @@ -1,10 +1,9 @@ package eu.eudat.logic.managers; -import eu.eudat.data.entities.DataRepository; import eu.eudat.data.entities.Registry; import eu.eudat.logic.services.ApiContext; -import eu.eudat.models.data.datarepository.DataRepositoryModel; import eu.eudat.models.data.registries.RegistryModel; +import eu.eudat.models.data.security.Principal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -18,8 +17,9 @@ public class RegistryManager { this.apiContext = apiContext; } - public Registry create(RegistryModel registryModel) throws Exception { + public Registry create(RegistryModel registryModel, Principal principal) throws Exception { Registry registry = registryModel.toDataModel(); + registry.getCreationUser().setId(principal.getId()); return apiContext.getOperationsContext().getDatabaseRepository().getRegistryDao().createOrUpdate(registry); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ServiceManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ServiceManager.java index 109c972a8..e86b45b74 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ServiceManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/ServiceManager.java @@ -1,9 +1,8 @@ package eu.eudat.logic.managers; -import eu.eudat.data.entities.Registry; import eu.eudat.data.entities.Service; import eu.eudat.logic.services.ApiContext; -import eu.eudat.models.data.registries.RegistryModel; +import eu.eudat.models.data.security.Principal; import eu.eudat.models.data.services.ServiceModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -21,8 +20,9 @@ public class ServiceManager { this.apiContext = apiContext; } - public Service create(ServiceModel serviceModel) throws Exception { + public Service create(ServiceModel serviceModel, Principal principal) throws Exception { Service service = serviceModel.toDataModel(); + service.getCreationUser().setId(principal.getId()); return apiContext.getOperationsContext().getDatabaseRepository().getServiceDao().createOrUpdate(service); } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/datarepository/DataRepositoryModel.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/datarepository/DataRepositoryModel.java index 6a5984ed9..dee16ecbd 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/data/datarepository/DataRepositoryModel.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/datarepository/DataRepositoryModel.java @@ -1,6 +1,7 @@ package eu.eudat.models.data.datarepository; import eu.eudat.data.entities.DataRepository; +import eu.eudat.data.entities.UserInfo; import eu.eudat.models.DataModel; import java.util.Date; @@ -20,7 +21,6 @@ public class DataRepositoryModel implements DataModel { public UUID getId() { return id; } - public void setId(UUID id) { this.id = id; } @@ -28,7 +28,6 @@ public class RegistryModel implements DataModel { public String getLabel() { return label; } - public void setLabel(String label) { this.label = label; } @@ -36,7 +35,6 @@ public class RegistryModel implements DataModel { public String getAbbreviation() { return abbreviation; } - public void setAbbreviation(String abbreviation) { this.abbreviation = abbreviation; } @@ -44,7 +42,6 @@ public class RegistryModel implements DataModel { public String getUri() { return uri; } - public void setUri(String uri) { this.uri = uri; } @@ -52,7 +49,6 @@ public class RegistryModel implements DataModel { public Date getCreated() { return created; } - public void setCreated(Date created) { this.created = created; } @@ -60,7 +56,6 @@ public class RegistryModel implements DataModel { public Date getModified() { return modified; } - public void setModified(Date modified) { this.modified = modified; } @@ -87,6 +82,7 @@ public class RegistryModel implements DataModel { registry.setModified(new Date()); registry.setReference("dmpdata/" + registry.getId()); registry.setStatus((short)0); + registry.setCreationUser(new UserInfo()); return registry; } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/services/ServiceModel.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/services/ServiceModel.java index d518ac8eb..66e913202 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/data/services/ServiceModel.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/services/ServiceModel.java @@ -1,6 +1,7 @@ package eu.eudat.models.data.services; import eu.eudat.data.entities.Service; +import eu.eudat.data.entities.UserInfo; import eu.eudat.models.DataModel; import java.util.Date; @@ -88,6 +89,7 @@ public class ServiceModel implements DataModel { service.setReference("innerdata/" + service.getId()); service.setModified(new Date()); service.setStatus((short)0); + service.setCreationUser(new UserInfo()); return service; } diff --git a/dmp-db-scema/updates/04/Add_creationUser_on_DataRepository_ExternalDataset_Registry_Service.sql b/dmp-db-scema/updates/04/Add_creationUser_on_DataRepository_ExternalDataset_Registry_Service.sql new file mode 100644 index 000000000..4cc036bdb --- /dev/null +++ b/dmp-db-scema/updates/04/Add_creationUser_on_DataRepository_ExternalDataset_Registry_Service.sql @@ -0,0 +1,11 @@ +ALTER TABLE "DataRepository" +ADD COLUMN "CreationUser" uuid + +ALTER TABLE "ExternalDataset" +ADD COLUMN "CreationUser" uuid + +ALTER TABLE "Registry" +ADD COLUMN "CreationUser" uuid + +ALTER TABLE "Service" +ADD COLUMN "CreationUser" uuid \ No newline at end of file