├── settings.xml ├── license.txt ├── src ├── test │ ├── java │ │ └── org │ │ │ └── omnifaces │ │ │ └── optimusfaces │ │ │ └── test │ │ │ ├── model │ │ │ ├── Gender.java │ │ │ ├── Group.java │ │ │ ├── dto │ │ │ │ └── PersonCard.java │ │ │ ├── Phone.java │ │ │ ├── LocalGeneratedIdEntity.java │ │ │ ├── Address.java │ │ │ └── Person.java │ │ │ ├── OptimusFacesH2IT.java │ │ │ ├── OptimusFacesMySQLIT.java │ │ │ ├── OptimusFacesPostgreSQLIT.java │ │ │ ├── service │ │ │ ├── PhoneService.java │ │ │ ├── StartupService.java │ │ │ └── PersonService.java │ │ │ └── view │ │ │ ├── OptimusFacesITLazyStatelessBean.java │ │ │ ├── OptimusFacesITNonLazyStatelessBean.java │ │ │ ├── OptimusFacesITLazyBean.java │ │ │ ├── OptimusFacesITNonLazyBean.java │ │ │ ├── OptimusFacesITLazyWithDTOBean.java │ │ │ ├── OptimusFacesITNonLazyWithDTOBean.java │ │ │ ├── OptimusFacesITLazyWithManyToOneBean.java │ │ │ ├── OptimusFacesITLazyWithDefaultOrderByBean.java │ │ │ ├── OptimusFacesITLazyWithOneToOneBean.java │ │ │ ├── OptimusFacesITNonLazyWithManyToOneBean.java │ │ │ ├── OptimusFacesITNonLazyWithOneToOneBean.java │ │ │ ├── OptimusFacesITNonLazyWithElementCollectionBean.java │ │ │ ├── OptimusFacesITLazyWithElementCollectionBean.java │ │ │ ├── OptimusFacesITLazyWithOneToManyBean.java │ │ │ ├── OptimusFacesITNonLazyWithOneToManyBean.java │ │ │ ├── OptimusFacesITLazyWithCriteriaBean.java │ │ │ └── OptimusFacesITNonLazyWithCriteriaBean.java │ └── resources │ │ ├── WEB-INF │ │ ├── resources.xml │ │ │ ├── h2.xml │ │ │ ├── mysql.xml │ │ │ └── postgresql.xml │ │ ├── wildfly-ds.xml │ │ │ ├── h2.xml │ │ │ ├── mysql.xml │ │ │ └── postgresql.xml │ │ └── glassfish-resources.xml │ │ │ ├── h2.xml │ │ │ ├── postgresql.xml │ │ │ └── mysql.xml │ │ ├── arquillian.xml │ │ ├── META-INF │ │ └── persistence.xml │ │ │ ├── wildfly-hibernate.xml │ │ │ ├── tomee-openjpa.xml │ │ │ ├── payara-hibernate.xml │ │ │ ├── wildfly-eclipselink.xml │ │ │ └── payara-eclipselink.xml │ │ ├── org.omnifaces.optimusfaces.test │ │ ├── OptimusFacesITLazyWithDTO.xhtml │ │ ├── OptimusFacesITNonLazyWithDTO.xhtml │ │ ├── OptimusFacesITLazyWithDefaultOrderBy.xhtml │ │ ├── OptimusFacesITLazyWithOneToOne.xhtml │ │ ├── OptimusFacesITNonLazyWithOneToOne.xhtml │ │ ├── OptimusFacesITLazyWithManyToOne.xhtml │ │ ├── OptimusFacesITLazyWithFilterOptions.xhtml │ │ ├── OptimusFacesITNonLazyWithManyToOne.xhtml │ │ ├── OptimusFacesITLazy.xhtml │ │ ├── OptimusFacesITNonLazyWithFilterOptions.xhtml │ │ ├── OptimusFacesITNonLazy.xhtml │ │ ├── OptimusFacesITLazyStateless.xhtml │ │ ├── OptimusFacesITNonLazyStateless.xhtml │ │ ├── OptimusFacesITLazyWithCriteria.xhtml │ │ ├── OptimusFacesITNonLazyWithCriteria.xhtml │ │ ├── OptimusFacesITLazyWithElementCollection.xhtml │ │ ├── OptimusFacesITLazyWithOneToMany.xhtml │ │ ├── OptimusFacesITNonLazyWithElementCollection.xhtml │ │ └── OptimusFacesITNonLazyWithOneToMany.xhtml │ │ └── wildfly-eclipselink │ │ └── module.xml └── main │ ├── resources │ └── META-INF │ │ ├── beans.xml │ │ ├── web-fragment.xml │ │ ├── faces-config.xml │ │ ├── resources │ │ └── optimusfaces │ │ │ ├── scripts │ │ │ ├── datatable.js │ │ │ └── optimusfaces.js │ │ │ └── tags │ │ │ ├── column.xhtml │ │ │ └── dataTable.xhtml │ │ └── optimusfaces.taglib.xml │ └── java │ └── org │ └── omnifaces │ └── optimusfaces │ ├── component │ └── ExtendedDataTable.java │ ├── el │ └── NestedBaseEntityELResolver.java │ └── model │ ├── NonLazyPagedDataModel.java │ └── LazyPagedDataModel.java ├── .github └── workflows │ └── develop.maven.yml ├── .gitignore └── README.md /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.SONATYPE_USERNAME} 6 | ${env.SONATYPE_PASSWORD} 7 | 8 | 9 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright OmniFaces 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 4 | the License. You may obtain a copy of the License at 5 | 6 | https://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 9 | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 10 | specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/Gender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model; 14 | 15 | public enum Gender { 16 | 17 | MALE, 18 | FEMALE, 19 | TRANS, 20 | OTHER; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/Group.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model; 14 | 15 | public enum Group { 16 | 17 | USER, 18 | MANAGER, 19 | ADMINISTRATOR, 20 | DEVELOPER; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/resources.xml/h2.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | JdbcDriver org.h2.Driver 22 | JdbcUrl jdbc:h2:mem:test 23 | UserName sa 24 | Password sa 25 | jtaManaged = true 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/resources.xml/mysql.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | JdbcDriver com.mysql.jdbc.Driver 22 | JdbcUrl jdbc:mysql://localhost:3306/test 23 | UserName root 24 | Password root 25 | jtaManaged = true 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/resources.xml/postgresql.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | JdbcDriver org.postgresql.Driver 22 | JdbcUrl jdbc:postgresql:test 23 | UserName test 24 | Password test 25 | jtaManaged = true 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | htmlUnit 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/web-fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | optimusfaces 23 | optimusfaces 24 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/wildfly-ds.xml/h2.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | jdbc:h2:mem:test 22 | h2 23 | 24 | sa 25 | sa 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/wildfly-ds.xml/mysql.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | jdbc:mysql://localhost:3306/test 22 | mysql.jar 23 | 24 | root 25 | root 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/wildfly-ds.xml/postgresql.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | jdbc:postgresql:test 22 | postgresql.jar 23 | 24 | test 25 | test 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/OptimusFacesH2IT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test; 14 | 15 | import static org.omnifaces.persistence.Database.H2; 16 | 17 | import org.jboss.arquillian.container.test.api.Deployment; 18 | import org.jboss.arquillian.junit5.ArquillianExtension; 19 | import org.jboss.shrinkwrap.api.spec.WebArchive; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | @ExtendWith(ArquillianExtension.class) 23 | public class OptimusFacesH2IT extends OptimusFacesIT { 24 | 25 | @Deployment(testable=false) 26 | public static WebArchive createDeployment() { 27 | return createArchive(OptimusFacesH2IT.class, H2); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/OptimusFacesMySQLIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test; 14 | 15 | import static org.omnifaces.persistence.Database.MYSQL; 16 | 17 | import org.jboss.arquillian.container.test.api.Deployment; 18 | import org.jboss.arquillian.junit5.ArquillianExtension; 19 | import org.jboss.shrinkwrap.api.spec.WebArchive; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | @ExtendWith(ArquillianExtension.class) 23 | public class OptimusFacesMySQLIT extends OptimusFacesIT { 24 | 25 | @Deployment(testable=false) 26 | public static WebArchive createDeployment() { 27 | return createArchive(OptimusFacesMySQLIT.class, MYSQL); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/OptimusFacesPostgreSQLIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test; 14 | 15 | import static org.omnifaces.persistence.Database.POSTGRESQL; 16 | 17 | import org.jboss.arquillian.container.test.api.Deployment; 18 | import org.jboss.arquillian.junit5.ArquillianExtension; 19 | import org.jboss.shrinkwrap.api.spec.WebArchive; 20 | import org.junit.jupiter.api.extension.ExtendWith; 21 | 22 | @ExtendWith(ArquillianExtension.class) 23 | public class OptimusFacesPostgreSQLIT extends OptimusFacesIT { 24 | 25 | @Deployment(testable=false) 26 | public static WebArchive createDeployment() { 27 | return createArchive(OptimusFacesPostgreSQLIT.class, POSTGRESQL); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/persistence.xml/wildfly-hibernate.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | OptimusFacesIT 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/dto/PersonCard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model.dto; 14 | 15 | import org.omnifaces.optimusfaces.test.model.Person; 16 | 17 | public class PersonCard extends Person { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | private String addressString; 22 | private Long totalPhones; 23 | 24 | public PersonCard(Long id, String email, String addressString, Long totalPhones) { 25 | setId(id); 26 | setEmail(email); 27 | this.addressString = addressString; 28 | this.totalPhones = totalPhones; 29 | } 30 | 31 | public String getAddressString() { 32 | return addressString; 33 | } 34 | 35 | public Long getTotalPhones() { 36 | return totalPhones; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/glassfish-resources.xml/h2.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/service/PhoneService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.service; 14 | 15 | import javax.ejb.Stateless; 16 | 17 | import org.omnifaces.optimusfaces.test.model.Phone; 18 | import org.omnifaces.persistence.model.dto.Page; 19 | import org.omnifaces.persistence.service.BaseEntityService; 20 | import org.omnifaces.utils.collection.PartialResultList; 21 | 22 | @Stateless 23 | public class PhoneService extends BaseEntityService { 24 | 25 | public PartialResultList getPageWithOwners(Page page, boolean count) { 26 | return getPage(page, count, (builder, query, phone) -> { 27 | phone.fetch("owner"); 28 | }); 29 | } 30 | 31 | public PartialResultList getAllWithOwners() { 32 | return getPageWithOwners(Page.ALL, false); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/glassfish-resources.xml/postgresql.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/faces-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | optimusfaces 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | org.omnifaces.optimusfaces.el.NestedBaseEntityELResolver 32 | 33 | 34 | 35 | org.primefaces.component.DataTable 36 | org.omnifaces.optimusfaces.component.ExtendedDataTable 37 | 38 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/glassfish-resources.xml/mysql.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/persistence.xml/tomee-openjpa.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | java:openejb/Resource/OptimusFacesIT 24 | 25 | org.omnifaces.persistence.model.BaseEntity 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyStatelessBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import javax.annotation.PostConstruct; 16 | import javax.enterprise.context.RequestScoped; 17 | import javax.inject.Inject; 18 | import javax.inject.Named; 19 | 20 | import org.omnifaces.optimusfaces.model.PagedDataModel; 21 | import org.omnifaces.optimusfaces.test.model.Person; 22 | import org.omnifaces.optimusfaces.test.service.PersonService; 23 | 24 | @Named 25 | @RequestScoped 26 | public class OptimusFacesITLazyStatelessBean { 27 | 28 | private PagedDataModel lazyPersons; 29 | 30 | @Inject 31 | private PersonService personService; 32 | 33 | @PostConstruct 34 | public void init() { 35 | lazyPersons = PagedDataModel.lazy(personService).build(); 36 | } 37 | 38 | public PagedDataModel getLazyPersons() { 39 | return lazyPersons; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyStatelessBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import javax.annotation.PostConstruct; 16 | import javax.enterprise.context.RequestScoped; 17 | import javax.inject.Inject; 18 | import javax.inject.Named; 19 | 20 | import org.omnifaces.optimusfaces.model.PagedDataModel; 21 | import org.omnifaces.optimusfaces.test.model.Person; 22 | import org.omnifaces.optimusfaces.test.service.PersonService; 23 | 24 | @Named 25 | @RequestScoped 26 | public class OptimusFacesITNonLazyStatelessBean { 27 | 28 | private PagedDataModel nonLazyPersons; 29 | 30 | @Inject 31 | private PersonService personService; 32 | 33 | @PostConstruct 34 | public void init() { 35 | nonLazyPersons = PagedDataModel.nonLazy(personService.list()).build(); 36 | } 37 | 38 | public PagedDataModel getNonLazyPersons() { 39 | return nonLazyPersons; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/persistence.xml/payara-hibernate.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | org.hibernate.jpa.HibernatePersistenceProvider 24 | java:app/OptimusFacesIT 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.Person; 24 | import org.omnifaces.optimusfaces.test.service.PersonService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITLazyBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel lazyPersons; 33 | 34 | @Inject 35 | private PersonService personService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | lazyPersons = PagedDataModel.lazy(personService).build(); 40 | } 41 | 42 | public PagedDataModel getLazyPersons() { 43 | return lazyPersons; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.Person; 24 | import org.omnifaces.optimusfaces.test.service.PersonService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITNonLazyBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel nonLazyPersons; 33 | 34 | @Inject 35 | private PersonService personService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | nonLazyPersons = PagedDataModel.nonLazy(personService.list()).build(); 40 | } 41 | 42 | public PagedDataModel getNonLazyPersons() { 43 | return nonLazyPersons; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithDTO.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with DTO 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithDTO.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with DTO 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/optimusfaces/component/ExtendedDataTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.component; 14 | 15 | import javax.faces.context.FacesContext; 16 | import javax.faces.model.DataModel; 17 | 18 | import org.omnifaces.optimusfaces.model.LazyPagedDataModel; 19 | import org.primefaces.component.datatable.DataTable; 20 | 21 | /** 22 | *

