I would like to show data from two different objects in one Grid. I have "folders" and "items" and I would like to see them together in one Grid. Folders at the top and items below folders. Something like list view in file manager application. But I don't know how to get the data together. I could probably create an abstract parent class for Item and Folder classes with getter methods, which I'm using in the grid. Is there any better solution for that?
Expected result:
#Route(value = "")
#PageTitle("Items | Test")
public class ListView extends VerticalLayout {
Grid<Item> grid = new Grid<>(Item.class);
ItemService service;
Folder currentFolder;
public ListView(ItemService service) {
this.service = service;
this.currentFolder = service.getAllFolders().get(0);
addClassName("list-view");
add(getGrid());
updateList();
}
private HorizontalLayout getGrid() {
HorizontalLayout layout = new HorizontalLayout(grid);
layout.setSizeFull();
grid.addClassNames("grid");
grid.setColumns("name");
grid.addColumn(testCase -> testCase.getStatus().getValue()).setHeader("Status").setSortable(true);
grid.addColumn(new ComponentRenderer<>(
testCase -> {
Checkbox checkbox = new Checkbox();
checkbox.setValue(testCase.getBooleanValue());
checkbox.setReadOnly(true);
return checkbox;
}
)
).setHeader("Boolean");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
return layout;
}
private void updateList() {
grid.setItems(service.getItemsFromFolder(currentFolder));
}
}
Service:
#Service
public class ItemService {
private final ItemRepository itemRepository;
private final FolderRepository folderRepository;
public ItemService(ItemRepository itemRepository, FolderRepository folderRepository) {
this.itemRepository = itemRepository;
this.folderRepository = folderRepository;
}
public List<Folder> getAllFolders() {
return folderRepository.findAll();
}
public List<Item> getItemsFromFolder(Folder folder) {
return itemRepository.getItemsFromFolder(folder.getId());
}
}
Item Repository:
public interface ItemRepository extends JpaRepository<Item, Long> {
#Query("Select i from Item i where i.folder.id = :folderId")
List<Item> getItemsFromFolder(#Param("folderId") Long folderId);
}
Folder Repository:
public interface FolderRepository extends JpaRepository<Folder, Long> {
}
Item Entity:
#Entity
#Getter
#Setter
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String name = "";
#Enumerated
#Column(columnDefinition = "smallint")
private Status status;
#NotNull
private Boolean booleanValue;
#ManyToOne
#JoinColumn(name = "folder_id")
#NotNull
private Folder folder;
}
Folder Entity:
#Entity
#Getter
#Setter
public class Folder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long parentFolderId;
}
Status enum:
#AllArgsConstructor
public enum Status {
DRAFT("Draft"),
READY("Ready"),
OBSOLETE("Obsolete");
#Getter
private final String value;
}
Grid can only have one type of bean, so an abstract parent class or interface is the way to go. But given the description of your use case of "folders" and "items", have you considered using TreeGrid instead?
Related
I am writing a PUT request API with spring and mongodb. But the save() inserts a new object instead of update the current one.
#Document("Test")
public class Expense {
#Field(name = "name")
private String expenseName;
#Field(name = "category")
private ExpenseCategory expenseCategory;
#Field(name = "amount")
private BigDecimal expenseAmount;
public Expense( String expenseName, ExpenseCategory expenseCategory, BigDecimal expenseAmount) {
this.expenseName = expenseName;
this.expenseCategory = expenseCategory;
this.expenseAmount = expenseAmount;
}
public String getExpenseName() {
return expenseName;
}
public void setExpenseName(String expenseName) {
this.expenseName = expenseName;
}
public ExpenseCategory getExpenseCategory() {
return expenseCategory;
}
public void setExpenseCategory(ExpenseCategory expenseCategory) {
this.expenseCategory = expenseCategory;
}
public BigDecimal getExpenseAmount() {
return expenseAmount;
}
public void setExpenseAmount(BigDecimal expenseAmount) {
this.expenseAmount = expenseAmount;
}
}
This is my reporsitory class
public interface ExpenseRepository extends MongoRepository<Expense, String> {
}
This is my Service class which shows how to update the class.
#Service
public class ExpenseService {
private final ExpenseRepository expenseRepository;
public ExpenseService(ExpenseRepository expenseRepository) {
this.expenseRepository = expenseRepository;
}
public void updateExpense(String id, Expense expense){
Expense savedExpense = expenseRepository.findById(id)
.orElseThrow(() -> new RuntimeException(
String.format("Cannot Find Expense by ID %s", id)));
savedExpense.setExpenseName(expense.getExpenseName());
savedExpense.setExpenseAmount(expense.getExpenseAmount());
savedExpense.setExpenseCategory(expense.getExpenseCategory());
expenseRepository.save(savedExpense);
}
}
This is my controller
#RestController
#RequestMapping("/api/expense")
public class ExpenseController {
private final ExpenseService expenseService;
public ExpenseController(ExpenseService expenseService) {
this.expenseService = expenseService;
}
#PutMapping("/{id}")
public ResponseEntity<Object> updateExpense(#PathVariable String id, #RequestBody Expense expense){
expenseService.updateExpense(id, expense);
return ResponseEntity.ok().build();
}
}
As shown in mongodb compass, mongodb auto generates an _id field for every object. So I do not define a id field or use #id annotation to define a primary for the collection. However, in the service class, expenseRepository.findById(id) retrieves the desired object and update it. Why does save() do the insert instead of update? Many thanks.
JPA Can't find the existing entry as no id field id set. You need to add an id field and set generation type to auto.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
I have an ExampleRequest entity that can optionally have one or more ExampleRequestYear. It's currently configured this way (unrelated fields and gettters/setters omitted for brevity, please let me know if you need anything else):
#Entity
#Table(name = "EXAMPLE_REQUEST")
#SequenceGenerator(name = "EXAMPLE_REQUEST_ID_SEQ", sequenceName = "EXAMPLE_REQUEST_ID_SEQ", allocationSize = 1)
#Cacheable(false)
public class ExampleRequest implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "EXAMPLE_REQUEST_ID_SEQ")
#Column(name="EXAMPLE_REQUEST_ID", nullable = false)
private Long exampleRequestId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "exampleRequest")
private List<ExampleRequestYear> exampleRequestYearList;
public ExampleRequest() {
}
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
}
return this.exampleRequestYearList;
}
public void setExampleRequestYearList(List<ExampleRequestYear> exampleRequestYearList) {
this.exampleRequestYearList = exampleRequestYearList;
}
public ExampleRequestYear addExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().add(exampleRequestYear);
exampleRequestYear.setExampleRequest(this);
return exampleRequestYear;
}
public ExampleRequestYear removeExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().remove(exampleRequestYear);
exampleRequestYear.setExampleRequest(null);
return exampleRequestYear;
}
}
#Entity
#Table(name = "EXAMPLE_REQUEST_YEAR")
#IdClass(ExampleRequestYearPK.class)
public class ExampleRequestYear implements Serializable {
#Id
#Column(nullable = false)
private Integer year;
#Id
#ManyToOne
#JoinColumn(name = "EXAMPLE_REQUEST_ID", referencedColumnName = "EXAMPLE_REQUEST_ID")
private ExampleRequest exampleRequest;
public ExampleRequestYear() {
}
public void setExampleRequest(ExampleRequest exampleRequest) {
this.exampleRequest = exampleRequest;
}
public ExampleRequest getExampleRequest() {
return exampleRequest;
}
}
Part of the code was auto-generated by the IDE and I'm still wrapping my head around JPA so there're probably design mistakes all around.
My app works (apparently) when I create a new ExampleRequest:
ExampleRequest exampleRequest = new ExampleRequest();
ExampleRequestYear exampleRequestYear = new ExampleRequestYear(2020);
request.addExampleRequestYear(exampleRequestYear);
However, I can't figure out how to edit an existing ExampleRequest because I'm unsure on how I'm meant to retrieve the linked entities. According to articles I've read, lazy fetching should be automatic, yet when I try this:
ExampleRequest exampleRequest = employeeRequestsController.getExampleRequestById(123);
System.out.println(exampleRequest.getExampleRequestYearList().size());
... I get a null pointer exception upon .size() because the getter runs but neither initialises an empty list, nor retrieves items from DB:
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
// Field is null and conditional is entered
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
// After initialisation, field is still null!
}
return this.exampleRequestYearList;
}
Also, switch to FetchType.EAGER solves this particular problem entirely. What am I missing?
Further details regarding app design. The Resource classes that handle HTTP requests interact with a set of Controller classes like this:
#Stateless(name = "ISomeActionController", mappedName = "ISomeActionController")
public class SomeActionController implements ISomeActionController {
#EJB
private IFooDAO fooDao;
#EJB
private IBarDAO barDao;
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId) {
return fooDao.getEntityById(exampleRequestId);
}
}
It's in the DAO classes where EntityManager is injected an used:
#Local
public interface IGenericDAO<T> {
public T persistEntity(T o);
public T persistEntityCommit(T o);
public void removeEntity(T o);
public void removeEntity(long id);
public T mergeEntity(T o);
public List<T> getEntitiesFindAll();
public List<T> getEntitiesFindAllActive();
public T getEntityById(Object id);
}
public interface IFooDAO extends IGenericDAO<ExampleRequest> {
public void flushDAO();
public ExampleRequest getExampleRequestById(Long exampleRequestId);
}
#Stateless(name = "IFooDAO", mappedName = "IFooDAO")
public class FooDAO extends GenericDAO<ExampleRequest> implements IFooDAO {
public FooDAO() {
super(ExampleRequest.class);
}
#Override
public void flushDAO(){
em.flush();
}
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId){
String sql = "...";
Query query = em.createNativeQuery(sql, ExampleRequest.class);
//...
}
}
I have read every single question on SO but I still can't figure out why OGM for Neo4j doesn't save my RelationshipEntities.
Here PubmedDocumentNode and PubmedAuthorNode are both saved, but not the relationships citations and authors. MeshHeadingsUI edges instead are created fine.
Code:
#NodeEntity
public class PubmedDocumentNode extends Node {
#Id
#Index(unique = true)
public int PMID;
...
#Relationship(type = "CITES")
public List<PubmedCitesRelationship> citations;
#Relationship(type = "MeSHClass")
public List<MeSHClass> MeshHeadingsUI;
#Relationship(type = "AUTHOR")
public List<PubmedDocAuthorRelationship> authors;
public PubmedDocumentNode() {
node_type = NODE_TYPE;
citations = new ArrayList<>();
MeshHeadingsUI = new ArrayList<>();
authors = new ArrayList<>();
}
}
#NodeEntity
public class PubmedAuthorNode extends Node {
...
#Id
#Index(unique = true)
public String Author_Name;
....
}
#RelationshipEntity(type = "AUTHOR")
public class PubmedDocAuthorRelationship extends FeatureEdge {
public PubmedDocAuthorRelationship() {
label = new float[]{3.0f}; //just to try
}
#Id
#GeneratedValue
public long Id;
#StartNode
public PubmedDocumentNode document;
#EndNode
public PubmedAuthorNode author;
}
Parent classes:
public abstract class FeatureEdge extends DistributedNeo4jEntity {
#Property
public float label[];
public FeatureEdge() {
}
}
public abstract class Node extends DistributedNeo4jEntity {
...
public float state[];
public float label[];
...
public Node() {
...
}
}
public abstract class DistributedNeo4jEntity extends Neo4jEntity {
...
}
public abstract class Neo4jEntity {
#Labels
public List<String> labels = new ArrayList<>(); //I already tried to comment this but no luck
}
Save code:
//s is obtained by openSession
Transaction tx = s.beginTransaction();
arts.forEach(x -> s.save(x));
//arts.forEach(x -> x.authors.forEach(y -> s.save(y))); //tried also
//arts.forEach(x -> x.citations.forEach(y -> s.save(y))); //tried also
tx.commit();
tx.close();
I am trying to add HATEOAS links with Resource<>, while also filtering with #JsonView. However, I don't know how to add the links to nested objects.
In the project on on Github, I've expanded on this project (adding in the open pull request to make it work without nested resources), adding the "Character" entity which has a nested User.
When accessing the ~/characters/resource-filtered route, it is expected that the nested User "player" appear with the firstNm and bioDetails fields, and with Spring generated links to itself, but without the userId and lastNm fields.
I have the filtering working correctly, but I cannot find an example of nested resources which fits with the ResourceAssembler paradigm. It appears to be necessary to use a ResourceAssembler to make #JsonView work.
Any help reconciling these two concepts would be appreciated. If you can crack it entirely, consider sending me a pull request.
User.java
//package and imports
...
public class User implements Serializable {
#JsonView(UserView.Detail.class)
private Long userId;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String bioDetails;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String firstNm;
#JsonView({ UserView.Detail.class, CharacterView.Detail.class })
private String lastNm;
public User(Long userId, String firstNm, String lastNm) {
this.userId = userId;
this.firstNm = firstNm;
this.lastNm = lastNm;
}
public User(Long userId) {
this.userId = userId;
}
...
// getters and setters
...
}
CharacterModel.java
//package and imports
...
#Entity
public class CharacterModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(CharacterView.Summary.class)
private Long characterId;
#JsonView(CharacterView.Detail.class)
private String biography;
#JsonView(CharacterView.Summary.class)
private String name;
#JsonView(CharacterView.Summary.class)
private User player;
public CharacterModel(Long characterId, String name, String biography, User player) {
this.characterId = characterId;
this.name = name;
this.biography = biography;
this.player = player;
}
public CharacterModel(Long characterId) {
this.characterId = characterId;
}
...
// getters and setters
...
}
CharacterController.java
//package and imports
...
#RestController
#RequestMapping("/characters")
public class CharacterController {
#Autowired
private CharacterResourceAssembler characterResourceAssembler;
...
#JsonView(CharacterView.Summary.class)
#RequestMapping(value = "/resource-filtered", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Resource<CharacterModel> getFilteredCharacterWithResource() {
CharacterModel model = new CharacterModel(1L, "TEST NAME", "TEST BIOGRAPHY", new User(1L, "Fred", "Flintstone"));
return characterResourceAssembler.toResource(model);
}
...
}
CharacterResourceAssembler.java
//package and imports
...
#Component
public class CharacterResourceAssembler implements ResourceAssembler<CharacterModel, Resource<CharacterModel>>{
#Override
public Resource<CharacterModel> toResource(CharacterModel user) {
Resource<CharacterModel> resource = new Resource<CharacterModel>(user);
resource.add(linkTo(CharacterController.class).withSelfRel());
return resource;
}
}
I have a problem with resolving entities during entity manager startup.
Now it falls with following error:
Exception [EclipseLink-197] (Eclipse Persistence Services -
2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DescriptorException Exception
Description: The mapping [an_div] is not the appropriate type for this
descriptor Mapping:
org.eclipse.persistence.mappings.DirectToFieldMapping[an_div-->div]
Descriptor:
EISDescriptor(com.cloudyle.paasplus.api.fhir.model.dstu2.composite.NarrativeDt
--> [DatabaseTable(DATATYPE), DatabaseTable(NARRATIVEDT)])
The abstract class configuration:
#MappedSuperclass
#UuidGenerator(name = "UUID_GEN_SQL")
#NoSql(dataFormat = DataFormatType.MAPPED)
public abstract class AbstractBaseResource
{
private static final long serialVersionUID = -1212459053211153257L;
protected static final Logger logger = LoggerFactory.getLogger(AbstractBaseResource.class);
public static final String DELIMITER = ", ";
#Id
#GeneratedValue(generator = "UUID_GEN_SQL")
#Column(name = "_id")
private String id;
#Version
#Field(name = "object_version")
private Long objectVersion;
#Embedded
#Field(name = "text")
private NarrativeDt text;
// getter and setter
// ...
}
A simple entity class:
#Entity
#NoSql(dataFormat = DataFormatType.MAPPED)
public class Account extends AbstractBaseResource
{
#Field(name = "name")
private String an_name;
// other fields + getter and setter
// ...
}
The embbedable entity where the problem could be:
#Embeddable
#NoSql(dataFormat = DataFormatType.MAPPED)
#Customizer(DtChildCustomizer.class)
public class NarrativeDt extends Datatype
{
#Field(name = "status")
private String an_status;
#Field(name = "div")
private String an_div;
// getter and setter
// ...
}
The extended embbedable:
#Embeddable
#NoSql(dataFormat = DataFormatType.MAPPED)
#Customizer(DtParentCustomizer.class)
public abstract class Datatype implements IDatatype, Serializable
{
#Field(name = "element_specific_id")
private String an_elementSpecificId;
// getter and setter
// ...
}
The child customizer:
public class DtChildCustomizer implements Serializable, DescriptorCustomizer
{
#Override
public void customize(final ClassDescriptor descriptor)
{
descriptor.getInheritancePolicy().setParentClass(Datatype.class);
}
}
And the parent customizer:
public class DtParentCustomizer implements Serializable, DescriptorCustomizer
{
#Override
public void customize(final ClassDescriptor descriptor) throws Exception
{
descriptor.getInheritancePolicy().setSingleTableStrategy();
final DatabaseField indicatorField = new DatabaseField();
indicatorField.setName("classType");
indicatorField.setLength(255);
indicatorField.setType(java.lang.String.class);
descriptor.getInheritancePolicy().setClassIndicatorField(indicatorField);
descriptor.getInheritancePolicy().useClassNameAsIndicator();
}
}
I can not understand why eclispeLink have a problem to resolve simple mapping an_div->div.
All suggestion will be appreciated, I already spent too much time working on it. I can not see the mapping problem :(
Kind regards