Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK 17
uses: actions/setup-java@v4
Expand Down
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,52 @@ To execute a specific test:
mvn clean test -Ppre-deployment-verification -pl <module-name> -Dtest=<test-class-name>
```

By default the logging output in tests is minimized.
The general log level is controlled by the variable `ROOT_LOG_LEVEL`, that in tests is set to `WARN` by default.
By default the logging output in tests is minimized. See [Logging](#logging) below for how to get
verbose/`DEBUG` output, both for the running webapp and for test runs (they work differently).

## Logging

Logging is configured via `engine/src/main/resources/base.xml` (logback) and driven by two environment
variables:
- `ROOT_LOG_LEVEL` — the root logger level. Defaults to `DEBUG` when running the webapp; overridden to
`WARN` when running tests (see `pom.xml` surefire configuration).
- `LOG_LEVEL` — the console (`STDOUT`) appender threshold. Defaults to `WARN`, regardless of
`ROOT_LOG_LEVEL`.

To run the webapp locally with `DEBUG` logs printed to the console:

```
cd webapp/
LOG_LEVEL=DEBUG mvn package jetty:run-war -Pjetty-local -Dspring.profiles.active=swagger -DskipTests -DskipLicenseDownload -Pderby -Pkeycloak
```

`ROOT_LOG_LEVEL` does not need to be set for this, since it already defaults to `DEBUG` outside of tests;
`LOG_LEVEL` is the variable that actually gates what reaches the console.

Test runs are different: `entando-engine`'s test-jar ships `logback-test.xml`, which every other module
picks up on its test classpath. It hardcodes `<root level="INFO">` (with explicit per-package `DEBUG`
overrides only for a couple of Spring test loggers), so **`ROOT_LOG_LEVEL`/`LOG_LEVEL` have no effect on
test runs** — only on the running webapp. To get `DEBUG` output from a test run, point Logback at a
throwaway config instead:

```
cat > /tmp/logback-debug.xml <<'EOF'
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
EOF
mvn clean test -Ppre-deployment-verification -pl <module-name> -Dtest=<test-class-name> \
-DargLine=-Dlogback.configurationFile=/tmp/logback-debug.xml
```

(Alternatively, add a one-off `<logger name="<package>" level="DEBUG"/>` line to
`engine/src/test/resources/logback-test.xml` before running the test — cheaper for a quick, throwaway
check, but remember to revert it since it's a shared test resource.)

## Environment Variables List
| Group | Name | Value [default] | Description |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
Expand All @@ -14,6 +14,7 @@
package com.agiletec.apsadmin.system.entity.type;

import com.agiletec.aps.system.common.entity.IEntityManager;
import com.agiletec.aps.system.common.entity.NestedBooleanSearchSupport;
import com.agiletec.aps.system.common.entity.model.IApsEntity;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeRole;
Expand Down Expand Up @@ -266,6 +267,24 @@
}
return false;
}

/**
* Whether the given attribute type may be flagged searchable when used as a <b>composite child</b>.
* Only plain boolean children are indexed (under the path key "&lt;composite&gt;_&lt;boolean&gt;") in
* the DB search tables; every other type - including CheckBox and ThreeState - is forced
* non-searchable as a composite child, so the searchable option must not be offered for them.
* @param attributeTypeCode the attribute type code.
* @return true only for the plain boolean type.
*/
public boolean isNestedSearchableOptionSupported(String attributeTypeCode) {
try {
AttributeInterface attribute = this.getAttributePrototype(attributeTypeCode);
return NestedBooleanSearchSupport.isIndexableNestedBoolean(attribute);
} catch (Exception t) {
_logger.error("error in isNestedSearchableOptionSupported", t);
}
return false;
}

public AttributeInterface getAttributePrototype(String typeCode) {
IEntityManager entityManager = this.getEntityManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@
</div>
</div>
</s:if>
<s:if test="isNestedSearchableOptionSupported(attributeTypeCode)">
<div class="form-group">
<div class="col-xs-2 control-label ">

<label for="searchable">
<s:text name="Entity.attribute.flag.searchable.full" />
</label>
</div>
<div class="col-xs-10">
<wpsf:checkbox name="searchable" id="searchable" cssClass="bootstrap-switch"/>
</div>
</div>
</s:if>

</fieldset>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@

import static com.agiletec.apsadmin.system.entity.type.ICompositeAttributeConfigAction.COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM;
import static com.agiletec.apsadmin.system.entity.type.IEntityTypeConfigAction.ENTITY_TYPE_ON_EDIT_SESSION_PARAM;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.agiletec.aps.system.common.entity.IEntityManager;
import com.agiletec.aps.system.common.entity.model.IApsEntity;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
import com.agiletec.aps.system.common.entity.model.attribute.BooleanAttribute;
import com.agiletec.aps.system.common.entity.model.attribute.CheckBoxAttribute;
import com.agiletec.aps.system.common.entity.model.attribute.CompositeAttribute;
import com.agiletec.aps.system.common.entity.model.attribute.MonoListAttribute;
import com.agiletec.aps.system.common.entity.model.attribute.TextAttribute;
import com.agiletec.aps.system.common.entity.model.attribute.ThreeStateAttribute;
import com.agiletec.apsadmin.system.ApsAdminSystemConstants;
import com.agiletec.apsadmin.system.BaseAction;
import org.apache.struts2.action.Action;
import org.apache.struts2.text.TextProvider;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
import org.apache.struts2.action.Action;
import org.apache.struts2.text.TextProvider;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.BeanFactory;

Expand All @@ -47,13 +54,13 @@ class CompositeAttributeConfigActionTest {

@BeforeEach
void setUp() {
Mockito.when(request.getSession()).thenReturn(session);
when(request.getSession()).thenReturn(session);

CompositeAttribute compositeAttribute = new CompositeAttribute();
compositeAttribute.setName(COMPOSITE_ATTRIBUTE_NAME);
addTextAttribute(compositeAttribute, "attribute1");
addTextAttribute(compositeAttribute, "attribute2");
Mockito.lenient().when(session.getAttribute(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM)).thenReturn(compositeAttribute);
lenient().when(session.getAttribute(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM)).thenReturn(compositeAttribute);
}

private void addTextAttribute(CompositeAttribute compositeAttribute, String attributeName) {
Expand All @@ -68,52 +75,85 @@ void testMoveAttribute() {
action.setMovement(ApsAdminSystemConstants.MOVEMENT_UP_CODE);
action.setAttributeIndex(1);
action.moveAttributeElement();
Mockito.verify(session, Mockito.times(1))
.setAttribute(Mockito.eq(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM), any());
verify(session, times(1))
.setAttribute(eq(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM), any());
}

@Test
void testRemoveAttributeElement() {
action.setAttributeIndex(0);
action.removeAttributeElement();
Mockito.verify(session, Mockito.times(1))
.setAttribute(Mockito.eq(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM), any());
verify(session, times(1))
.setAttribute(eq(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM), any());
}


@Test
void testSaveAttributeElement() {
String entityManagerName = "EntityManagerName";
String attributeTypeCode = "typeCode";
Mockito.when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
.thenReturn(entityManagerName);
Map<String, AttributeInterface> attributeTypes = new HashMap<>();
attributeTypes.put(attributeTypeCode, new TextAttribute());
IEntityManager entityManager = Mockito.mock(IEntityManager.class);
Mockito.when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
Mockito.when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);
IEntityManager entityManager = mock(IEntityManager.class);
when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);
action.setAttributeTypeCode(attributeTypeCode);
action.saveAttributeElement();
Mockito.verify(session, Mockito.times(1))
.setAttribute(Mockito.eq(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM), any());
verify(session, times(1))
.setAttribute(eq(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM), any());
}

@Test
void testNestedSearchableOptionSupportedForBooleanLikes() {
String entityManagerName = "EntityManagerName";
when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
.thenReturn(entityManagerName);
Map<String, AttributeInterface> attributeTypes = new HashMap<>();
attributeTypes.put("Boolean", new BooleanAttribute());
attributeTypes.put("CheckBox", new CheckBoxAttribute());
attributeTypes.put("ThreeState", new ThreeStateAttribute());
attributeTypes.put("Text", new TextAttribute());
IEntityManager entityManager = mock(IEntityManager.class);
when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);

// every boolean-like composite child may be flagged searchable; other types may not
Assertions.assertTrue(action.isNestedSearchableOptionSupported("Boolean"));
Assertions.assertTrue(action.isNestedSearchableOptionSupported("CheckBox"));
Assertions.assertTrue(action.isNestedSearchableOptionSupported("ThreeState"));
Assertions.assertFalse(action.isNestedSearchableOptionSupported("Text"));
}

@Test
void testNestedSearchableOptionSupportedHandlesException() {
String entityManagerName = "EntityManagerName";
when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
.thenReturn(entityManagerName);
IEntityManager entityManager = mock(IEntityManager.class);
when(entityManager.getEntityAttributePrototypes()).thenThrow(new RuntimeException());
when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);

Assertions.assertFalse(action.isNestedSearchableOptionSupported("Boolean"));
}

@Test
void shouldMethodNotAddAttributeElement() {

String entityManagerName = "EntityManagerName";
Mockito.when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
.thenReturn(entityManagerName);

String attributeTypeCode = "typeCode";
Map<String, AttributeInterface> attributeTypes = new HashMap<>();
attributeTypes.put(attributeTypeCode, new TextAttribute());
IEntityManager entityManager = Mockito.mock(IEntityManager.class);
Mockito.when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);
Mockito.when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
IEntityManager entityManager = mock(IEntityManager.class);
when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);
when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
action.setAttributeTypeCode("attributeTypeCodeNotExistent");

Mockito.when(textProvider.getText(any(), (String[]) any())).thenReturn("label");
when(textProvider.getText(any(), (String[]) any())).thenReturn("label");

String result = action.addAttributeElement();

Expand All @@ -125,12 +165,12 @@ void shouldMethodNotAddAttributeElement() {
void shouldMethodAddAttributeElementRaiseFailure() {

String entityManagerName = "EntityManagerName";
Mockito.when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
.thenReturn(entityManagerName);

IEntityManager entityManager = Mockito.mock(IEntityManager.class);
Mockito.when(entityManager.getEntityAttributePrototypes()).thenThrow(new RuntimeException());
Mockito.when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
IEntityManager entityManager = mock(IEntityManager.class);
when(entityManager.getEntityAttributePrototypes()).thenThrow(new RuntimeException());
when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
action.setAttributeTypeCode("");

String result = action.addAttributeElement();
Expand All @@ -142,15 +182,15 @@ void shouldMethodAddAttributeElementRaiseFailure() {
void shouldMethodAddAttributeElement() {

String entityManagerName = "EntityManagerName";
Mockito.when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
when(session.getAttribute(IEntityTypeConfigAction.ENTITY_TYPE_MANAGER_SESSION_PARAM))
.thenReturn(entityManagerName);

String attributeTypeCode = "typeCode";
Map<String, AttributeInterface> attributeTypes = new HashMap<>();
attributeTypes.put(attributeTypeCode, new TextAttribute());
IEntityManager entityManager = Mockito.mock(IEntityManager.class);
Mockito.when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);
Mockito.when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
IEntityManager entityManager = mock(IEntityManager.class);
when(entityManager.getEntityAttributePrototypes()).thenReturn(attributeTypes);
when(beanFactory.getBean(entityManagerName)).thenReturn(entityManager);
action.setAttributeTypeCode(attributeTypeCode);

String result = action.addAttributeElement();
Expand All @@ -163,31 +203,31 @@ void shouldMethodAddAttributeElement() {
@Test
void shouldMethodSaveCompositeAttributeSaveComposite() {

IApsEntity entity = Mockito.mock(IApsEntity.class);
Mockito.when(session.getAttribute(ENTITY_TYPE_ON_EDIT_SESSION_PARAM)).thenReturn(entity);
IApsEntity entity = mock(IApsEntity.class);
when(session.getAttribute(ENTITY_TYPE_ON_EDIT_SESSION_PARAM)).thenReturn(entity);

CompositeAttribute compositeAttribute = new CompositeAttribute();
compositeAttribute.setName(COMPOSITE_ATTRIBUTE_NAME);
Mockito.when(entity.getAttribute(COMPOSITE_ATTRIBUTE_NAME)).thenReturn(compositeAttribute);
when(entity.getAttribute(COMPOSITE_ATTRIBUTE_NAME)).thenReturn(compositeAttribute);

action.saveCompositeAttribute();

Mockito.verify(session, Mockito.times(1))
.setAttribute(Mockito.eq(ENTITY_TYPE_ON_EDIT_SESSION_PARAM), any());
verify(session, times(1))
.setAttribute(eq(ENTITY_TYPE_ON_EDIT_SESSION_PARAM), any());

Mockito.verify(session, Mockito.times(1))
verify(session, times(1))
.removeAttribute(COMPOSITE_ATTRIBUTE_ON_EDIT_SESSION_PARAM);
}

@Test
void shouldMethodSaveCompositeAttributeSaveMonolist() {
MonoListAttribute attribute = new MonoListAttribute();
IApsEntity entity = Mockito.mock(IApsEntity.class);
Mockito.when(entity.getAttribute(COMPOSITE_ATTRIBUTE_NAME)).thenReturn(attribute);
Mockito.when(session.getAttribute(ENTITY_TYPE_ON_EDIT_SESSION_PARAM)).thenReturn(entity);
IApsEntity entity = mock(IApsEntity.class);
when(entity.getAttribute(COMPOSITE_ATTRIBUTE_NAME)).thenReturn(attribute);
when(session.getAttribute(ENTITY_TYPE_ON_EDIT_SESSION_PARAM)).thenReturn(entity);
action.saveCompositeAttribute();
Mockito.verify(session, Mockito.times(1))
.setAttribute(Mockito.eq(ENTITY_TYPE_ON_EDIT_SESSION_PARAM), any());
verify(session, times(1))
.setAttribute(eq(ENTITY_TYPE_ON_EDIT_SESSION_PARAM), any());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public Map<String, SmallContentType> getSmallContentTypesMap() {
@Override
public String getViewPage(String contentId) {
Content type = this.getTypeById(contentId);
return type.getViewPage();
return (null != type) ? type.getViewPage() : null;
}

/**
Expand All @@ -157,7 +157,7 @@ public String getViewPage(String contentId) {
@Override
public String getDefaultModel(String contentId) {
Content type = this.getTypeById(contentId);
return type.getDefaultModel();
return (null != type) ? type.getDefaultModel() : null;
}

/**
Expand All @@ -170,7 +170,7 @@ public String getDefaultModel(String contentId) {
@Override
public String getListModel(String contentId) {
Content type = this.getTypeById(contentId);
return type.getListModel();
return (null != type) ? type.getListModel() : null;
}

/**
Expand Down
Loading
Loading