23 | * This extended data table is already automatically registered via our faces-config.xml. 24 | * This will preload lazy loaded model for decode against a request scoped bean or a stateless view. 25 | */ 26 | public class ExtendedDataTable extends DataTable { 27 | 28 | @Override 29 | protected void preDecode(FacesContext context) { 30 | if (context.isPostback() && isLazy()) { 31 | DataModel model = getDataModel(); 32 | 33 | if (model instanceof LazyPagedDataModel && model.getWrappedData() == null) { 34 | ((LazyPagedDataModel) model).preloadPage(context, this); 35 | } 36 | } 37 | 38 | super.preDecode(context); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithDTOBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.dto.PersonCard; 24 | import org.omnifaces.optimusfaces.test.service.PersonService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITLazyWithDTOBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel lazyPersonCards; 33 | 34 | @Inject 35 | private PersonService personService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | lazyPersonCards = PagedDataModel.lazy(personService::getPageOfPersonCards).build(); 40 | } 41 | 42 | public PagedDataModel getLazyPersonCards() { 43 | return lazyPersonCards; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyWithDTOBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.dto.PersonCard; 24 | import org.omnifaces.optimusfaces.test.service.PersonService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITNonLazyWithDTOBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel allPersonCards; 33 | 34 | @Inject 35 | private PersonService personService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | allPersonCards = PagedDataModel.nonLazy(personService.getAllPersonCards()).build(); 40 | } 41 | 42 | public PagedDataModel getAllPersonCards() { 43 | return allPersonCards; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithDefaultOrderBy.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with default orderBy 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithManyToOneBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.Phone; 24 | import org.omnifaces.optimusfaces.test.service.PhoneService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITLazyWithManyToOneBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel lazyPhonesWithOwners; 33 | 34 | @Inject 35 | private PhoneService phoneService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | lazyPhonesWithOwners = PagedDataModel.lazy(phoneService::getPageWithOwners).build(); 40 | } 41 | 42 | public PagedDataModel getLazyPhonesWithOwners() { 43 | return lazyPhonesWithOwners; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithDefaultOrderByBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import org.omnifaces.cdi.ViewScoped; 16 | import org.omnifaces.optimusfaces.model.PagedDataModel; 17 | import org.omnifaces.optimusfaces.test.model.Person; 18 | import org.omnifaces.optimusfaces.test.service.PersonService; 19 | 20 | import javax.annotation.PostConstruct; 21 | import javax.inject.Inject; 22 | import javax.inject.Named; 23 | import java.io.Serializable; 24 | 25 | @Named 26 | @ViewScoped 27 | public class OptimusFacesITLazyWithDefaultOrderByBean implements Serializable { 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | private PagedDataModel lazyPersons; 32 | 33 | @Inject 34 | private PersonService personService; 35 | 36 | @PostConstruct 37 | public void init() { 38 | lazyPersons = PagedDataModel.lazy(personService) 39 | .orderBy(Person::getEmail, false) 40 | .build(); 41 | } 42 | 43 | public PagedDataModel getLazyPersons() { 44 | return lazyPersons; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithOneToOne.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with @OneToOne 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithOneToOneBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.Person; 24 | import org.omnifaces.optimusfaces.test.service.PersonService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITLazyWithOneToOneBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel lazyPersonsWithAddress; 33 | 34 | @Inject 35 | private PersonService personService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | lazyPersonsWithAddress = PagedDataModel.lazy(personService::getPageWithAddress).build(); 40 | } 41 | 42 | public PagedDataModel getLazyPersonsWithAddress() { 43 | return lazyPersonsWithAddress; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyWithManyToOneBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.Phone; 24 | import org.omnifaces.optimusfaces.test.service.PhoneService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITNonLazyWithManyToOneBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel nonLazyPhonesWithOwners; 33 | 34 | @Inject 35 | private PhoneService phoneService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | nonLazyPhonesWithOwners = PagedDataModel.nonLazy(phoneService.getAllWithOwners()).build(); 40 | } 41 | 42 | public PagedDataModel getNonLazyPhonesWithOwners() { 43 | return nonLazyPhonesWithOwners; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithOneToOne.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with @OneToOne 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithManyToOne.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with @ManyToOne 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyWithOneToOneBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.annotation.PostConstruct; 18 | import javax.inject.Inject; 19 | import javax.inject.Named; 20 | 21 | import org.omnifaces.cdi.ViewScoped; 22 | import org.omnifaces.optimusfaces.model.PagedDataModel; 23 | import org.omnifaces.optimusfaces.test.model.Person; 24 | import org.omnifaces.optimusfaces.test.service.PersonService; 25 | 26 | @Named 27 | @ViewScoped 28 | public class OptimusFacesITNonLazyWithOneToOneBean implements Serializable { 29 | 30 | private static final long serialVersionUID = 1L; 31 | 32 | private PagedDataModel nonLazyPersonsWithAddress; 33 | 34 | @Inject 35 | private PersonService personService; 36 | 37 | @PostConstruct 38 | public void init() { 39 | nonLazyPersonsWithAddress = PagedDataModel.nonLazy(personService.getAllWithAddress()).build(); 40 | } 41 | 42 | public PagedDataModel getNonLazyPersonsWithAddress() { 43 | return nonLazyPersonsWithAddress; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/persistence.xml/wildfly-eclipselink.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | org.eclipse.persistence.jpa.PersistenceProvider 24 | OptimusFacesIT 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/persistence.xml/payara-eclipselink.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | org.eclipse.persistence.jpa.PersistenceProvider 24 | java:app/OptimusFacesIT 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/resources/wildfly-eclipselink/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithFilterOptions.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with filter options 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithManyToOne.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with @ManyToOne 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazy.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithFilterOptions.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with filter options 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazy.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyStateless.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | 27 | OptimusFacesIT - lazy stateless 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyStateless.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | 27 | OptimusFacesIT - non-lazy stateless 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/develop.maven.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 OmniFaces 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | # the License. You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | # specific language governing permissions and limitations under the License. 12 | # 13 | 14 | name: develop 15 | 16 | on: 17 | push: 18 | branches: [ develop ] 19 | pull_request: 20 | branches: [ develop ] 21 | 22 | jobs: 23 | test: 24 | name: Run tests on ${{matrix.server}} 25 | runs-on: ubuntu-latest 26 | continue-on-error: true 27 | strategy: 28 | matrix: 29 | server: [wildfly-hibernate, wildfly-eclipselink, payara-hibernate, payara-eclipselink, tomee-openjpa] 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | - name: Set up JDK 8 34 | uses: actions/setup-java@v2 35 | with: 36 | java-version: '8' 37 | distribution: 'adopt' 38 | cache: maven 39 | - name : Set up MySQL 40 | run: | 41 | sudo /etc/init.d/mysql start 42 | mysql -u root -proot -e 'CREATE DATABASE test;' 43 | - name : Set up PostgreSQL 44 | run: | 45 | sudo systemctl start postgresql.service 46 | sudo -u postgres psql postgres -c "CREATE DATABASE test;" 47 | sudo -u postgres psql postgres -c "CREATE USER test WITH ENCRYPTED PASSWORD 'test';" 48 | sudo -u postgres psql postgres -c "GRANT ALL PRIVILEGES ON DATABASE test TO test;" 49 | - name: Test with Maven 50 | run: mvn verify -Dmaven.javadoc.skip=true -P ${{matrix.server}} 51 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/Phone.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model; 14 | 15 | import static javax.persistence.EnumType.STRING; 16 | 17 | import javax.persistence.Entity; 18 | import javax.persistence.Enumerated; 19 | import javax.persistence.FetchType; 20 | import javax.persistence.ManyToOne; 21 | import javax.persistence.Transient; 22 | import javax.validation.constraints.NotNull; 23 | 24 | @Entity 25 | public class Phone extends LocalGeneratedIdEntity { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | public enum Type { 30 | MOBILE, 31 | HOME, 32 | WORK; 33 | } 34 | 35 | private @NotNull @Enumerated(STRING) Type type; 36 | private @NotNull String number; 37 | 38 | @ManyToOne(optional=false, fetch=FetchType.LAZY) 39 | private @NotNull Person owner; 40 | 41 | public Type getType() { 42 | return type; 43 | } 44 | 45 | public void setType(Type type) { 46 | this.type = type; 47 | } 48 | 49 | public String getNumber() { 50 | return number; 51 | } 52 | 53 | public void setNumber(String number) { 54 | this.number = number; 55 | } 56 | 57 | public Person getOwner() { 58 | return owner; 59 | } 60 | 61 | public void setOwner(Person owner) { 62 | this.owner = owner; 63 | } 64 | 65 | @Transient 66 | public String getEmail() { 67 | return getOwner().getEmail(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithCriteria.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with criteria 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithCriteria.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with criteria 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithElementCollection.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with element collection 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITLazyWithOneToMany.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - lazy with @OneToMany 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithElementCollection.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with element collection 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/resources/org.omnifaces.optimusfaces.test/OptimusFacesITNonLazyWithOneToMany.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | OptimusFacesIT - non-lazy with @OneToMany 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/LocalGeneratedIdEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model; 14 | 15 | import static javax.persistence.GenerationType.IDENTITY; 16 | 17 | import javax.persistence.GeneratedValue; 18 | import javax.persistence.Id; 19 | import javax.persistence.MappedSuperclass; 20 | 21 | import org.omnifaces.persistence.model.BaseEntity; 22 | import org.omnifaces.persistence.model.GeneratedIdEntity; 23 | 24 | /** 25 | * This is needed by OpenJPA because it doesn't recognize a parameterized ID in a MappedSuperClass in a JAR. 26 | * OpenJPA 2.4.2 will fail as below: 27 | *

28 |  * WARN openjpa.Runtime - Fields "org.omnifaces.persistence.model.GeneratedIdEntity.id" are not a default persistent type,
29 |  * and do not have any annotations indicating their persistence strategy. They will be treated as non-persistent.
30 |  * 
31 | * And OpenJPA 2.4.3 will fail as below: 32 | *
33 |  * org.apache.openjpa.persistence.ArgumentException: Type "class org.omnifaces.persistence.model.GeneratedIdEntity"
34 |  * declares field "id" as a primary key, but keys of type "java.lang.Comparable" are not supported.
35 |  * 
36 | * This is NOT needed for Hibernate and EclipseLink. You can just extend from {@link GeneratedIdEntity} directly. 37 | */ 38 | @MappedSuperclass 39 | public class LocalGeneratedIdEntity extends BaseEntity { 40 | 41 | private static final long serialVersionUID = 1L; 42 | 43 | @Id @GeneratedValue(strategy = IDENTITY) 44 | private Long id; 45 | 46 | @Override 47 | public Long getId() { 48 | return id; 49 | } 50 | 51 | @Override 52 | public void setId(Long id) { 53 | this.id = id; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyWithElementCollectionBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import static java.util.Collections.singletonMap; 16 | 17 | import java.io.Serializable; 18 | import java.util.Map; 19 | import java.util.Set; 20 | 21 | import javax.annotation.PostConstruct; 22 | import javax.inject.Inject; 23 | import javax.inject.Named; 24 | 25 | import org.omnifaces.cdi.ViewScoped; 26 | import org.omnifaces.optimusfaces.model.PagedDataModel; 27 | import org.omnifaces.optimusfaces.test.model.Group; 28 | import org.omnifaces.optimusfaces.test.model.Person; 29 | import org.omnifaces.optimusfaces.test.service.PersonService; 30 | import org.omnifaces.utils.reflect.Getter; 31 | 32 | @Named 33 | @ViewScoped 34 | public class OptimusFacesITNonLazyWithElementCollectionBean implements Serializable { 35 | 36 | private static final long serialVersionUID = 1L; 37 | 38 | private PagedDataModel nonLazyGroupies; 39 | private Set selectedGroups; 40 | 41 | @Inject 42 | private PersonService personService; 43 | 44 | @PostConstruct 45 | public void init() { 46 | nonLazyGroupies = PagedDataModel.nonLazy(personService.getAllWithGroups()).criteria(this::mapSelectedCriteria).build(); 47 | } 48 | 49 | private Map, Object> mapSelectedCriteria() { 50 | return singletonMap(Person::getGroups, selectedGroups); 51 | } 52 | 53 | public PagedDataModel getNonLazyGroupies() { 54 | return nonLazyGroupies; 55 | } 56 | 57 | public Set getSelectedGroups() { 58 | return selectedGroups; 59 | } 60 | 61 | public void setSelectedGroups(Set selectedGroups) { 62 | this.selectedGroups = selectedGroups; 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model; 14 | 15 | import javax.persistence.Entity; 16 | import javax.validation.constraints.NotNull; 17 | 18 | import org.hibernate.annotations.Formula; 19 | 20 | @Entity 21 | public class Address extends LocalGeneratedIdEntity { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | private @NotNull String street; 26 | private @NotNull String houseNumber; 27 | private @NotNull String postcode; 28 | private @NotNull String city; 29 | private @NotNull String country; 30 | 31 | @Formula("CONCAT(street, ' ', houseNumber, ', ', postcode, ' ', city, ', ', country)") // NOTE: EclipseLink doesn't have a reasonable equivalent for @Formula. 32 | private String string; 33 | 34 | public String getStreet() { 35 | return street; 36 | } 37 | 38 | public void setStreet(String street) { 39 | this.street = street; 40 | } 41 | 42 | public String getHouseNumber() { 43 | return houseNumber; 44 | } 45 | 46 | public void setHouseNumber(String houseNumber) { 47 | this.houseNumber = houseNumber; 48 | } 49 | 50 | public String getPostcode() { 51 | return postcode; 52 | } 53 | 54 | public void setPostcode(String postcode) { 55 | this.postcode = postcode; 56 | } 57 | 58 | public String getCity() { 59 | return city; 60 | } 61 | 62 | public void setCity(String city) { 63 | this.city = city; 64 | } 65 | 66 | public String getCountry() { 67 | return country; 68 | } 69 | 70 | public void setCountry(String country) { 71 | this.country = country; 72 | } 73 | 74 | public String getString() { 75 | return string; 76 | } 77 | 78 | public void setString(String string) { 79 | this.string = string; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithElementCollectionBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import static java.util.Collections.emptyMap; 16 | import static java.util.Collections.singletonMap; 17 | import static org.omnifaces.utils.Lang.isEmpty; 18 | 19 | import java.io.Serializable; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | import javax.annotation.PostConstruct; 24 | import javax.inject.Inject; 25 | import javax.inject.Named; 26 | 27 | import org.omnifaces.cdi.ViewScoped; 28 | import org.omnifaces.optimusfaces.model.PagedDataModel; 29 | import org.omnifaces.optimusfaces.test.model.Group; 30 | import org.omnifaces.optimusfaces.test.model.Person; 31 | import org.omnifaces.optimusfaces.test.service.PersonService; 32 | import org.omnifaces.utils.reflect.Getter; 33 | 34 | @Named 35 | @ViewScoped 36 | public class OptimusFacesITLazyWithElementCollectionBean implements Serializable { 37 | 38 | private static final long serialVersionUID = 1L; 39 | 40 | private PagedDataModel lazyGroupies; 41 | private Set selectedGroups; 42 | 43 | @Inject 44 | private PersonService personService; 45 | 46 | @PostConstruct 47 | public void init() { 48 | lazyGroupies = PagedDataModel.lazy(personService::getPageWithGroups).criteria(this::mapSelectedCriteria).build(); 49 | } 50 | 51 | private Map, Object> mapSelectedCriteria() { 52 | return isEmpty(selectedGroups) ? emptyMap() : singletonMap(Person::getGroups, selectedGroups); 53 | } 54 | 55 | public PagedDataModel getLazyGroupies() { 56 | return lazyGroupies; 57 | } 58 | 59 | public Set getSelectedGroups() { 60 | return selectedGroups; 61 | } 62 | 63 | public void setSelectedGroups(Set selectedGroups) { 64 | this.selectedGroups = selectedGroups; 65 | } 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/optimusfaces/el/NestedBaseEntityELResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.el; 14 | 15 | import static java.util.stream.Collectors.toList; 16 | import static org.omnifaces.utils.stream.Streams.stream; 17 | 18 | import java.beans.FeatureDescriptor; 19 | import java.util.Collection; 20 | import java.util.Iterator; 21 | 22 | import javax.el.ELContext; 23 | import javax.el.ELResolver; 24 | 25 | import org.omnifaces.persistence.model.BaseEntity; 26 | 27 | public class NestedBaseEntityELResolver extends ELResolver { 28 | 29 | @Override 30 | public Class getCommonPropertyType(ELContext context, Object base) { 31 | return null; 32 | } 33 | 34 | @Override 35 | public Class getType(ELContext context, Object base, Object property) { 36 | return null; 37 | } 38 | 39 | @Override 40 | public Object getValue(ELContext context, Object base, Object property) { 41 | if (!(base instanceof BaseEntity)) { 42 | return null; 43 | } 44 | 45 | String propertyString = property.toString(); 46 | 47 | if (!propertyString.contains(".")) { 48 | return null; 49 | } 50 | 51 | Object value = base; 52 | 53 | for (String propertyPart : propertyString.split("\\.")) { 54 | if (value instanceof Collection) { 55 | value = stream(value).map(item -> context.getELResolver().getValue(context, item, propertyPart)).collect(toList()); 56 | } 57 | else { 58 | value = context.getELResolver().getValue(context, value, propertyPart); 59 | } 60 | 61 | } 62 | 63 | context.setPropertyResolved(true); 64 | return value; 65 | } 66 | 67 | @Override 68 | public void setValue(ELContext context, Object base, Object property, Object val) { 69 | // NOOP. 70 | } 71 | 72 | @Override 73 | public boolean isReadOnly(ELContext context, Object base, Object property) { 74 | return true; 75 | } 76 | 77 | @Override 78 | public Iterator getFeatureDescriptors(ELContext context, Object base) { 79 | return null; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithOneToManyBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import static java.util.Collections.emptyMap; 16 | import static org.omnifaces.utils.Lang.isEmpty; 17 | 18 | import java.io.Serializable; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | import javax.annotation.PostConstruct; 24 | import javax.inject.Inject; 25 | import javax.inject.Named; 26 | 27 | import org.omnifaces.cdi.ViewScoped; 28 | import org.omnifaces.optimusfaces.model.PagedDataModel; 29 | import org.omnifaces.optimusfaces.test.model.Person; 30 | import org.omnifaces.optimusfaces.test.model.Phone; 31 | import org.omnifaces.optimusfaces.test.service.PersonService; 32 | import org.omnifaces.utils.reflect.Getter; 33 | 34 | @Named 35 | @ViewScoped 36 | public class OptimusFacesITLazyWithOneToManyBean implements Serializable { 37 | 38 | private static final long serialVersionUID = 1L; 39 | 40 | private PagedDataModel lazyPersonsWithPhones; 41 | private Set selectedPhoneTypes; 42 | 43 | @Inject 44 | private PersonService personService; 45 | 46 | @PostConstruct 47 | public void init() { 48 | lazyPersonsWithPhones = PagedDataModel.lazy(personService::getPageWithPhones).criteria(this::mapSelectedCriteria).build(); 49 | } 50 | 51 | private Map, Object> mapSelectedCriteria() { 52 | if (isEmpty(selectedPhoneTypes)) { 53 | return emptyMap(); 54 | } 55 | 56 | Map, Object> phoneCriteria = new HashMap<>(); 57 | phoneCriteria.put(Phone::getType, selectedPhoneTypes); 58 | 59 | Map, Object> personCriteria = new HashMap<>(); 60 | personCriteria.put(Person::getPhones, phoneCriteria); 61 | 62 | return personCriteria; 63 | } 64 | 65 | public PagedDataModel getLazyPersonsWithPhones() { 66 | return lazyPersonsWithPhones; 67 | } 68 | 69 | public Set getSelectedPhoneTypes() { 70 | return selectedPhoneTypes; 71 | } 72 | 73 | public void setSelectedPhoneTypes(Set selectedPhoneTypes) { 74 | this.selectedPhoneTypes = selectedPhoneTypes; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyWithOneToManyBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import static java.util.Collections.emptyMap; 16 | import static org.omnifaces.utils.Lang.isEmpty; 17 | 18 | import java.io.Serializable; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | import javax.annotation.PostConstruct; 24 | import javax.inject.Inject; 25 | import javax.inject.Named; 26 | 27 | import org.omnifaces.cdi.ViewScoped; 28 | import org.omnifaces.optimusfaces.model.PagedDataModel; 29 | import org.omnifaces.optimusfaces.test.model.Person; 30 | import org.omnifaces.optimusfaces.test.model.Phone; 31 | import org.omnifaces.optimusfaces.test.service.PersonService; 32 | import org.omnifaces.utils.reflect.Getter; 33 | 34 | @Named 35 | @ViewScoped 36 | public class OptimusFacesITNonLazyWithOneToManyBean implements Serializable { 37 | 38 | private static final long serialVersionUID = 1L; 39 | 40 | private PagedDataModel nonLazyPersonsWithPhones; 41 | private Set selectedPhoneTypes; 42 | 43 | @Inject 44 | private PersonService personService; 45 | 46 | @PostConstruct 47 | public void init() { 48 | nonLazyPersonsWithPhones = PagedDataModel.nonLazy(personService.getAllWithPhones()).criteria(this::mapSelectedCriteria).build(); 49 | } 50 | 51 | private Map, Object> mapSelectedCriteria() { 52 | if (isEmpty(selectedPhoneTypes)) { 53 | return emptyMap(); 54 | } 55 | 56 | Map, Object> phoneCriteria = new HashMap<>(); 57 | phoneCriteria.put(Phone::getType, selectedPhoneTypes); 58 | 59 | Map, Object> personCriteria = new HashMap<>(); 60 | personCriteria.put(Person::getPhones, phoneCriteria); 61 | 62 | return personCriteria; 63 | } 64 | 65 | public PagedDataModel getNonLazyPersonsWithPhones() { 66 | return nonLazyPersonsWithPhones; 67 | } 68 | 69 | public Set getSelectedPhoneTypes() { 70 | return selectedPhoneTypes; 71 | } 72 | 73 | public void setSelectedPhoneTypes(Set selectedPhoneTypes) { 74 | this.selectedPhoneTypes = selectedPhoneTypes; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/model/Person.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.model; 14 | 15 | import static javax.persistence.CascadeType.PERSIST; 16 | import static javax.persistence.EnumType.STRING; 17 | import static javax.persistence.FetchType.LAZY; 18 | 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | import javax.persistence.Column; 26 | import javax.persistence.ElementCollection; 27 | import javax.persistence.Entity; 28 | import javax.persistence.Enumerated; 29 | import javax.persistence.OneToMany; 30 | import javax.persistence.OneToOne; 31 | import javax.validation.constraints.NotNull; 32 | 33 | @Entity 34 | public class Person extends LocalGeneratedIdEntity { 35 | 36 | private static final long serialVersionUID = 1L; 37 | 38 | private @NotNull String email; 39 | private @NotNull @Enumerated Gender gender; 40 | private @NotNull LocalDate dateOfBirth; 41 | 42 | @OneToOne(cascade=PERSIST, fetch=LAZY) 43 | private @NotNull Address address; 44 | 45 | @OneToMany(cascade=PERSIST) 46 | private @NotNull List phones = new ArrayList<>(); 47 | 48 | @ElementCollection 49 | @Column(name="\"groups\"") // "groups" has become a new reserved word since MySQL 8.0.2, so we need to quote it. 50 | private @Enumerated(STRING) Set groups = new HashSet<>(); 51 | 52 | public String getEmail() { 53 | return email; 54 | } 55 | 56 | public void setEmail(String email) { 57 | this.email = email; 58 | } 59 | 60 | public Gender getGender() { 61 | return gender; 62 | } 63 | 64 | public void setGender(Gender gender) { 65 | this.gender = gender; 66 | } 67 | 68 | public LocalDate getDateOfBirth() { 69 | return dateOfBirth; 70 | } 71 | 72 | public void setDateOfBirth(LocalDate dateOfBirth) { 73 | this.dateOfBirth = dateOfBirth; 74 | } 75 | 76 | public Address getAddress() { 77 | return address; 78 | } 79 | 80 | public void setAddress(Address address) { 81 | this.address = address; 82 | } 83 | 84 | public List getPhones() { 85 | return phones; 86 | } 87 | 88 | public void setPhones(List phones) { 89 | this.phones = phones; 90 | } 91 | 92 | public Set getGroups() { 93 | return groups; 94 | } 95 | 96 | public void setGroups(Set groups) { 97 | this.groups = groups; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/service/StartupService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.service; 14 | 15 | import static java.lang.Math.abs; 16 | 17 | import java.time.LocalDate; 18 | import java.util.Arrays; 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.concurrent.ThreadLocalRandom; 22 | 23 | import javax.annotation.PostConstruct; 24 | import javax.enterprise.context.ApplicationScoped; 25 | import javax.inject.Inject; 26 | 27 | import org.omnifaces.cdi.Eager; 28 | import org.omnifaces.optimusfaces.test.model.Address; 29 | import org.omnifaces.optimusfaces.test.model.Gender; 30 | import org.omnifaces.optimusfaces.test.model.Group; 31 | import org.omnifaces.optimusfaces.test.model.Person; 32 | import org.omnifaces.optimusfaces.test.model.Phone; 33 | 34 | @Eager 35 | @ApplicationScoped 36 | public class StartupService { 37 | 38 | public static final int TOTAL_RECORDS = 200; 39 | public static final int ROWS_PER_PAGE = 10; 40 | 41 | @Inject 42 | private PersonService personService; 43 | 44 | @PostConstruct 45 | public void init() { 46 | createTestPersons(); 47 | } 48 | 49 | private void createTestPersons() { 50 | Gender[] genders = Gender.values(); 51 | Phone.Type[] phoneTypes = Phone.Type.values(); 52 | List groups = Arrays.asList(Group.values()); 53 | ThreadLocalRandom random = ThreadLocalRandom.current(); 54 | 55 | for (int i = 0; i < TOTAL_RECORDS; i++) { 56 | Person person = new Person(); 57 | person.setEmail("name" + i + "@example.com"); 58 | person.setGender(genders[random.nextInt(genders.length)]); 59 | person.setDateOfBirth(LocalDate.ofEpochDay(random.nextLong(LocalDate.of(1900, 1, 1).toEpochDay(), LocalDate.of(2000, 1, 1).toEpochDay()))); 60 | 61 | Address address = new Address(); 62 | address.setStreet("Street" + i); 63 | address.setHouseNumber("" + i); 64 | address.setPostcode("Postcode" + i); 65 | address.setCity("City" + i); 66 | address.setCountry("Country" + i); 67 | person.setAddress(address); 68 | 69 | int totalPhones = random.nextInt(1, 6); 70 | for (int j = 0; j < totalPhones; j++) { 71 | Phone phone = new Phone(); 72 | phone.setType(phoneTypes[random.nextInt(phoneTypes.length)]); 73 | phone.setNumber("0" + abs(random.nextInt())); 74 | phone.setOwner(person); 75 | person.getPhones().add(phone); 76 | } 77 | 78 | Collections.shuffle(groups, random); 79 | person.getGroups().addAll(groups.subList(0, random.nextInt(1, groups.size() + 1))); 80 | 81 | personService.persist(person); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/optimusfaces/scripts/datatable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | /** 14 | * Improve script. 15 | * 16 | * 1. Make row with key=0 unselectable. 17 | * 2. Check if default sort needs to be descending (by presence of class="desc" on th). 18 | * 3. Initialize op:dataTable global search/filter events. 19 | * 20 | * Original source here: https://github.com/primefaces/primefaces/blob/master/src/main/resources/META-INF/resources/primefaces/datatable/datatable.js 21 | */ 22 | if (PrimeFaces.widget.DataTable) { 23 | 24 | function makeRowWithKey0Unselectable(id) { 25 | $(document.getElementById(id)).find("tr[data-rk=0]").removeClass("ui-widget-content"); 26 | } 27 | 28 | function checkIfDefaultSortNeedsToBeDescending($sortableColumns) { 29 | for(var i = 0; i < $sortableColumns.length; i++) { 30 | var $sortableColumn = $sortableColumns.eq(i); 31 | if ($sortableColumn.hasClass("desc") && !$sortableColumn.hasClass("ui-state-active")) { 32 | $sortableColumn.data("sortorder", 1); 33 | } 34 | } 35 | } 36 | 37 | PrimeFaces.widget.DataTable = PrimeFaces.widget.DataTable.extend({ 38 | init: function(cfg) { 39 | makeRowWithKey0Unselectable(cfg.id); 40 | this._super(cfg); 41 | checkIfDefaultSortNeedsToBeDescending(this.sortableColumns); 42 | }, 43 | sort: function(columnHeader, order, multi) { 44 | this._super(columnHeader, order, multi); 45 | checkIfDefaultSortNeedsToBeDescending(this.sortableColumns); 46 | }, 47 | postUpdateData: function() { 48 | this._super(); 49 | this.jq.toggleClass("empty", this.isEmpty()); 50 | } 51 | }); 52 | 53 | /** 54 | * Toggle global filter class in filterable columns on focus of global filter input. 55 | */ 56 | $(document).on("focus", ".ui-datatable-actions .ui-inputfield.filter", function() { 57 | $(this).closest("form").find("th.ui-filter-column").addClass("global"); 58 | }).on("blur", ".ui-datatable-actions .ui-inputfield.filter", function() { 59 | $(this).closest("form").find("th.ui-filter-column").removeClass("global"); 60 | }); 61 | 62 | /** 63 | * Global search actions. 64 | */ 65 | $(document).on("keypress", ".ui-datatable-actions .ui-inputfield.filter", function(event) { 66 | if (event.keyCode == 13) { 67 | $(this).next().click(); 68 | return false; 69 | } 70 | }).on("search", ".ui-datatable-actions .ui-inputfield.filter", function() { 71 | $(this).next().click(); 72 | }).on("click", ".ui-datatable-actions .ui-button.search", function() { 73 | var dataTableWidget = PF($(this).data("tablewidgetid")); 74 | var $globalFilter = dataTableWidget.jq.find("[id$=globalFilter]"); 75 | var $globalFilterValue = $(this).prev().val().trim(); 76 | 77 | if ($globalFilter.val() != $globalFilterValue) { 78 | $globalFilter.val($globalFilterValue); 79 | dataTableWidget.filter(); 80 | } 81 | }); 82 | 83 | } -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITLazyWithCriteriaBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import static java.util.stream.Collectors.toMap; 16 | import static org.omnifaces.utils.stream.Streams.stream; 17 | 18 | import java.io.Serializable; 19 | import java.time.LocalDate; 20 | import java.util.AbstractMap.SimpleEntry; 21 | import java.util.LinkedHashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Map.Entry; 25 | 26 | import javax.annotation.PostConstruct; 27 | import javax.inject.Inject; 28 | import javax.inject.Named; 29 | 30 | import org.omnifaces.cdi.ViewScoped; 31 | import org.omnifaces.optimusfaces.model.PagedDataModel; 32 | import org.omnifaces.optimusfaces.test.model.Gender; 33 | import org.omnifaces.optimusfaces.test.model.Person; 34 | import org.omnifaces.optimusfaces.test.service.PersonService; 35 | import org.omnifaces.persistence.criteria.Between; 36 | import org.omnifaces.persistence.criteria.Like; 37 | import org.omnifaces.persistence.criteria.Order; 38 | import org.omnifaces.utils.reflect.Getter; 39 | 40 | @Named 41 | @ViewScoped 42 | public class OptimusFacesITLazyWithCriteriaBean implements Serializable { 43 | 44 | private static final long serialVersionUID = 1L; 45 | 46 | private static final Map, Object>> AVAILABLE_CRITERIA = new LinkedHashMap<>(); 47 | 48 | static { 49 | AVAILABLE_CRITERIA.put("Id BETWEEN 50 AND 150", new SimpleEntry<>(Person::getId, Between.range(50L, 150L))); 50 | AVAILABLE_CRITERIA.put("Email LIKE name1%", new SimpleEntry<>(Person::getEmail, Like.startsWith("name1"))); 51 | AVAILABLE_CRITERIA.put("Gender = FEMALE", new SimpleEntry<>(Person::getGender, Gender.FEMALE)); 52 | AVAILABLE_CRITERIA.put("DateOfBirth < 1950", new SimpleEntry<>(Person::getDateOfBirth, Order.lessThan(LocalDate.of(1950, 1, 1)))); 53 | } 54 | 55 | private PagedDataModel lazyPersonsWithCriteria; 56 | private List, Object>> selectedCriteria; 57 | 58 | @Inject 59 | private PersonService personService; 60 | 61 | @PostConstruct 62 | public void init() { 63 | lazyPersonsWithCriteria = PagedDataModel.lazy(personService).criteria(this::mapSelectedCriteria).build(); 64 | } 65 | 66 | private Map, Object> mapSelectedCriteria() { 67 | return stream(selectedCriteria).collect(toMap(Entry::getKey, Entry::getValue)); 68 | } 69 | 70 | public PagedDataModel getLazyPersonsWithCriteria() { 71 | return lazyPersonsWithCriteria; 72 | } 73 | 74 | public Map, Object>> getAvailableCriteria() { 75 | return AVAILABLE_CRITERIA; 76 | } 77 | 78 | public List, Object>> getSelectedCriteria() { 79 | return selectedCriteria; 80 | } 81 | 82 | public void setSelectedCriteria(List, Object>> selectedCriteria) { 83 | this.selectedCriteria = selectedCriteria; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/view/OptimusFacesITNonLazyWithCriteriaBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.view; 14 | 15 | import static java.util.stream.Collectors.toMap; 16 | import static org.omnifaces.utils.stream.Streams.stream; 17 | 18 | import java.io.Serializable; 19 | import java.time.LocalDate; 20 | import java.util.AbstractMap.SimpleEntry; 21 | import java.util.LinkedHashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Map.Entry; 25 | 26 | import javax.annotation.PostConstruct; 27 | import javax.inject.Inject; 28 | import javax.inject.Named; 29 | 30 | import org.omnifaces.cdi.ViewScoped; 31 | import org.omnifaces.optimusfaces.model.PagedDataModel; 32 | import org.omnifaces.optimusfaces.test.model.Gender; 33 | import org.omnifaces.optimusfaces.test.model.Person; 34 | import org.omnifaces.optimusfaces.test.service.PersonService; 35 | import org.omnifaces.persistence.criteria.Between; 36 | import org.omnifaces.persistence.criteria.Like; 37 | import org.omnifaces.persistence.criteria.Order; 38 | import org.omnifaces.utils.reflect.Getter; 39 | 40 | @Named 41 | @ViewScoped 42 | public class OptimusFacesITNonLazyWithCriteriaBean implements Serializable { 43 | 44 | private static final long serialVersionUID = 1L; 45 | 46 | private static final Map, Object>> AVAILABLE_CRITERIA = new LinkedHashMap<>(); 47 | 48 | static { 49 | AVAILABLE_CRITERIA.put("Id BETWEEN 50 AND 150", new SimpleEntry<>(Person::getId, Between.range(50L, 150L))); 50 | AVAILABLE_CRITERIA.put("Email LIKE name1%", new SimpleEntry<>(Person::getEmail, Like.startsWith("name1"))); 51 | AVAILABLE_CRITERIA.put("Gender = FEMALE", new SimpleEntry<>(Person::getGender, Gender.FEMALE)); 52 | AVAILABLE_CRITERIA.put("DateOfBirth < 1950", new SimpleEntry<>(Person::getDateOfBirth, Order.lessThan(LocalDate.of(1950, 1, 1)))); 53 | } 54 | 55 | private PagedDataModel nonLazyPersonsWithCriteria; 56 | private List, Object>> selectedCriteria; 57 | 58 | @Inject 59 | private PersonService personService; 60 | 61 | @PostConstruct 62 | public void init() { 63 | nonLazyPersonsWithCriteria = PagedDataModel.nonLazy(personService.list()).criteria(this::mapSelectedCriteria).build(); 64 | } 65 | 66 | private Map, Object> mapSelectedCriteria() { 67 | return stream(selectedCriteria).collect(toMap(Entry::getKey, Entry::getValue)); 68 | } 69 | 70 | public PagedDataModel getNonLazyPersonsWithCriteria() { 71 | return nonLazyPersonsWithCriteria; 72 | } 73 | 74 | public Map, Object>> getAvailableCriteria() { 75 | return AVAILABLE_CRITERIA; 76 | } 77 | 78 | public List, Object>> getSelectedCriteria() { 79 | return selectedCriteria; 80 | } 81 | 82 | public void setSelectedCriteria(List, Object>> selectedCriteria) { 83 | this.selectedCriteria = selectedCriteria; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/optimusfaces/scripts/optimusfaces.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | /** 14 | * The OptimusFaces namespace. 15 | * 16 | * @author Bauke Scholtz 17 | */ 18 | var OptimusFaces = OptimusFaces || {}; 19 | 20 | /** 21 | * Utility scripts. 22 | */ 23 | OptimusFaces.Util = (function(window, document) { 24 | 25 | // Private static fields ------------------------------------------------------------------------------------------ 26 | 27 | var self = {}; 28 | 29 | // Public static functions ---------------------------------------------------------------------------------------- 30 | 31 | self.historyPushQueryString = function(queryString) { 32 | if (window.history && window.history.pushState) { 33 | var url = window.location.href.split(/\?/, 2)[0] + (queryString ? "?" : "") + queryString; 34 | window.history.pushState(null, document.title, url); 35 | } 36 | } 37 | 38 | self.historyPushQueryStringParameter = function(name, value) { 39 | if (window.history && window.history.pushState) { 40 | var url = self.updateQueryStringParameter(window.location.href, name, value); 41 | window.history.pushState(null, document.title, url); 42 | } 43 | } 44 | 45 | self.historyReplaceQueryString = function(queryString) { 46 | if (window.history && window.history.replaceState) { 47 | var url = window.location.href.split(/\?/, 2)[0] + (queryString ? "?" : "") + queryString; 48 | window.history.replaceState(null, document.title, url); 49 | } 50 | } 51 | 52 | self.historyReplaceQueryStringParameter = function(name, value) { 53 | if (window.history && window.history.replaceState) { 54 | var url = self.updateQueryStringParameter(window.location.href, name, value); 55 | window.history.replaceState(null, document.title, url); 56 | } 57 | } 58 | 59 | self.updateQueryString = function(queryString) { 60 | self.historyReplaceQueryString(queryString); 61 | for (var i = 0; i < document.forms.length; i++) { 62 | var form = document.forms[i]; 63 | if (form["javax.faces.ViewState"]) { 64 | form.action = form.action.split(/\?/, 2)[0] + (queryString ? "?" : "") + queryString; 65 | } 66 | } 67 | } 68 | 69 | self.updateQueryStringParameter = function(url, name, value) { 70 | var parts = url.split(/#/, 2); 71 | var uri = parts[0]; 72 | var hash = (parts.length > 1) ? ("#" + parts[1]) : ""; 73 | var re = new RegExp("([?&])" + name + "=.*?(&|$)", "i"); 74 | 75 | if (value) { 76 | var parameter = name + "=" + encodeURIComponent(value); 77 | 78 | if (uri.match(re)) { 79 | uri = uri.replace(re, "$1" + parameter + "$2"); 80 | } 81 | else { 82 | uri += "&" + parameter; 83 | } 84 | } 85 | else { 86 | uri = uri.replace(re, "$2"); 87 | } 88 | 89 | if (uri.indexOf("?") == -1) { 90 | uri = uri.replace(/&/, "?"); 91 | } 92 | else if (uri.slice(-1) == "?") { 93 | uri = uri.slice(0, -1); 94 | } 95 | 96 | return uri + hash; 97 | } 98 | 99 | // Expose self to public ------------------------------------------------------------------------------------------ 100 | 101 | return self; 102 | 103 | })(window, document); -------------------------------------------------------------------------------- /src/test/java/org/omnifaces/optimusfaces/test/service/PersonService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.test.service; 14 | 15 | import static org.omnifaces.persistence.Database.POSTGRESQL; 16 | import static org.omnifaces.persistence.JPA.concat; 17 | import static org.omnifaces.persistence.Provider.HIBERNATE; 18 | 19 | import java.util.LinkedHashMap; 20 | 21 | import javax.ejb.Stateless; 22 | import javax.persistence.criteria.Expression; 23 | import javax.persistence.criteria.Join; 24 | 25 | import org.omnifaces.optimusfaces.test.model.Address; 26 | import org.omnifaces.optimusfaces.test.model.Person; 27 | import org.omnifaces.optimusfaces.test.model.Phone; 28 | import org.omnifaces.optimusfaces.test.model.dto.PersonCard; 29 | import org.omnifaces.persistence.model.dto.Page; 30 | import org.omnifaces.persistence.service.BaseEntityService; 31 | import org.omnifaces.utils.collection.PartialResultList; 32 | import org.omnifaces.utils.reflect.Getter; 33 | 34 | @Stateless 35 | public class PersonService extends BaseEntityService { 36 | 37 | public PartialResultList getPageWithAddress(Page page, boolean count) { 38 | return getPage(page, count, (builder, query, person) -> { 39 | person.fetch("address"); 40 | }); 41 | } 42 | 43 | public PartialResultList getPageWithPhones(Page page, boolean count) { 44 | return getPage(page, count, (builder, query, person) -> { 45 | person.fetch("phones"); 46 | }); 47 | } 48 | 49 | public PartialResultList getPageWithGroups(Page page, boolean count) { 50 | return getPage(page, count, (builder, query, person) -> { 51 | person.fetch("groups"); 52 | }); 53 | } 54 | 55 | public PartialResultList getPageOfPersonCards(Page page, boolean count) { 56 | return getPage(page, count, PersonCard.class, (builder, query, person) -> { 57 | Join personAddress = person.join("address"); 58 | Join personPhones = person.join("phones"); 59 | 60 | LinkedHashMap, Expression> mapping = new LinkedHashMap<>(); 61 | mapping.put(PersonCard::getId, person.get("id")); 62 | mapping.put(PersonCard::getEmail, person.get("email")); 63 | 64 | if (getProvider() == HIBERNATE) { 65 | mapping.put(PersonCard::getAddressString, personAddress.get("string")); // address.string uses Hibernate specific @Formula, so no need for manual concat(). 66 | } 67 | else { 68 | mapping.put(PersonCard::getAddressString, concat(builder, personAddress.get("street"), " ", personAddress.get("houseNumber"), ", ", personAddress.get("postcode"), " ", personAddress.get("city"), ", ", personAddress.get("country"))); 69 | } 70 | 71 | if (getDatabase() == POSTGRESQL) { // Doesn't hurt on other DBs but makes query unnecessarily bloated. 72 | query.groupBy(personAddress); 73 | } 74 | 75 | mapping.put(PersonCard::getTotalPhones, builder.count(personPhones)); 76 | 77 | return mapping; 78 | }); 79 | } 80 | 81 | public PartialResultList getAllWithAddress() { 82 | return getPageWithAddress(Page.ALL, false); 83 | } 84 | 85 | public PartialResultList getAllWithPhones() { 86 | return getPageWithPhones(Page.ALL, false); 87 | } 88 | 89 | public PartialResultList getAllWithGroups() { 90 | return getPageWithGroups(Page.ALL, false); 91 | } 92 | 93 | public PartialResultList getAllPersonCards() { 94 | return getPageOfPersonCards(Page.ALL, false); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,maven,eclipse,intellij,netbeans,windows,osx,linux 3 | 4 | ### Java ### 5 | *.class 6 | 7 | # Mobile Tools for Java (J2ME) 8 | .mtj.tmp/ 9 | 10 | # Package Files # 11 | *.jar 12 | *.war 13 | *.ear 14 | 15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 16 | hs_err_pid* 17 | 18 | 19 | ### Maven ### 20 | target/ 21 | pom.xml.tag 22 | pom.xml.releaseBackup 23 | pom.xml.versionsBackup 24 | pom.xml.next 25 | release.properties 26 | dependency-reduced-pom.xml 27 | buildNumber.properties 28 | .mvn/timing.properties 29 | 30 | 31 | ### Eclipse ### 32 | 33 | .metadata 34 | bin/ 35 | tmp/ 36 | *.tmp 37 | *.bak 38 | *.swp 39 | *~.nib 40 | local.properties 41 | .settings/ 42 | .loadpath 43 | 44 | # Eclipse Core 45 | .project 46 | 47 | # External tool builders 48 | .externalToolBuilders/ 49 | 50 | # Locally stored "Eclipse launch configurations" 51 | *.launch 52 | 53 | # PyDev specific (Python IDE for Eclipse) 54 | *.pydevproject 55 | 56 | # CDT-specific (C/C++ Development Tooling) 57 | .cproject 58 | 59 | # JDT-specific (Eclipse Java Development Tools) 60 | .classpath 61 | 62 | # Java annotation processor (APT) 63 | .factorypath 64 | 65 | # PDT-specific (PHP Development Tools) 66 | .buildpath 67 | 68 | # sbteclipse plugin 69 | .target 70 | 71 | # Tern plugin 72 | .tern-project 73 | 74 | # TeXlipse plugin 75 | .texlipse 76 | 77 | # STS (Spring Tool Suite) 78 | .springBeans 79 | 80 | # Code Recommenders 81 | .recommenders/ 82 | 83 | 84 | ### Intellij ### 85 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 86 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 87 | 88 | # User-specific stuff: 89 | .idea/workspace.xml 90 | .idea/tasks.xml 91 | .idea/dictionaries 92 | .idea/vcs.xml 93 | .idea/jsLibraryMappings.xml 94 | 95 | # Sensitive or high-churn files: 96 | .idea/dataSources.ids 97 | .idea/dataSources.xml 98 | .idea/sqlDataSources.xml 99 | .idea/dynamic.xml 100 | .idea/uiDesigner.xml 101 | 102 | # Gradle: 103 | .idea/gradle.xml 104 | .idea/libraries 105 | 106 | # Mongo Explorer plugin: 107 | .idea/mongoSettings.xml 108 | 109 | ## File-based project format: 110 | *.iws 111 | 112 | ## Plugin-specific files: 113 | 114 | # IntelliJ 115 | /out/ 116 | 117 | # mpeltonen/sbt-idea plugin 118 | .idea_modules/ 119 | 120 | # JIRA plugin 121 | atlassian-ide-plugin.xml 122 | 123 | # Crashlytics plugin (for Android Studio and IntelliJ) 124 | com_crashlytics_export_strings.xml 125 | crashlytics.properties 126 | crashlytics-build.properties 127 | fabric.properties 128 | 129 | # Manually added 130 | .idea/ 131 | *.iml 132 | 133 | ### NetBeans ### 134 | nbproject/private/ 135 | build/ 136 | nbbuild/ 137 | dist/ 138 | nbdist/ 139 | nbactions.xml 140 | .nb-gradle/ 141 | 142 | 143 | ### Windows ### 144 | # Windows image file caches 145 | Thumbs.db 146 | ehthumbs.db 147 | 148 | # Folder config file 149 | Desktop.ini 150 | 151 | # Recycle Bin used on file shares 152 | $RECYCLE.BIN/ 153 | 154 | # Windows Installer files 155 | *.cab 156 | *.msi 157 | *.msm 158 | *.msp 159 | 160 | # Windows shortcuts 161 | *.lnk 162 | 163 | 164 | ### OSX ### 165 | .DS_Store 166 | .AppleDouble 167 | .LSOverride 168 | 169 | # Icon must end with two \r 170 | Icon 171 | 172 | 173 | # Thumbnails 174 | ._* 175 | 176 | # Files that might appear in the root of a volume 177 | .DocumentRevisions-V100 178 | .fseventsd 179 | .Spotlight-V100 180 | .TemporaryItems 181 | .Trashes 182 | .VolumeIcon.icns 183 | 184 | # Directories potentially created on remote AFP share 185 | .AppleDB 186 | .AppleDesktop 187 | Network Trash Folder 188 | Temporary Items 189 | .apdisk 190 | 191 | 192 | ### Linux ### 193 | *~ 194 | 195 | # temporary files which can be created if a process still has a handle open of a deleted file 196 | .fuse_hidden* 197 | 198 | # KDE directory preferences 199 | .directory 200 | 201 | # Linux trash folder which might appear on any partition or disk 202 | .Trash-* 203 | 204 | 205 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/optimusfaces/tags/column.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | #{value.toString()} 72 | #{$value.toString()} 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/optimusfaces/tags/dataTable.xhtml: -------------------------------------------------------------------------------- 1 | 15 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 | 93 | 94 | 96 | 97 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | 111 | 112 | 113 | 115 | 117 | 118 | 119 | 120 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 |
139 |
140 | 141 |
142 | 143 | 144 | 145 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maven](https://img.shields.io/maven-metadata/v/https/repo.maven.apache.org/maven2/org/omnifaces/optimusfaces/maven-metadata.xml.svg)](https://repo.maven.apache.org/maven2/org/omnifaces/optimusfaces/) 2 | [![Javadoc](https://javadoc.io/badge/org.omnifaces/optimusfaces.svg)](https://javadoc.io/doc/org.omnifaces/optimusfaces) 3 | [![Tests](https://github.com/omnifaces/optimusfaces/actions/workflows/maven.yml/badge.svg)](https://github.com/omnifaces/optimusfaces/actions) 4 | [![License](https://img.shields.io/:license-apache-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 5 | 6 | # OptimusFaces 7 | 8 | Utility library for OmniFaces + PrimeFaces combined. 9 | 10 | 11 | ## This project is currently still in development stage! 12 | 13 | This project basically combines best of [OmniFaces](http://omnifaces.org/) and [PrimeFaces](http://www.primefaces.org/) with help of [OmniPersistence](https://github.com/omnifaces/omnipersistence), an utility library for JPA. This project should make it a breeze to create semi-dynamic lazy-loaded, searchable, sortable and filterable `` based on a JPA model and a generic entity service. 14 | 15 | 16 | ### Installation 17 | 18 | `pom.xml` 19 | 20 | ```XML 21 | 22 | 23 | 24 | javax 25 | javaee-api 26 | 8.0 27 | provided 28 | 29 | 30 | 31 | 32 | org.omnifaces 33 | omnifaces 34 | 3.13.3 35 | 36 | 37 | org.primefaces 38 | primefaces 39 | 10.0.0 40 | 41 | 42 | org.omnifaces 43 | optimusfaces 44 | 0.15 45 | 46 | 47 | ``` 48 | 49 | **Minumum supported Java / OmniFaces / PrimeFaces versions** 50 | 51 | - OptimusFaces 0.1 - 0.14: Java 11 / OmniFaces 2.2 / PrimeFaces 7.0 52 | - OptimusFaces 0.15+: Java 11 / OmniFaces 3.0 / PrimeFaces 10.0.0 53 | - OptimusFaces 0.14-J1+: Java 11 / OmniFaces 4.0 / PrimeFaces 10.0.0:jakarta 54 | - OptimusFaces 0.17-J1+: Java 17 / OmniFaces 4.0 / PrimeFaces 13.0.0:jakarta 55 | 56 | 57 | ### Basic Usage 58 | 59 | First create your entity service extending [`org.omnifaces.omnipersistence.service.BaseEntityService`](https://static.javadoc.io/org.omnifaces/omnipersistence/latest/org/omnifaces/persistence/service/BaseEntityService.html). You don't necessarily need to add new methods, just extending it is sufficient. It's useful for other generic things too. 60 | 61 | ```Java 62 | @Stateless 63 | public class YourEntityService extends BaseEntityService { 64 | 65 | // ... 66 | 67 | } 68 | ``` 69 | 70 | And make sure `YourEntity` extends [`org.omnifaces.omnipersistence.model.BaseEntity`](https://static.javadoc.io/org.omnifaces/omnipersistence/latest/org/omnifaces/persistence/model/BaseEntity.html) or one of its subclasses `GeneratedIdEntity`, `TimestampedEntity`, `TimestampedBaseEntity`, `VersionedEntity` or `VersionedBaseEntity`. 71 | 72 | ```Java 73 | @Entity 74 | public class YourEntity extends BaseEntity { 75 | 76 | @Id @GeneratedValue(strategy=IDENTITY) 77 | private Long id; 78 | private Instant created; 79 | private String name; 80 | private Type type; 81 | private boolean deleted; 82 | 83 | // ... 84 | } 85 | ``` 86 | 87 | Then create a `org.omnifaces.optimusfaces.model.PagedDataModel` in your backing bean as below. 88 | 89 | ```Java 90 | @Named 91 | @ViewScoped 92 | public class YourBackingBean implements Serializable { 93 | 94 | private PagedDataModel model; 95 | 96 | @Inject 97 | private YourEntityService service; 98 | 99 | @PostConstruct 100 | public void init() { 101 | model = PagedDataModel.lazy(service).build(); 102 | } 103 | 104 | public PagedDataModel getModel() { 105 | return model; 106 | } 107 | 108 | } 109 | ``` 110 | 111 | Finally use `` to have a semi-dynamic lazy-loaded, pageable, sortable and filterable 112 | `` without much hassle. 113 | 114 | ```XML 115 | <... xmlns:op="http://omnifaces.org/optimusfaces"> 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | 128 | The `field` attribute of `` represents the entity property path. This will 129 | in turn be used in `id`, `field`, `headerText` and `filterBy` attributes 130 | of ``. 131 | 132 | Here's how it looks like with default PrimeFaces UI and all. This example uses **exactly** the above Java and XHTML code with a `Person` entity with `Long id`, `String email`, `Gender gender` and `LocalDate dateOfBirth` fields. 133 | 134 | ![example of op:dataTable](https://i.imgur.com/VJyNKMH.png) 135 | 136 | 137 | ### Advanced Usage 138 | 139 | [Check `PagedDataModel` javadoc](http://static.javadoc.io/org.omnifaces/optimusfaces/latest/org/omnifaces/optimusfaces/model/PagedDataModel.html). 140 | 141 | 142 | ### Known Issues 143 | 144 | - EclipseLink refuses to perform a `JOIN` with Criteria API when setFirstResult/setMaxResults is used. This returns a cartesian product. This has been workarounded, but this removes the ability to perform sorting on a column referenced by a join (`@OneToMany` and `@ElementCollection`). You should set such columns as ``. Another consequence is that you cannot search with a multi-valued criteria in a field referenced by a `@OneToMany` relationship. You should consider using a DTO instead. 145 | - OpenJPA adds internally a second `JOIN` when sorting a column referenced by a join (`@OneToMany` and `@ElementCollection`). This has as consequence that the sorting is performed on a different join than the one referenced in `GROUP BY` and will thus be off from what's presented. You should for now set such columns as `` or consider using a DTO instead. 146 | - OpenJPA does not correctly apply setFirstResult/setMaxResults when an `@OneToMany` relationship is involved in the query. It will basically apply it on the results of the `@OneToMany` relationship instead of on the query root, causing the page to contain fewer records than expected. There is no clear solution/workaround for that yet. 147 | 148 | The [integration tests](https://github.com/omnifaces/optimusfaces/tree/develop/src/test/java/org/omnifaces/optimusfaces/test) currently run on [following environments](https://github.com/omnifaces/optimusfaces/actions/workflows/maven.yml): 149 | - WildFly 26.1.1 with Mojarra 2.3.17 and Hibernate 5.3.24 150 | - WildFly 26.1.1 with Mojarra 2.3.17 and EclipseLink 2.7.10 151 | - Payara 5.2022.2 with Mojarra 2.3.14 and Hibernate 5.4.33 152 | - Payara 5.2022.2 with Mojarra 2.3.14 and EclipseLink 2.7.9 153 | - TomEE 8.0.11 with MyFaces 2.3.9 and OpenJPA 3.2.2 154 | 155 | Each environment will run the IT on following databases: 156 | - H2 1.4.200 (embedded database) 157 | - MySQL latest 8.x (provided by GitHub Actions) with JDBC driver 8.0.29 158 | - PostgreSQL latest 12.x (provided by GitHub Actions) with JDBC driver 42.3.5 159 | 160 | Effectively, there are thus 15 full test runs of each [31 test cases](https://github.com/omnifaces/optimusfaces/blob/develop/src/test/java/org/omnifaces/optimusfaces/test/OptimusFacesIT.java#L429) on [19 XHTML files](https://github.com/omnifaces/optimusfaces/tree/develop/src/test/resources/org.omnifaces.optimusfaces.test). 161 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/optimusfaces/model/NonLazyPagedDataModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.model; 14 | 15 | import static java.lang.Math.min; 16 | import static java.util.Collections.singletonMap; 17 | import static java.util.Collections.unmodifiableList; 18 | import static java.util.stream.Collectors.toList; 19 | import static org.omnifaces.utils.Lang.isEmpty; 20 | import static org.omnifaces.utils.reflect.Reflections.invokeMethod; 21 | import static org.omnifaces.utils.stream.Streams.stream; 22 | 23 | import java.beans.IntrospectionException; 24 | import java.beans.Introspector; 25 | import java.beans.PropertyDescriptor; 26 | import java.lang.reflect.Method; 27 | import java.lang.reflect.ParameterizedType; 28 | import java.lang.reflect.Type; 29 | import java.text.Collator; 30 | import java.util.AbstractMap; 31 | import java.util.ArrayList; 32 | import java.util.Collection; 33 | import java.util.Comparator; 34 | import java.util.LinkedHashMap; 35 | import java.util.List; 36 | import java.util.Locale; 37 | import java.util.Map; 38 | import java.util.Map.Entry; 39 | import java.util.Objects; 40 | import java.util.function.Supplier; 41 | 42 | import org.omnifaces.persistence.criteria.Criteria; 43 | import org.omnifaces.persistence.model.Identifiable; 44 | import org.omnifaces.persistence.model.dto.Page; 45 | import org.omnifaces.utils.collection.PartialResultList; 46 | import org.omnifaces.utils.reflect.Getter; 47 | import org.primefaces.component.datatable.DataTable; 48 | import org.primefaces.model.SortMeta; 49 | 50 | /** 51 | * Use {@link PagedDataModel#nonLazy(List)} to build one. 52 | * 53 | * @see PagedDataModel 54 | * @author Bauke Scholtz 55 | */ 56 | public final class NonLazyPagedDataModel> extends LazyPagedDataModel { 57 | 58 | // Constants ------------------------------------------------------------------------------------------------------ 59 | 60 | private static final long serialVersionUID = 1L; 61 | 62 | 63 | // Internal properties -------------------------------------------------------------------------------------------- 64 | 65 | private List allData; 66 | 67 | 68 | // Constructors --------------------------------------------------------------------------------------------------- 69 | 70 | NonLazyPagedDataModel(List allData, LinkedHashMap defaultOrdering, Map predefinedCriteria, Supplier, Object>> dynamicCriteria) { 71 | super(null, defaultOrdering, predefinedCriteria, dynamicCriteria); 72 | this.allData = unmodifiableList(allData); 73 | } 74 | 75 | @Override 76 | @SuppressWarnings("unchecked") 77 | protected PartialResultList load(Page page, boolean estimateTotalNumberOfResults) { 78 | DataTable table = (DataTable) getDataComponent(); 79 | List data = new ArrayList<>(allData); 80 | 81 | if (!data.isEmpty()) { 82 | Class type = (Class) data.stream().filter(Objects::nonNull).map(Object::getClass).findFirst().orElse(null); 83 | 84 | if (type != null) { 85 | if (!page.getRequiredCriteria().isEmpty() || !page.getOptionalCriteria().isEmpty()) { 86 | Map, Entry> requiredCriteria = resolveGetters(type, page.getRequiredCriteria()); 87 | Map, Entry> optionalCriteria = resolveGetters(type, page.getOptionalCriteria()); 88 | BeanPropertyFilter filter = new BeanPropertyFilter(table, requiredCriteria, optionalCriteria); 89 | data = data.stream().filter(filter::matches).collect(toList()); 90 | } 91 | 92 | if (data.size() > 1) { 93 | Map, Entry> ordering = resolveGetters(type, page.getOrdering()); 94 | data.sort(new BeanPropertyComparator(table, ordering)); 95 | } 96 | } 97 | } 98 | 99 | int offset = min(data.size(), page.getOffset()); 100 | int limit = min(data.size() - offset, page.getLimit()); 101 | return new PartialResultList<>(new ArrayList<>(data.subList(offset, offset + limit)), offset, data.size()); 102 | } 103 | 104 | /** 105 | * Optimized version of PrimeFaces FilterFeature which does not use EL to resolve properties. 106 | */ 107 | private class BeanPropertyFilter { 108 | 109 | private final Locale locale; 110 | private final Map, Entry> requiredCriteria; 111 | private final Map, Entry> optionalCriteria; 112 | 113 | public BeanPropertyFilter(DataTable table, Map, Entry> requiredCriteria, Map, Entry> optionalCriteria) { 114 | this.locale = table.resolveDataLocale(); 115 | this.requiredCriteria = requiredCriteria; 116 | this.optionalCriteria = optionalCriteria; 117 | } 118 | 119 | public boolean matches(E entity) { 120 | if (entity == null) { 121 | return true; // Not our problem. 122 | } 123 | 124 | for (Entry, Entry> criteria : requiredCriteria.entrySet()) { 125 | if (!matches(entity, criteria)) { 126 | return false; 127 | } 128 | } 129 | 130 | for (Entry, Entry> criteria : optionalCriteria.entrySet()) { 131 | if (matches(entity, criteria)) { 132 | return true; 133 | } 134 | } 135 | 136 | return optionalCriteria.isEmpty(); 137 | } 138 | 139 | private boolean matches(E entity, Entry, Entry> criteria) { 140 | Object propertyValue = invokeMethods(entity, criteria.getKey(), null, false); 141 | Object criteriaValue = criteria.getValue().getValue(); 142 | 143 | if (propertyValue instanceof Collection && !(criteriaValue instanceof Criteria)) { 144 | return isEmpty(criteriaValue) || stream(criteriaValue).allMatch(value -> ((Collection) propertyValue).contains(value)); 145 | } 146 | else { 147 | return stream(criteriaValue).anyMatch(value -> { 148 | return (value instanceof Criteria && ((Criteria) value).applies(propertyValue)) 149 | || (Objects.equals(propertyValue, value)) 150 | || (Objects.equals(lower(propertyValue, locale), lower(value, locale))); 151 | }); 152 | } 153 | } 154 | } 155 | 156 | /** 157 | * Optimized version of PrimeFaces SortFeature which does not use EL to resolve properties. 158 | */ 159 | private class BeanPropertyComparator implements Comparator { 160 | 161 | private final Locale locale; 162 | private final Collator collator; 163 | private final Map sortBy; 164 | private final Map, Entry> ordering; 165 | 166 | public BeanPropertyComparator(DataTable table, Map, Entry> ordering) { 167 | this.locale = table.resolveDataLocale(); 168 | this.collator = Collator.getInstance(locale); 169 | this.sortBy = table.getActiveSortMeta(); 170 | this.ordering = ordering; 171 | } 172 | 173 | public BeanPropertyComparator(BeanPropertyComparator parent, Map, Entry> remainingOrdering) { 174 | this.locale = parent.locale; 175 | this.collator = parent.collator; 176 | this.sortBy = parent.sortBy; 177 | this.ordering = remainingOrdering; 178 | } 179 | 180 | @Override 181 | public int compare(E left, E right) { 182 | for (Entry, Entry> getter : ordering.entrySet()) { 183 | Object leftProperty = left != null ? invokeMethods(left, getter.getKey(), this, getter.getValue().getValue()) : null; 184 | Object rightProperty = right != null ? invokeMethods(right, getter.getKey(), this, getter.getValue().getValue()) : null; 185 | SortMeta sortMeta = sortBy.get(getter.getValue().getKey()); 186 | int result = compareProperties(leftProperty, rightProperty, sortMeta) * (getter.getValue().getValue() ? 1 : -1); 187 | 188 | if (result != 0) { 189 | return result; 190 | } 191 | } 192 | 193 | return 0; 194 | } 195 | 196 | @SuppressWarnings("unchecked") 197 | private int compareProperties(Object left, Object right, SortMeta sortMeta) { 198 | if (Objects.equals(left, right)) { 199 | return 0; 200 | } 201 | else if (left == null) { 202 | return sortMeta != null ? sortMeta.getNullSortOrder() : 1; 203 | } 204 | else if (right == null) { 205 | return sortMeta != null ? sortMeta.getNullSortOrder() : -1; 206 | } 207 | else if (left instanceof String && right instanceof String) { 208 | if (sortMeta != null && sortMeta.isCaseSensitiveSort()) { 209 | return collator.compare(left, right); 210 | } 211 | else { 212 | return collator.compare(lower(left, locale), lower(right, locale)); 213 | } 214 | } 215 | else if (left instanceof Comparable && right instanceof Comparable) { 216 | return ((Comparable) left).compareTo(right); 217 | } 218 | else { 219 | return compareProperties(left.toString(), right.toString(), sortMeta); 220 | } 221 | } 222 | } 223 | 224 | 225 | // Helpers -------------------------------------------------------------------------------------------------------- 226 | 227 | private static Map, Entry> resolveGetters(Class type, Map properties) { 228 | Map, Entry> getters = new LinkedHashMap<>(); 229 | 230 | for (Entry entry : properties.entrySet()) { 231 | Class beanClass = type; 232 | List methods = new ArrayList<>(2); 233 | 234 | for (String propertyName : entry.getKey().split("\\.")) { 235 | Method getter = resolveGetter(beanClass, propertyName); 236 | methods.add(getter); 237 | beanClass = getter.getReturnType(); 238 | 239 | if (Collection.class.isAssignableFrom(beanClass)) { 240 | Type genericReturnType = getter.getGenericReturnType(); 241 | 242 | if (genericReturnType instanceof ParameterizedType) { 243 | beanClass = (Class) ((ParameterizedType) genericReturnType).getActualTypeArguments()[0]; 244 | } 245 | } 246 | } 247 | 248 | getters.put(methods, entry); 249 | } 250 | 251 | return getters; 252 | } 253 | 254 | private static Method resolveGetter(Class beanClass, String propertyName) { 255 | try { 256 | return stream(Introspector.getBeanInfo(beanClass).getPropertyDescriptors()) 257 | .filter(property -> property.getName().equals(propertyName)) 258 | .map(PropertyDescriptor::getReadMethod) 259 | .findFirst().orElse(null); 260 | } 261 | catch (IntrospectionException e) { 262 | throw new UnsupportedOperationException(e); 263 | } 264 | } 265 | 266 | @SuppressWarnings({ "unchecked", "rawtypes" }) 267 | private Object invokeMethods(Object instance, List methods, BeanPropertyComparator comparator, boolean ascending) { 268 | Object result = instance; 269 | 270 | for (int i = 0; i < methods.size(); i++) { 271 | if (result instanceof List) { 272 | List remainingMethods = methods.subList(i, methods.size()); 273 | 274 | if (!remainingMethods.isEmpty() && comparator != null && ((List) result).size() > 1) { 275 | ((List) result).sort(new BeanPropertyComparator(comparator, singletonMap(remainingMethods, new AbstractMap.SimpleEntry<>(null, ascending)))); 276 | } 277 | 278 | return stream(result).map(item -> invokeMethods(item, remainingMethods, comparator, ascending)).collect(toList()); 279 | } 280 | else { 281 | result = invokeMethod(result, methods.get(i)); 282 | } 283 | } 284 | 285 | return result; 286 | } 287 | 288 | private static String lower(Object value, Locale locale) { 289 | return value == null ? null : value.toString().toLowerCase(locale); 290 | } 291 | 292 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/optimusfaces.taglib.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | http://omnifaces.org/optimusfaces 23 | op 24 | 25 | 26 | Renders a table based on p:dataTable with PagedDataModel. Each item is available by #{item}. 27 | dataTable 28 | resources/optimusfaces/tags/dataTable.xhtml 29 | 30 | 31 | The id of the p:dataTable. Will also be used as widgetVar name. 32 | id 33 | true 34 | java.lang.String 35 | 36 | 37 | Data model must be an instance of PagedDataModel. 38 | value 39 | true 40 | org.omnifaces.optimusfaces.model.PagedDataModel 41 | 42 | 43 | The style class of the p:dataTable. Defaults to null. 44 | styleClass 45 | false 46 | java.lang.String 47 | 48 | 49 | Whether the table is rendered. Defaults to true. 50 | rendered 51 | false 52 | boolean 53 | 54 | 55 | Whether to update the query string on every paging, sorting, filtering, searching and selection action. Defaults to true. 56 | updateQueryString 57 | false 58 | boolean 59 | 60 | 61 | The unique prefix of the query string parameter name in case you're having multiple tables in the same page while having updateQueryString. Defaults to empty string. 62 | queryParameterPrefix 63 | false 64 | java.lang.String 65 | 66 | 67 | 68 | Whether the table is sortable. Defaults to true. This can be overriden on a per-column basis when same attribute is also set in op:column. 69 | sortable 70 | false 71 | boolean 72 | 73 | 74 | Whether the table is filterable. Defaults to true. This can be overriden on a per-column basis when same attribute is also set in op:column. 75 | filterable 76 | false 77 | boolean 78 | 79 | 80 | Whether the table is paginable. Defaults to true. This will show a paginator in bottom of table. 81 | paginable 82 | false 83 | boolean 84 | 85 | 86 | Number of rows to display per page. Defaults to 10. 87 | rows 88 | false 89 | java.lang.Integer 90 | 91 | 92 | Options to show in rows per page dropdown of p:dataTable paginator template. Defaults to 10,25,50 93 | rowsPerPage 94 | false 95 | java.lang.String 96 | 97 | 98 | The p:dataTable paginator template. Defaults to {CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} 99 | paginatorTemplate 100 | false 101 | java.lang.String 102 | 103 | 104 | Text to display before the {CurrentPageReport} template. Defaults to "Showing". 105 | currentPageReportPrefix 106 | false 107 | java.lang.String 108 | 109 | 110 | The {CurrentPageReport} template. Defaults to {startRecord} - {endRecord} of {totalRecords} 111 | currentPageReportTemplate 112 | false 113 | java.lang.String 114 | 115 | 116 | Text to display after the {CurrentPageReport} template. Defaults to "records" 117 | currentPageReportSuffix 118 | false 119 | java.lang.String 120 | 121 | 122 | 123 | Whether the table is searchable. Defaults to false. This will show a global filter input field in top of table. 124 | searchable 125 | false 126 | boolean 127 | 128 | 129 | The placeholder of the global search field. Defaults to "Search…". 130 | searchPlaceholder 131 | false 132 | java.lang.String 133 | 134 | 135 | The label of the global search button. Defaults to "Search". 136 | searchButtonLabel 137 | false 138 | java.lang.String 139 | 140 | 141 | 142 | Whether the table is exportable. Defaults to false. This will show a column toggler and export button in top of table. 143 | exportable 144 | false 145 | boolean 146 | 147 | 148 | The label of the column toggler button. Defaults to "Columns". 149 | columnTogglerButtonLabel 150 | false 151 | java.lang.String 152 | 153 | 154 | The export file type. Defaults to "csv". This delegates to p:dataExporter type. 155 | exportType 156 | false 157 | java.lang.String 158 | 159 | 160 | The label of the export button. Defaults to "CSV". 161 | exportButtonLabel 162 | false 163 | java.lang.String 164 | 165 | 166 | The label of the export visible columns button. Defaults to "Visible Columns". 167 | exportVisibleColumnsButtonLabel 168 | false 169 | java.lang.String 170 | 171 | 172 | The label of the export all columns button. Defaults to "All Columns". 173 | exportAllColumnsButtonLabel 174 | false 175 | java.lang.String 176 | 177 | 178 | The file name format of the exported file. Defaults to #{id}-#{of:formatDate(now, 'yyyyMMddHHmmss')}. 179 | exportFilename 180 | false 181 | java.lang.String 182 | 183 | 184 | Method to pre-process the exported document. 185 | exportPreProcessor 186 | false 187 | void preProcess(java.lang.Object) 188 | 189 | 190 | Method to post-process the exported document. 191 | exportPostProcessor 192 | false 193 | void postProcess(java.lang.Object) 194 | 195 | 196 | 197 | <ui:define name="actions"> in top of table. Defaults to searchable or exportable.]]> 198 | actionable 199 | false 200 | boolean 201 | 202 | 203 | 204 | Whether the table is selectable. Defaults to false. 205 | selectable 206 | false 207 | boolean 208 | 209 | 210 | 211 | 212 | Renders a column based on p:column for op:dataTable. Each item is available by #{item}. 213 | column 214 | resources/optimusfaces/tags/column.xhtml 215 | 216 | 217 | The field (property) name of the #{item} representing the column value. By default, this is used in id, field, headerText, value and filterBy attributes of p:column. 218 | field 219 | true 220 | java.lang.String 221 | 222 | 223 | The column ID. Defaults to field. 224 | id 225 | false 226 | java.lang.String 227 | 228 | 229 | The column header text. Defaults to field. 230 | head 231 | false 232 | java.lang.String 233 | 234 | 235 | The column value. Defaults to #{item[field]}. 236 | value 237 | false 238 | java.lang.Object 239 | 240 | 241 | The column cell tooltip. Defaults to nothing. 242 | tooltip 243 | false 244 | java.lang.String 245 | 246 | 247 | The style class of the p:column. Defaults to null. 248 | styleClass 249 | false 250 | java.lang.String 251 | 252 | 253 | Whether the column is rendered. Defaults to true. 254 | rendered 255 | false 256 | boolean 257 | 258 | 259 | Whether the column is by default visible when using column toggler. Defaults to true. 260 | visible 261 | false 262 | boolean 263 | 264 | 265 | The width of the p:column. Defaults to null. 266 | width 267 | false 268 | java.lang.String 269 | 270 | 271 | Whether the value is iterable. This will trigger the value to be rendered in an ui:repeat with linebreaks. Defaults to whether the field property is an instance of Iterable. 272 | iterable 273 | false 274 | boolean 275 | 276 | 277 | 278 | Whether the column is sortable. Defaults to sortable attribute of parent op:dataTable. 279 | sortable 280 | false 281 | boolean 282 | 283 | 284 | Whether to sort column descending on first click. Defaults to false. This attribute is ignored when sortable=false. 285 | sortDescending 286 | false 287 | boolean 288 | 289 | 290 | 291 | Whether the column is filterable. Defaults to filterable attribute of parent op:dataTable. 292 | filterable 293 | false 294 | boolean 295 | 296 | 297 | The filter mode of the field. Supported values are "contains", "startsWith", "endsWith" and "exact". Defaults to "contains" when filterOptions=null, else "exact". Any unsupported value is interpreted as "exact". This attribute is ignored when filterable=false. 298 | filterMode 299 | false 300 | java.lang.String 301 | 302 | 303 | The filter options of the field. Supported values are Object[], Collection and Map. This attribute is ignored when filterable=false. 304 | filterOptions 305 | false 306 | java.lang.Object 307 | 308 | 309 | 310 | Whether the column is exportable. Defaults to true. 311 | exportable 312 | false 313 | boolean 314 | 315 | 316 | The export value. Defaults to #{value}. 317 | exportValue 318 | false 319 | java.lang.Object 320 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /src/main/java/org/omnifaces/optimusfaces/model/LazyPagedDataModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OmniFaces 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * https://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | package org.omnifaces.optimusfaces.model; 14 | 15 | import static java.lang.Boolean.parseBoolean; 16 | import static java.lang.Math.abs; 17 | import static java.util.Collections.emptyList; 18 | import static java.util.Collections.emptyMap; 19 | import static java.util.Collections.singletonMap; 20 | import static java.util.Collections.unmodifiableSet; 21 | import static java.util.Optional.ofNullable; 22 | import static java.util.function.Function.identity; 23 | import static java.util.stream.Collectors.toList; 24 | import static java.util.stream.Collectors.toSet; 25 | import static javax.faces.component.UIComponent.getCurrentComponent; 26 | import static org.omnifaces.persistence.model.Identifiable.ID; 27 | import static org.omnifaces.util.Ajax.oncomplete; 28 | import static org.omnifaces.util.Components.getCurrentComponent; 29 | import static org.omnifaces.util.Faces.getContext; 30 | import static org.omnifaces.util.FacesLocal.getRequestParameter; 31 | import static org.omnifaces.util.FacesLocal.getRequestParameterValues; 32 | import static org.omnifaces.util.FacesLocal.isAjaxRequest; 33 | import static org.omnifaces.utils.Lang.coalesce; 34 | import static org.omnifaces.utils.Lang.isEmpty; 35 | import static org.omnifaces.utils.stream.Collectors.toLinkedMap; 36 | import static org.omnifaces.utils.stream.Collectors.toLinkedSet; 37 | import static org.omnifaces.utils.stream.Streams.stream; 38 | import static org.primefaces.model.SortOrder.ASCENDING; 39 | import static org.primefaces.model.SortOrder.DESCENDING; 40 | 41 | import java.util.ArrayList; 42 | import java.util.HashMap; 43 | import java.util.LinkedHashMap; 44 | import java.util.List; 45 | import java.util.Map; 46 | import java.util.Map.Entry; 47 | import java.util.Objects; 48 | import java.util.Set; 49 | import java.util.function.Supplier; 50 | import java.util.stream.Collectors; 51 | import java.util.stream.Stream; 52 | 53 | import javax.faces.component.UIComponent; 54 | import javax.faces.component.UINamingContainer; 55 | import javax.faces.context.FacesContext; 56 | 57 | import org.omnifaces.component.ParamHolder; 58 | import org.omnifaces.component.SimpleParam; 59 | import org.omnifaces.persistence.criteria.Criteria; 60 | import org.omnifaces.persistence.criteria.Like; 61 | import org.omnifaces.persistence.model.Identifiable; 62 | import org.omnifaces.persistence.model.dto.Page; 63 | import org.omnifaces.persistence.service.BaseEntityService; 64 | import org.omnifaces.util.Servlets; 65 | import org.omnifaces.utils.Lang; 66 | import org.omnifaces.utils.collection.PartialResultList; 67 | import org.omnifaces.utils.reflect.Getter; 68 | import org.primefaces.component.api.UIColumn; 69 | import org.primefaces.component.api.UIData; 70 | import org.primefaces.component.datascroller.DataScroller; 71 | import org.primefaces.component.datatable.DataTable; 72 | import org.primefaces.model.FilterMeta; 73 | import org.primefaces.model.LazyDataModel; 74 | import org.primefaces.model.SortMeta; 75 | import org.primefaces.model.SortOrder; 76 | 77 | /** 78 | * Use {@link PagedDataModel#lazy(BaseEntityService)} or {@link PagedDataModel#lazy(PagedDataModel.PartialResultListLoader)} to build one. 79 | * 80 | * @see PagedDataModel 81 | * @author Bauke Scholtz 82 | */ 83 | public class LazyPagedDataModel> extends LazyDataModel implements PagedDataModel { 84 | 85 | // Constants ------------------------------------------------------------------------------------------------------ 86 | 87 | private static final long serialVersionUID = 1L; 88 | private static final String GLOBAL_FILTER = "globalFilter"; 89 | 90 | 91 | // Internal properties -------------------------------------------------------------------------------------------- 92 | 93 | private final PartialResultListLoader loader; 94 | private final LinkedHashMap defaultOrdering; 95 | private final Map predefinedCriteria; 96 | private final Supplier, Object>> dynamicCriteria; 97 | 98 | protected boolean updateQueryString; 99 | protected String queryParameterPrefix; 100 | protected LinkedHashMap ordering; 101 | protected LinkedHashMap filters; 102 | protected String globalFilter; 103 | 104 | private Page page; 105 | private PartialResultList list; 106 | 107 | 108 | // op:dataTable properties ---------------------------------------------------------------------------------------- 109 | 110 | private List filteredValue; 111 | private List selection; 112 | 113 | 114 | // Constructors --------------------------------------------------------------------------------------------------- 115 | 116 | LazyPagedDataModel(PartialResultListLoader loader, LinkedHashMap defaultOrdering, Map predefinedCriteria, Supplier, Object>> dynamicCriteria) { 117 | this.loader = loader; 118 | this.defaultOrdering = defaultOrdering; 119 | this.predefinedCriteria = predefinedCriteria; 120 | this.dynamicCriteria = dynamicCriteria; 121 | filters = new LinkedHashMap<>(); 122 | page = Page.ALL; 123 | setRowCount(-1); 124 | } 125 | 126 | 127 | // Actions -------------------------------------------------------------------------------------------------------- 128 | 129 | @Override 130 | public List load(int offset, int limit, Map sortBy, Map filterBy) { 131 | FacesContext context = getContext(); 132 | UIData data = getDataComponent(); 133 | 134 | if (data instanceof DataTable) { 135 | loadPage(context, (DataTable) data, sortBy, filterBy); 136 | } 137 | else if (data instanceof DataScroller) { 138 | loadPage(data, offset, limit, emptyMap(), emptyMap()); 139 | } 140 | else { 141 | throw new UnsupportedOperationException("UIData component " + data + " is not yet supported."); 142 | } 143 | 144 | return list; 145 | } 146 | 147 | // Introduced in PrimeFaces 11.0.0, but we want to keep OptimusFaces backwards compatible with PrimeFaces 10.0.0, 148 | // hence the @Override annotation is dropped for now. 149 | public int count(Map filterBy) { 150 | return list == null ? 0 : list.getEstimatedTotalNumberOfResults(); 151 | } 152 | 153 | public void preloadPage(FacesContext context, DataTable table) { 154 | SortMeta sortBy = getInitialOrdering(context, table); 155 | loadPage(context, table, singletonMap(sortBy.getField(), sortBy), emptyMap()); 156 | setWrappedData(list); 157 | setRowCount(list.getEstimatedTotalNumberOfResults()); 158 | setPageSize(table.getRows()); 159 | } 160 | 161 | private void loadPage(FacesContext context, DataTable table, Map sortBy, Map filterBy) { 162 | List processableColumns = table.getColumns().stream().filter(this::isProcessableColumn).collect(toList()); 163 | 164 | updateQueryString = parseBoolean(String.valueOf(table.getAttributes().get("updateQueryString"))); 165 | queryParameterPrefix = parseQueryParameterPrefix(table); 166 | ordering = processPageAndOrdering(context, table, sortBy); 167 | filters = processFilters(context, table, processableColumns, filterBy); 168 | globalFilter = processGlobalFilter(context, table, filterBy); 169 | selection = processSelectionIfNecessary(context, selection); 170 | 171 | int offset = table.getFirst(); 172 | int limit = table.getRows(); 173 | Map requiredCriteria = processRequiredCriteria(processableColumns); 174 | Map optionalCriteria = processOptionalCriteria(processableColumns); 175 | 176 | loadPage(table, offset, limit, requiredCriteria, optionalCriteria); 177 | updateQueryStringIfNecessary(context); 178 | } 179 | 180 | private static String parseQueryParameterPrefix(DataTable table) { 181 | return coalesce((String) table.getAttributes().get("queryParameterPrefix"), ""); 182 | } 183 | 184 | private void loadPage(UIData data, int offset, int limit, Map requiredCriteria, Map optionalCriteria) { 185 | boolean pageOfSameCriteria = requiredCriteria.equals(page.getRequiredCriteria()) && optionalCriteria.equals(page.getOptionalCriteria()); 186 | boolean nextOrPreviousPageOfSameCriteria = pageOfSameCriteria && !isEmpty(list) && abs(offset - page.getOffset()) == limit && ordering.equals(page.getOrdering()); 187 | boolean previousPageOfSameCriteria = nextOrPreviousPageOfSameCriteria && offset < page.getOffset(); 188 | boolean rowCountNeedsUpdate = getRowCount() <= 0 || !pageOfSameCriteria; 189 | E last = nextOrPreviousPageOfSameCriteria ? list.get(previousPageOfSameCriteria ? 0 : list.size() - 1) : null; 190 | 191 | page = new Page(offset, limit, last, previousPageOfSameCriteria, ordering, requiredCriteria, optionalCriteria); 192 | list = load(page, rowCountNeedsUpdate); 193 | int count = list.getEstimatedTotalNumberOfResults(); 194 | 195 | if (count != -1 && count != getRowCount()) { 196 | if (list.isEmpty() && count > 0 && offset > count) { // Can happen when user has paginated too far and then changed criteria which returned fewer results. 197 | int offsetOfLastPage = offset - ((((offset - count) / data.getRows()) + 1) * data.getRows()); 198 | data.setFirst(offsetOfLastPage); 199 | page = new Page(offsetOfLastPage, limit, ordering, requiredCriteria, optionalCriteria); 200 | list = load(page, false); 201 | } 202 | 203 | setRowCount(count); 204 | } 205 | } 206 | 207 | protected PartialResultList load(Page page, boolean estimateTotalNumberOfResults) { 208 | return loader.getPage(page, estimateTotalNumberOfResults); 209 | } 210 | 211 | protected UIData getDataComponent() { 212 | UIComponent currentComponent = getCurrentComponent(); 213 | 214 | if (currentComponent instanceof UIData) { 215 | return (UIData) currentComponent; 216 | } 217 | else { 218 | String id = currentComponent.getId().split("_", 2)[0]; 219 | return (UIData) currentComponent.findComponent(id); 220 | } 221 | } 222 | 223 | protected boolean isProcessableColumn(UIColumn column) { 224 | if (column.getField() == null) { 225 | return false; 226 | } 227 | 228 | if (column.isFilterable()) { 229 | return true; 230 | } 231 | 232 | DataTable table = (DataTable) ((UIComponent) column).getParent(); 233 | return Boolean.parseBoolean(String.valueOf(table.getAttributes().get("searchable"))); 234 | } 235 | 236 | protected LinkedHashMap processPageAndOrdering(FacesContext context, DataTable table, Map sortBy) { 237 | LinkedHashMap ordering = new LinkedHashMap<>(2); 238 | 239 | SortMeta tableSortMeta = sortBy.isEmpty() ? new SortMeta() : sortBy.values().iterator().next(); 240 | String tableSortField = tableSortMeta.getField(); 241 | SortOrder tableSortOrder = tableSortMeta.getOrder(); 242 | 243 | if (list == null) { 244 | String page = getTrimmedQueryParameter(context, queryParameterPrefix + QUERY_PARAMETER_PAGE); 245 | 246 | if (!isEmpty(page)) { 247 | try { 248 | table.setFirst((Integer.valueOf(page) - 1) * table.getRows()); 249 | } 250 | catch (NumberFormatException ignore) { 251 | // 252 | } 253 | } 254 | } 255 | 256 | if (!isEmpty(tableSortField)) { 257 | ordering.put(tableSortField, tableSortOrder == ASCENDING); 258 | } 259 | 260 | defaultOrdering.forEach((defaultSortField, defaultSortAscending) -> ordering.putIfAbsent(defaultSortField, defaultSortAscending)); 261 | return ordering; 262 | } 263 | 264 | protected SortMeta getInitialOrdering(FacesContext context, DataTable table) { 265 | String sort = getTrimmedQueryParameter(context, parseQueryParameterPrefix(table) + QUERY_PARAMETER_ORDER); 266 | 267 | if (!isEmpty(sort)) { 268 | String field; 269 | SortOrder order; 270 | 271 | if (sort.startsWith("-")) { 272 | field = sort.substring(1); 273 | order = DESCENDING; 274 | } 275 | else { 276 | field = sort; 277 | order = ASCENDING; 278 | } 279 | 280 | if (!isEmpty(sort) && table.getColumns().stream().anyMatch(column -> field.equals(column.getField()))) { 281 | return SortMeta.builder().field(field).order(order).build(); 282 | } 283 | } 284 | 285 | Entry defaultOrder = defaultOrdering.entrySet().iterator().next(); 286 | return SortMeta.builder().field(defaultOrder.getKey()).order(defaultOrder.getValue() ? ASCENDING : DESCENDING).build(); 287 | } 288 | 289 | protected LinkedHashMap processFilters(FacesContext context, DataTable table, List processableColumns, Map filterBy) { 290 | LinkedHashMap mergedFilters = new LinkedHashMap<>(); 291 | 292 | for (UIColumn column : processableColumns) { 293 | String field = column.getField(); 294 | Object value = getFilterValue(filterBy, field); 295 | 296 | if (isEmpty(value)) { 297 | value = getTrimmedQueryParameters(context, getFilterParameterName(context, table, field)); 298 | } 299 | 300 | if (!isEmpty(value)) { 301 | mergedFilters.put(field, normalizeCriteriaValue(value)); 302 | } 303 | } 304 | 305 | return mergedFilters; 306 | } 307 | 308 | protected List processSelectionIfNecessary(FacesContext context, List currentSelection) { 309 | if (currentSelection != null || context.isPostback()) { 310 | return currentSelection; 311 | } 312 | 313 | List selection = getTrimmedQueryParameters(context, queryParameterPrefix + QUERY_PARAMETER_SELECTION); 314 | return selection.isEmpty() ? emptyList() : new ArrayList<>(load(new Page(0, selection.size(), null, singletonMap(ID, selection), null), false)); 315 | } 316 | 317 | protected String processGlobalFilter(FacesContext context, DataTable table, Map filterBy) { 318 | String globalFilter = getFilterValue(filterBy, GLOBAL_FILTER); 319 | 320 | if (globalFilter != null) { 321 | globalFilter = globalFilter.trim(); 322 | } 323 | 324 | if (isEmpty(globalFilter)) { 325 | globalFilter = getTrimmedQueryParameter(context, getFilterParameterName(context, table, null)); 326 | } 327 | 328 | return isEmpty(globalFilter) ? null : globalFilter; 329 | } 330 | 331 | private String getFilterValue(Map filterBy, String field) { 332 | FilterMeta filterMeta = filterBy.get(field); 333 | return (filterMeta == null) ? null : (String) filterMeta.getFilterValue(); 334 | } 335 | 336 | private String getFilterParameterName(FacesContext context, DataTable table, String field) { 337 | String param; 338 | 339 | if (context.isPostback()) { 340 | char separatorChar = UINamingContainer.getSeparatorChar(context); 341 | param = table.getClientId(context) + separatorChar + (field == null ? "" : (field + separatorChar)) + "filter"; 342 | } 343 | else { 344 | param = queryParameterPrefix + ((field == null) ? QUERY_PARAMETER_SEARCH : field); 345 | } 346 | 347 | return param; 348 | } 349 | 350 | protected Map processRequiredCriteria(List processableColumns) { 351 | Map requiredCriteria = new HashMap<>(); 352 | 353 | if (predefinedCriteria != null) { 354 | requiredCriteria.putAll(predefinedCriteria); 355 | } 356 | 357 | if (dynamicCriteria != null) { 358 | ofNullable(dynamicCriteria.get()).orElse(emptyMap()).forEach((getter, value) -> processDynamicCriteria(getter.getPropertyName(), value, requiredCriteria)); 359 | } 360 | 361 | for (UIColumn column : processableColumns) { 362 | String field = column.getField(); 363 | Object value = filters.get(field); 364 | 365 | if (!isEmpty(value)) { 366 | String filterMatchMode = column.getFilterMatchMode(); 367 | 368 | if ("startsWith".equals(filterMatchMode)) { 369 | value = Like.startsWith(value.toString()); 370 | } 371 | else if ("endsWith".equals(filterMatchMode)) { 372 | value = Like.endsWith(value.toString()); 373 | } 374 | else if ("contains".equals(filterMatchMode)) { 375 | value = Like.contains(value.toString()); 376 | } 377 | 378 | requiredCriteria.merge(field, value, LazyPagedDataModel::mergeCriteriaValue); 379 | } 380 | } 381 | 382 | return requiredCriteria; 383 | } 384 | 385 | @SuppressWarnings("unchecked") 386 | private static void processDynamicCriteria(String field, Object value, Map requiredCriteria) { 387 | if (value instanceof Map && !((Map) value).isEmpty() && ((Map) value).keySet().iterator().next() instanceof Getter) { 388 | Map, Object> nestedCriteria = (Map, Object>) value; 389 | nestedCriteria.forEach((getter, nestedValue) -> processDynamicCriteria(field + "." + getter.getPropertyName(), nestedValue, requiredCriteria)); 390 | } 391 | else { 392 | requiredCriteria.put(field, value); 393 | } 394 | } 395 | 396 | protected Map processOptionalCriteria(List processableColumns) { 397 | Map optionalCriteria = new HashMap<>(); 398 | 399 | for (UIColumn column : processableColumns) { 400 | String field = column.getField(); 401 | 402 | if (!isEmpty(globalFilter)) { 403 | optionalCriteria.put(field, Like.contains(globalFilter)); 404 | } 405 | } 406 | 407 | return optionalCriteria; 408 | } 409 | 410 | protected void updateQueryStringIfNecessary(FacesContext context) { 411 | if (!updateQueryString || !isAjaxRequest(context)) { 412 | return; 413 | } 414 | 415 | List params = new ArrayList<>(); 416 | 417 | if (!isEmpty(globalFilter)) { 418 | params.add(new SimpleParam(queryParameterPrefix + QUERY_PARAMETER_SEARCH, globalFilter)); 419 | } 420 | 421 | int currentPage = (page.getOffset() / page.getLimit()) + 1; 422 | 423 | if (currentPage > 1) { 424 | params.add(new SimpleParam(queryParameterPrefix + QUERY_PARAMETER_PAGE, currentPage)); 425 | } 426 | 427 | if (!page.getOrdering().equals(defaultOrdering)) { 428 | Entry order = page.getOrdering().entrySet().iterator().next(); 429 | params.add(new SimpleParam(queryParameterPrefix + QUERY_PARAMETER_ORDER, (order.getValue() ? "" : "-") + order.getKey())); 430 | } 431 | 432 | filters.entrySet().stream() 433 | .forEach(entry -> stream(normalizeCriteriaValue(stream(entry.getValue()).map(Criteria::unwrap))) 434 | .forEach(value -> params.add(new SimpleParam(queryParameterPrefix + entry.getKey(), value)))); 435 | 436 | if (selection != null) { 437 | selection.stream().sorted().forEach(entity -> params.add(new SimpleParam(queryParameterPrefix + QUERY_PARAMETER_SELECTION, entity.getId()))); 438 | } 439 | 440 | oncomplete("OptimusFaces.Util.updateQueryString('" + Servlets.toQueryString(params) + "')"); 441 | } 442 | 443 | 444 | // PagedDataModel state ------------------------------------------------------------------------------------------- 445 | 446 | @Override 447 | public Page getPage() { 448 | return page; 449 | } 450 | 451 | 452 | // Getters+setters for op:dataTable and op:column ----------------------------------------------------------------- 453 | 454 | @Override 455 | public String getRowKey(E entity) { 456 | return String.valueOf(entity.getId() != null ? entity.getId() : entity.hashCode()); 457 | } 458 | 459 | @Override 460 | public E getRowData(String rowKey) { 461 | return load(new Page(0, 1, null, singletonMap(ID, rowKey), null), false).get(0); 462 | } 463 | 464 | @Override 465 | public SortMeta getOrdering() { 466 | FacesContext context = FacesContext.getCurrentInstance(); 467 | DataTable table = (DataTable) getCurrentComponent(context); 468 | 469 | return ordering == null ? getInitialOrdering(context, table) : ordering.entrySet().stream() 470 | .map(entry -> SortMeta.builder().field(entry.getKey()).order(entry.getValue() ? SortOrder.ASCENDING : SortOrder.DESCENDING).build()) 471 | .findFirst().orElse(null); // TODO: optimize/cache this (and utilize the new multisort feature) 472 | } 473 | 474 | @Override 475 | public Map getFilters() { 476 | return filters == null ? emptyMap() : filters.entrySet().stream() 477 | .map(entry -> FilterMeta.builder().field(entry.getKey()).filterValue(entry.getValue()).build()) 478 | .collect(Collectors.toMap(FilterMeta::getField, identity())); // TODO: optimize/cache this 479 | } 480 | 481 | @Override 482 | public List getFilteredValue() { 483 | return filteredValue; 484 | } 485 | 486 | @Override 487 | public void setFilteredValue(List filteredValue) { 488 | this.filteredValue = filteredValue; 489 | } 490 | 491 | @Override 492 | public List getSelection() { 493 | return selection; 494 | } 495 | 496 | @Override 497 | public void setSelection(List selection) { 498 | if (!Objects.equals(selection, this.selection)) { 499 | this.selection = selection; 500 | updateQueryStringIfNecessary(getContext()); 501 | } 502 | } 503 | 504 | 505 | // Helpers --------------------------------------------------------------------------------------------------------- 506 | 507 | private static Object normalizeCriteriaValue(Object value) { 508 | Set set = stream(value).collect(toLinkedSet()); 509 | return set.size() == 1 ? set.iterator().next() : unmodifiableSet(set); 510 | } 511 | 512 | private static Object mergeCriteriaValue(Object oldValue, Object newValue) { 513 | Map newValues = stream(newValue).collect(toLinkedMap(Object::toString)); 514 | newValues.keySet().removeAll(stream(oldValue).map(Object::toString).collect(toSet())); 515 | 516 | if (newValues.isEmpty()) { 517 | return oldValue; 518 | } 519 | else { 520 | return normalizeCriteriaValue(Stream.concat(stream(oldValue), stream(newValues.values()))); 521 | } 522 | } 523 | 524 | private static String getTrimmedQueryParameter(FacesContext context, String name) { 525 | String param = getRequestParameter(context, name); 526 | return (param != null) ? param.trim() : null; 527 | } 528 | 529 | private static List getTrimmedQueryParameters(FacesContext context, String name) { 530 | String[] params = getRequestParameterValues(context, name); 531 | return params != null ? stream(params).filter(Lang::isNotBlank).collect(toList()) : emptyList(); 532 | } 533 | 534 | } --------------------------------------------------------------------------------