Compare commits

...

69 Commits

Author SHA1 Message Date
Robert von Burg 3054d5fca5
[Minor] Added ExceptionHelper.getRootCauseMessage() 2024-05-03 10:08:10 +02:00
Robert von Burg 19825b37a8
[Major] Refactored facet generation for reports
simplified code, move facet value generation into policy, added extension point for formatting criteria to Json, so the facet value's name can be changed in subclasses.
2024-05-02 12:43:21 +02:00
Robert von Burg c65c6624fb
[Fix] Ignoring more bundles in I18nMessage 2024-04-30 12:50:54 +02:00
Robert von Burg 4d7b3faf3f
[Fix] Fixed RestfulStrolchComponent not starting 2024-04-23 15:30:59 +02:00
Robert von Burg dc93800fd1
[Project] Set new version 2.3.0-SNAPSHOT 2024-04-23 14:46:57 +02:00
Robert von Burg 4b78b30600
[Minor] Updated restful logging 2024-04-23 14:29:03 +02:00
Robert von Burg 3edcec4d08
[Minor] Cleaned up logging in BasicAuth and ServletRequestHelper 2024-04-23 11:37:09 +02:00
Robert von Burg cf4ae20d0b
[Minor] Fixed HTTP return code for BasicAuth errors 2024-04-23 10:33:12 +02:00
Robert von Burg 68bd3dd6d6
[New] Added ServletRequestHelper.logRequest() 2024-04-23 10:32:52 +02:00
Robert von Burg c4ac21ec98
[New] Added ExceptionHelper.getRootCauseExceptionMessage() 2024-04-23 10:32:43 +02:00
Robert von Burg a45475f783
[Fix] BasicAuth should check root cause of exception 2024-04-23 09:47:31 +02:00
Robert von Burg baa7a0af4e
[New] Added enabled flag to notifications 2024-04-23 08:19:52 +02:00
Robert von Burg 1080096549
[New] Added LogRequestFilter to log requests prior to filter mapping 2024-04-22 15:01:05 +02:00
Robert von Burg 393659ff19
[Fix] Fixed Strolch exception i18n hierarchy 2024-03-25 10:18:43 +01:00
Robert von Burg a7258f0d06
[Major] Refactored basic auth and getRemoteIp() helper 2024-03-21 14:23:49 +01:00
Robert von Burg 2aca456729
[New] Added CertificateThreadLocal 2024-03-21 14:23:04 +01:00
Robert von Burg f4f9e2c798
[Project] Removed duplicate version declaration 2024-03-21 12:06:48 +01:00
Robert von Burg f051008d68
[Project] Updated dependencies to JAX-WS 2024-03-18 11:11:51 +01:00
Robert von Burg 1e6ac0c042
[Minor] Code cleanup 2024-03-14 16:06:56 +01:00
Robert von Burg a103864eb8
[Project] Updated to camel 3.22.1 2024-03-11 08:31:06 +01:00
Robert von Burg 125dfe9b03
[Fix] Notification only visible to all if forAll
otherwise role, etc. must be set and match
2024-03-07 12:26:51 +01:00
Robert von Burg 2914889172
[Minor] Code cleanup 2024-03-05 15:33:34 +01:00
Robert von Burg d3c261b750
[Fix] Fixed action name in NotificationResource 2024-03-05 15:33:02 +01:00
Robert von Burg de8d35480d
[Minor] Code cleanup 2024-03-05 13:31:15 +01:00
Robert von Burg 1158acfd90
[Fix] Use existing TX when writing audits for invalidating sessions 2024-03-05 13:30:46 +01:00
Robert von Burg faf05126b4
[Fix] Fixed CME in tx.streamCached*() methods 2024-03-05 11:12:12 +01:00
Robert von Burg d00b00d234
[Fix] Fixed updating sessions on changes of user or roles 2024-03-05 10:16:32 +01:00
Robert von Burg 76d38e9af0
[Minor] Made methods public 2024-03-04 17:21:55 +01:00
Robert von Burg f850367d2b
[Fix] Trying to fix broken test 2024-03-04 16:28:44 +01:00
Robert von Burg a4119ef1da
[New] Completed implementation of notifications 2024-03-04 16:10:38 +01:00
Robert von Burg abe089f95c
[Minor] Allow RoleAccessPrivilege to use String parameter 2024-03-04 16:10:14 +01:00
Robert von Burg 15b2788b9a
[WIP] Further implementing notifications 2024-03-01 16:22:11 +01:00
Robert von Burg f02b541848
[Minor] Sorting of supported languages 2024-03-01 16:21:52 +01:00
Robert von Burg 5526f20220
[New] Added StrolchAgent.getRuntimeConfiguration() 2024-03-01 09:58:19 +01:00
Robert von Burg a9c393f02a
[Fix] Implemented to JSON for notifications 2024-02-29 17:05:45 +01:00
Robert von Burg 22359e51d8
[New] Added CRUD services for notifications 2024-02-27 16:44:53 +01:00
Robert von Burg 7b9f2f867f
[New] Extended builder pattern 2024-02-27 14:46:41 +01:00
Robert von Burg a9067bf161
[Major] Refactored SimpleExecution.runWith* actions to take a Consumer<Throwable> in the event of an exception 2024-02-27 12:01:42 +01:00
dependabot[bot] e91a96b549
Bump org.postgresql:postgresql from 42.5.1 to 42.7.2 (#31)
Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.5.1 to 42.7.2.
- [Release notes](https://github.com/pgjdbc/pgjdbc/releases)
- [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pgjdbc/pgjdbc/commits)

---
updated-dependencies:
- dependency-name: org.postgresql:postgresql
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 10:28:07 +01:00
Robert von Burg 5c2bcc1620
[New] Added NotificationResource 2024-02-15 17:24:35 +01:00
Robert von Burg f3adc63af2
[New] Added new FileHelper.getTempFile() option MILLIS_FIRST, code cleanup 2024-02-13 09:49:39 +01:00
Robert von Burg a07bd83249
[WIP] Implementing system notifications 2024-02-12 16:43:57 +01:00
Robert von Burg c46c60c8fe
[Project] Changed final name to include strolch- prefix 2024-02-12 12:48:38 +01:00
Robert von Burg a2c720ce48
[New] Allow override of methods in DefaultStrolchSessionHandler 2024-02-09 11:27:40 +01:00
Robert von Burg c66446391a
[Fix] Fixed NPE in toString() 2024-02-06 15:22:43 +01:00
Robert von Burg 69d1f77364
[Fix] Searches should search on items in cache as well
But then ignore the item from the ElementMap stream
2024-02-06 08:00:44 +01:00
Robert von Burg e33950b7a1
[New] Better cache management in TX
- Added items to cache when modified
- added streaming of cached elements
2024-02-06 07:59:28 +01:00
Robert von Burg 56098c96fa
[Minor] Changed the toString()-methods to include the Locator 2024-02-05 14:01:13 +01:00
Robert von Burg 63670a77b6
[Fix] Fixed ExecutionPolicy not initialized before use 2024-01-19 10:22:41 +01:00
Robert von Burg 9c15d0d1d2
[Fix] Don't set Action to WARNING if already Executed 2024-01-18 14:46:34 +01:00
Robert von Burg 77cd9264d0
[Minor] Code cleanup 2024-01-15 14:37:12 +01:00
Robert von Burg bf01876f7c
[Minor] Don't log exception for session validation 2024-01-11 16:24:31 +01:00
Robert von Burg 373235f5e2
[Fix] Renamed varargs methd ExpressionBuilder.isInArray 2024-01-11 14:21:41 +01:00
Robert von Burg 2eeda52f86
[New] ObjectHelper.isIn() extended to allow left and right side to be an array/collection 2024-01-11 13:35:32 +01:00
Robert von Burg ce22f180af
[New] Added new SearchExpressions.paramOnBagType()
This allows to search to have a search, where multiple bags of the same type having a parameter with a given ID, yet different values is searched using the .isIn().

For example:

    paramOnBagType("Owner", "name").isIn("Felix", "Jill")

This allows for one less where clause with lambdas.
2024-01-11 12:33:51 +01:00
Robert von Burg 8b20e4392d
[New] ObjectHelper.isIn() extended to allow left and right side to be an array/collection 2024-01-11 12:32:03 +01:00
Robert von Burg 83f575de41
[New] Added new SearchExpression extract(Function<T, Object>) 2024-01-08 15:40:50 +01:00
Robert von Burg 23054a5bad
[Project] Updated maven-deploy-plugin version to 3.1.1 2024-01-04 15:54:41 +01:00
Robert von Burg d0008f9951
[Project] Removed qodana badge 2024-01-04 11:22:07 +01:00
Robert von Burg 1e2965d588
[Project] Added deployment to repo.strolch.li 2024-01-04 11:09:11 +01:00
Robert von Burg 7a8edc7cc6
[Project] Updated SECURITY.md 2024-01-04 09:40:33 +01:00
Robert von Burg 22e2e5cc68
[Project] Added deployment to repo.strolch.li 2024-01-02 20:32:43 +01:00
Robert von Burg f6a8746069
[Project] Added deployment to repo.strolch.li 2024-01-02 20:28:33 +01:00
Robert von Burg fd5f344764
[Project] Added deployment to repo.strolch.li 2024-01-02 14:47:13 +01:00
Robert von Burg ec8965eee2
[Project] Added deployment to repo.strolch.li 2024-01-01 13:37:16 +01:00
Robert von Burg c17b23b72f
[Fix] Fixed LoggingLoader.reloadLogging() 2023-12-17 20:46:47 +01:00
Robert von Burg 5064249e72
[Project] Updated logback to 1.4.14 2023-12-13 21:08:55 +01:00
Robert von Burg 269bd83ded
[New] search with contains() extended for matchAny or matchAll (default) 2023-12-11 15:00:28 +01:00
Robert von Burg 529c1d0e3b
[Minor] Fixed NPE 2023-12-08 09:31:22 +01:00
96 changed files with 2618 additions and 1145 deletions

View File

@ -7,7 +7,6 @@
[![Stars](https://img.shields.io/github/stars/strolch-li/strolch?style=flat-square "Stars")](https://github.com/strolch-li/strolch/stargazers)
[![Issues](https://img.shields.io/github/issues/strolch-li/strolch?style=flat-square "Issues")](https://github.com/strolch-li/strolch/issues)
[![Build Status](https://ci.atexxi.ch/buildStatus/icon?job=strolch)](https://ci.atexxi.ch/job/strolch/)
[![Qodana](https://github.com/strolch-li/strolch/actions/workflows/code_quality.yml/badge.svg)](https://github.com/strolch-li/strolch/actions/workflows/code_quality.yml)
[![Project Map](https://sourcespy.com/shield.svg)](https://sourcespy.com/github/strolchlistrolch/)
The main repository which contains all of Strolch.

View File

@ -4,15 +4,14 @@
The current branches are supported with security fixes:
| Version | Supported |
| ------- | ------------------ |
| 2.0.x | :white_check_mark: |
| 1.8.x | :white_check_mark: |
| 1.7.x | :white_check_mark: |
| <= 1.6.x | :x: |
| Version | Supported |
|----------|--------------------|
| 2.2.x | :white_check_mark: |
| 2.1.x | :white_check_mark: |
| <= 2.0.x | :x: |
## Reporting a Vulnerability
If you find a security vulnerability, then please send an email to eitch@eitchnet.ch. You might encrypt it using KeyOxide with my public GPG key: https://kxd.eitchnet.ch/b1359c320a72a2907f1a7f7875db9c85c74331f7
If you find a security vulnerability, then please email eitch@eitchnet.ch. You might encrypt it using KeyOxide with my public GPG key: https://kxd.eitchnet.ch/b1359c320a72a2907f1a7f7875db9c85c74331f7
We will then consider the vulnerability and shall try our best to close the vulnerability as soon as possible if it is part of an active branch and a fix is possible.

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -2,12 +2,12 @@ package li.strolch.agent.api;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.classic.util.DefaultJoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.MalformedURLException;
public class LoggingLoader {
@ -25,22 +25,25 @@ public class LoggingLoader {
"Not changing logback configuration as " + logConfigFile.getAbsolutePath() + " does not exist.");
} else {
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext loggerContext)) {
logger.error(logConfigFile.getAbsolutePath() +
" exists, but LoggerFactory is not instance of ch.qos.logback.classic.LoggerContext. Ignoring.");
logger.error(logConfigFile.getAbsolutePath()
+ " exists, but LoggerFactory is not instance of ch.qos.logback.classic.LoggerContext. Ignoring.");
} else {
logger.info(logConfigFile.getAbsolutePath() + " file exists. Reloading logging configuration from " +
logConfigFile);
logger.info(logConfigFile.getAbsolutePath()
+ " file exists. Reloading logging configuration from "
+ logConfigFile);
try {
loggerContext.reset();
new ContextInitializer(loggerContext).configureByResource(logConfigFile.toURI().toURL());
DefaultJoranConfigurator configurator = new DefaultJoranConfigurator();
configurator.setContext(loggerContext);
configurator.configureByResource(logConfigFile.toURI().toURL());
logger.info("Reloaded logger configuration from " + logConfigFile.getAbsolutePath());
lastConfigFile = logConfigFile;
} catch (MalformedURLException | JoranException e) {
} catch (Exception e) {
try {
new ContextInitializer(loggerContext).autoConfig();
} catch (JoranException e1) {
logger.error("Failed to reload original config after failure to load new config from " +
logConfigFile.getAbsolutePath(), e);
logger.error("Failed to reload original config after failure to load new config from "
+ logConfigFile.getAbsolutePath(), e);
}
logger.error("Failed to reload logback configuration from file " + logConfigFile, e);
}
@ -55,10 +58,12 @@ public class LoggingLoader {
reloadLogging(lastConfigFile.getParentFile());
} else {
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext loggerContext)) {
logger.error("LoggerFactory is not instance of " + LoggerContext.class.getName() +
". Ignoring request to reload configuration!");
System.out.println("LoggerFactory is not instance of " + LoggerContext.class.getName() +
". Ignoring request to reload configuration!");
logger.error("LoggerFactory is not instance of "
+ LoggerContext.class.getName()
+ ". Ignoring request to reload configuration!");
System.out.println("LoggerFactory is not instance of "
+ LoggerContext.class.getName()
+ ". Ignoring request to reload configuration!");
} else {
logger.info(
"Resetting logging configuration using auto config as no previous config fila available...");
@ -69,6 +74,7 @@ public class LoggingLoader {
} catch (JoranException e) {
logger.error("Failed to do logging auto configuration", e);
System.out.println("Failed to do logging auto configuration");
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
}

View File

@ -81,20 +81,14 @@ public class StrolchAgent {
this.appVersion = appVersion;
}
/**
* Return the {@link StrolchConfiguration}
*
* @return the {@link StrolchConfiguration}
*/
public StrolchConfiguration getStrolchConfiguration() {
return this.strolchConfiguration;
}
/**
* Return the container
*
* @return the container
*/
public RuntimeConfiguration getRuntimeConfiguration() {
return this.strolchConfiguration.getRuntimeConfiguration();
}
public ComponentContainer getContainer() {
return this.container;
}
@ -411,8 +405,9 @@ public class StrolchAgent {
public JsonObject getSystemState(long updateInterval, TimeUnit updateIntervalUnit) {
if (this.systemState == null ||
System.currentTimeMillis() - this.systemStateUpdateTime > updateIntervalUnit.toMillis(updateInterval)) {
if (this.systemState == null
|| System.currentTimeMillis() - this.systemStateUpdateTime > updateIntervalUnit.toMillis(
updateInterval)) {
this.systemState = new JsonObject();
JsonObject osJ = new JsonObject();
@ -474,8 +469,8 @@ public class StrolchAgent {
File configPathF = runtimeConfig.getConfigPath();
File dataPathF = runtimeConfig.getDataPath();
File tempPathF = runtimeConfig.getTempPath();
StrolchConfiguration newConfig = parseConfiguration(runtimeConfig.getEnvironment(), configPathF,
dataPathF, tempPathF);
StrolchConfiguration newConfig = parseConfiguration(runtimeConfig.getEnvironment(), configPathF, dataPathF,
tempPathF);
for (String name : this.container.getComponentNames()) {
ComponentConfiguration newComponentConfig = newConfig.getComponentConfiguration(name);

View File

@ -31,6 +31,8 @@ import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.utils.dbc.DBC;
import static li.strolch.runtime.StrolchConstants.*;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -70,9 +72,9 @@ public class DefaultRealmHandler extends StrolchComponent implements RealmHandle
@Override
public void setup(ComponentConfiguration configuration) {
this.realms = new HashMap<>(1);
String[] realms = configuration.getStringArray(PROP_REALMS, StrolchConstants.DEFAULT_REALM);
String[] realms = configuration.getStringArray(PROP_REALMS, DEFAULT_REALM);
for (String realmName : realms) {
String dataStoreModeKey = StrolchConstants.makeRealmKey(realmName, PREFIX_DATA_STORE_MODE);
String dataStoreModeKey = makeRealmKey(realmName, PREFIX_DATA_STORE_MODE);
String realmMode = configuration.getString(dataStoreModeKey, null);
InternalStrolchRealm realm = buildRealm(realmName, realmMode);
this.realms.put(realmName, realm);

View File

@ -30,6 +30,8 @@ import li.strolch.runtime.configuration.StrolchConfigurationException;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
import static li.strolch.agent.impl.DefaultRealmHandler.*;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -87,7 +89,7 @@ public class TransientRealm extends InternalStrolchRealm {
public void initialize(ComponentContainer container, ComponentConfiguration configuration) {
super.initialize(container, configuration);
String key = StrolchConstants.makeRealmKey(getRealm(), DefaultRealmHandler.PREFIX_DATA_STORE_FILE);
String key = StrolchConstants.makeRealmKey(getRealm(), PREFIX_DATA_STORE_FILE);
if (!configuration.hasProperty(key)) {
String msg = "There is no data store file for realm {0}. Set a property with key {1}";
msg = MessageFormat.format(msg, getRealm(), key);

View File

@ -38,11 +38,11 @@ import li.strolch.utils.objectfilter.ObjectFilterStatistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import static java.text.MessageFormat.format;
import static li.strolch.agent.api.StrolchAgent.getUniqueId;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.model.Tags.*;
@ -515,7 +515,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (locator.getSize() < 3) {
String msg
= "The locator is invalid as it does not have at least three path elements (e.g. Resource/MyType/@id): {0}";
msg = MessageFormat.format(msg, locator.toString());
msg = format(msg, locator.toString());
throw new StrolchModelException(msg);
}
@ -535,7 +535,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (allowNull)
return null;
String msg = "No top level object could be found with locator {0}";
throw new StrolchModelException(MessageFormat.format(msg, locator));
throw new StrolchModelException(format(msg, locator));
}
if (elements.size() == 3)
@ -551,7 +551,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (allowNull)
return null;
String msg = "Could not find ParameterBag for locator {0} on element {1}";
throw new StrolchModelException(MessageFormat.format(msg, locator, rootElement.getLocator()));
throw new StrolchModelException(format(msg, locator, rootElement.getLocator()));
}
if (elements.size() == 5)
@ -563,7 +563,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (allowNull)
return null;
String msg = "Could not find Parameter for locator {0} on element {1}";
throw new StrolchModelException(MessageFormat.format(msg, locator, bag.getLocator()));
throw new StrolchModelException(format(msg, locator, bag.getLocator()));
}
return (T) parameter;
@ -571,7 +571,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (elements.size() != 5) {
String msg = "Missing state Id on locator {0}";
throw new StrolchModelException(MessageFormat.format(msg, locator));
throw new StrolchModelException(format(msg, locator));
}
Resource resource = rootElement.asResource();
@ -589,7 +589,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (!(element instanceof Activity)) {
String msg = "Invalid locator {0} with part {1} as not an Activity but deeper element specified";
throw new StrolchModelException(MessageFormat.format(msg, locator, next));
throw new StrolchModelException(format(msg, locator, next));
}
element = ((Activity) element).getElement(next);
@ -602,7 +602,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
return null;
String msg = "Invalid locator {0} with part {1}";
throw new StrolchModelException(MessageFormat.format(msg, locator, stateOrBagOrActivity));
throw new StrolchModelException(format(msg, locator, stateOrBagOrActivity));
}
@Override
@ -641,8 +641,8 @@ public abstract class AbstractTransaction implements StrolchTransaction {
break;
if (!parents.add(parent))
throw new IllegalStateException(
"circular dependencies from " + element.getLocator() + " to " + parent.getLocator() +
" on relations parameter " + parentParamKey);
format("circular dependencies from {0} to {1} on relations parameter {2}", element.getLocator(),
parent.getLocator(), parentParamKey));
t = parent.getParameter(bagKey, paramKey);
}
@ -787,7 +787,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (assertExists && refP.isEmpty()) {
String msg = "The Order with type \"{0}\" and id \"{1}\" does not exist for param \"{2}\"";
throw new StrolchException(MessageFormat.format(msg, refP.getUom(), refP.getValue(), refP.getLocator()));
throw new StrolchException(format(msg, refP.getUom(), refP.getValue(), refP.getLocator()));
}
if (refP.isEmpty())
@ -901,7 +901,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (assertExists && refP.isEmpty()) {
String msg = "The Resource with type \"{0}\" and id \"{1}\" does not exist for param \"{2}\"";
throw new StrolchException(MessageFormat.format(msg, refP.getUom(), refP.getValue(), refP.getLocator()));
throw new StrolchException(format(msg, refP.getUom(), refP.getValue(), refP.getLocator()));
}
if (refP.isEmpty())
@ -1031,7 +1031,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (assertExists && refP.isEmpty()) {
String msg = "The Activity with type \"{0}\" and id \"{1}\" does not exist for param \"{2}\"";
throw new StrolchException(MessageFormat.format(msg, refP.getUom(), refP.getValue(), refP.getLocator()));
throw new StrolchException(format(msg, refP.getUom(), refP.getValue(), refP.getLocator()));
}
if (refP.isEmpty())
@ -1134,6 +1134,21 @@ public abstract class AbstractTransaction implements StrolchTransaction {
this.objectFilter.removeObjectCache(locator.get(0), locator);
}
@Override
public boolean isResourceCached(String type, String id) {
return this.resourceCache.containsElement(type, id);
}
@Override
public boolean isOrderCached(String type, String id) {
return this.orderCache.containsElement(type, id);
}
@Override
public boolean isActivityCached(String type, String id) {
return this.activityCache.containsElement(type, id);
}
@Override
public Resource getCachedResource(String type, String id) {
return this.resourceCache.getElement(type, id);
@ -1149,6 +1164,66 @@ public abstract class AbstractTransaction implements StrolchTransaction {
return this.activityCache.getElement(type, id);
}
@Override
public Stream<Resource> streamCachedResources(String... types) {
if (types.length == 0)
return this.resourceCache.values().stream();
if (types.length == 1) {
String type = types[0];
if (!this.resourceCache.containsMap(type))
return Stream.empty();
return new ArrayList<>(this.resourceCache.getMap(type).values()).stream();
}
return this.resourceCache.values().stream().filter(element -> {
String resType = element.getType();
for (String type : types) {
if (resType.equals(type))
return true;
}
return false;
});
}
@Override
public Stream<Order> streamCachedOrders(String... types) {
if (types.length == 0)
return this.orderCache.values().stream();
if (types.length == 1) {
String type = types[0];
if (!this.orderCache.containsMap(type))
return Stream.empty();
return new ArrayList<>(this.orderCache.getMap(type).values()).stream();
}
return this.orderCache.values().stream().filter(element -> {
String resType = element.getType();
for (String type : types) {
if (resType.equals(type))
return true;
}
return false;
});
}
@Override
public Stream<Activity> streamCachedActivities(String... types) {
if (types.length == 0)
return this.activityCache.values().stream();
if (types.length == 1) {
String type = types[0];
if (!this.activityCache.containsMap(type))
return Stream.empty();
return new ArrayList<>(this.activityCache.getMap(type).values()).stream();
}
return this.activityCache.values().stream().filter(element -> {
String resType = element.getType();
for (String type : types) {
if (resType.equals(type))
return true;
}
return false;
});
}
@Override
public boolean hasResource(String type, String id) {
boolean inFilter = hasElementInFilter(Tags.RESOURCE, Resource.locatorFor(type, id));
@ -1253,6 +1328,8 @@ public abstract class AbstractTransaction implements StrolchTransaction {
lock(resource);
this.objectFilter.add(Tags.RESOURCE, resource.getLocator(), resource);
}
this.resourceCache.addElement(resource.getType(), resource.getId(), resource);
}
@Override
@ -1271,6 +1348,8 @@ public abstract class AbstractTransaction implements StrolchTransaction {
lock(order);
this.objectFilter.add(Tags.ORDER, order.getLocator(), order);
}
this.orderCache.addElement(order.getType(), order.getId(), order);
}
@Override
@ -1289,6 +1368,8 @@ public abstract class AbstractTransaction implements StrolchTransaction {
lock(activity);
this.objectFilter.add(Tags.ACTIVITY, activity.getLocator(), activity);
}
this.activityCache.addElement(activity.getType(), activity.getId(), activity);
}
@Override
@ -1298,6 +1379,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
resource.assertNotReadonly();
lock(resource);
this.objectFilter.add(Tags.RESOURCE, resource.getLocator(), resource);
this.resourceCache.addElement(resource.getType(), resource.getId(), resource);
}
@Override
@ -1307,6 +1389,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
order.assertNotReadonly();
lock(order);
this.objectFilter.add(Tags.ORDER, order.getLocator(), order);
this.orderCache.addElement(order.getType(), order.getId(), order);
}
@Override
@ -1316,6 +1399,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
activity.assertNotReadonly();
lock(activity);
this.objectFilter.add(Tags.ACTIVITY, activity.getLocator(), activity);
this.activityCache.addElement(activity.getType(), activity.getId(), activity);
}
@Override
@ -1324,6 +1408,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
DBC.PRE.assertNotNull("resource must not be null", resource);
resource.assertNotReadonly();
this.objectFilter.update(Tags.RESOURCE, resource.getLocator(), resource);
this.resourceCache.addElement(resource.getType(), resource.getId(), resource);
}
@Override
@ -1332,6 +1417,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
DBC.PRE.assertNotNull("order must not be null", order);
order.assertNotReadonly();
this.objectFilter.update(Tags.ORDER, order.getLocator(), order);
this.orderCache.addElement(order.getType(), order.getId(), order);
}
@Override
@ -1340,6 +1426,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
DBC.PRE.assertNotNull("activity must not be null", activity);
activity.assertNotReadonly();
this.objectFilter.update(Tags.ACTIVITY, activity.getLocator(), activity);
this.activityCache.addElement(activity.getType(), activity.getId(), activity);
}
@Override
@ -1383,7 +1470,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
List<Resource> changedR = this.objectFilter.getRemoved(Resource.class, Tags.RESOURCE);
if (changedR.size() == 1) {
RemoveResourceCommand cmd = new RemoveResourceCommand(this);
cmd.setResource(changedR.get(0));
cmd.setResource(changedR.getFirst());
add(cmd);
} else if (changedR.size() > 1) {
RemoveResourcesCommand cmd = new RemoveResourcesCommand(this);
@ -1395,7 +1482,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
changedR = this.objectFilter.getUpdated(Resource.class, Tags.RESOURCE);
if (changedR.size() == 1) {
UpdateResourceCommand cmd = new UpdateResourceCommand(this);
cmd.setResource(changedR.get(0));
cmd.setResource(changedR.getFirst());
add(cmd);
} else if (changedR.size() > 1) {
UpdateResourcesCommand cmd = new UpdateResourcesCommand(this);
@ -1407,7 +1494,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
changedR = this.objectFilter.getAdded(Resource.class, Tags.RESOURCE);
if (changedR.size() == 1) {
AddResourceCommand cmd = new AddResourceCommand(this);
cmd.setResource(changedR.get(0));
cmd.setResource(changedR.getFirst());
add(cmd);
} else if (changedR.size() > 1) {
AddResourcesCommand cmd = new AddResourcesCommand(this);
@ -1422,7 +1509,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
List<Order> changedO = this.objectFilter.getRemoved(Order.class, Tags.ORDER);
if (changedO.size() == 1) {
RemoveOrderCommand cmd = new RemoveOrderCommand(this);
cmd.setOrder(changedO.get(0));
cmd.setOrder(changedO.getFirst());
add(cmd);
} else if (changedO.size() > 1) {
RemoveOrdersCommand cmd = new RemoveOrdersCommand(this);
@ -1434,7 +1521,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
changedO = this.objectFilter.getUpdated(Order.class, Tags.ORDER);
if (changedO.size() == 1) {
UpdateOrderCommand cmd = new UpdateOrderCommand(this);
cmd.setOrder(changedO.get(0));
cmd.setOrder(changedO.getFirst());
add(cmd);
} else if (changedO.size() > 1) {
UpdateOrdersCommand cmd = new UpdateOrdersCommand(this);
@ -1446,7 +1533,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
changedO = this.objectFilter.getAdded(Order.class, Tags.ORDER);
if (changedO.size() == 1) {
AddOrderCommand cmd = new AddOrderCommand(this);
cmd.setOrder(changedO.get(0));
cmd.setOrder(changedO.getFirst());
add(cmd);
} else if (changedO.size() > 1) {
AddOrdersCommand cmd = new AddOrdersCommand(this);
@ -1461,7 +1548,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
List<Activity> changedA = this.objectFilter.getRemoved(Activity.class, Tags.ACTIVITY);
if (changedA.size() == 1) {
RemoveActivityCommand cmd = new RemoveActivityCommand(this);
cmd.setActivity(changedA.get(0));
cmd.setActivity(changedA.getFirst());
add(cmd);
} else if (changedA.size() > 1) {
RemoveActivitiesCommand cmd = new RemoveActivitiesCommand(this);
@ -1473,7 +1560,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
changedA = this.objectFilter.getUpdated(Activity.class, Tags.ACTIVITY);
if (changedA.size() == 1) {
UpdateActivityCommand cmd = new UpdateActivityCommand(this);
cmd.setActivity(changedA.get(0));
cmd.setActivity(changedA.getFirst());
add(cmd);
} else if (changedA.size() > 1) {
UpdateActivitiesCommand cmd = new UpdateActivitiesCommand(this);
@ -1485,7 +1572,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
changedA = this.objectFilter.getAdded(Activity.class, Tags.ACTIVITY);
if (changedA.size() == 1) {
AddActivityCommand cmd = new AddActivityCommand(this);
cmd.setActivity(changedA.get(0));
cmd.setActivity(changedA.getFirst());
add(cmd);
} else if (changedA.size() > 1) {
AddActivitiesCommand cmd = new AddActivitiesCommand(this);
@ -1531,7 +1618,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
this.closeStrategy = TransactionCloseStrategy.ROLLBACK;
String msg = "Strolch Transaction for realm {0} failed due to {1}";
msg = MessageFormat.format(msg, getRealmName(), getExceptionMessage(e));
msg = format(msg, getRealmName(), getExceptionMessage(e));
throw new StrolchTransactionException(msg, e);
}
}
@ -1595,7 +1682,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
@Override
public void autoCloseableRollback() {
long start = System.nanoTime();
logger.warn(MessageFormat.format("Rolling back TX for realm {0}...", getRealmName()));
logger.warn(format("Rolling back TX for realm {0}...", getRealmName()));
try {
this.txResult.setState(TransactionState.ROLLING_BACK);
undoCommands();
@ -1808,12 +1895,13 @@ public abstract class AbstractTransaction implements StrolchTransaction {
OperationsLog operationsLog = container.getComponent(OperationsLog.class);
operationsLog.addMessage(new LogMessage(this.realm.getRealm(), this.certificate.getUsername(),
Locator.valueOf(AGENT, "tx", this.action, getUniqueId()), LogSeverity.Exception,
LogMessageState.Information, ResourceBundle.getBundle("strolch-agent"),
"agent.tx.failed").withException(e).value("reason", e));
LogMessageState.Information, ResourceBundle.getBundle("strolch-agent"), "agent.tx.failed")
.withException(e)
.value("reason", e));
}
String msg = "Strolch Transaction for realm {0} failed due to {1}\n{2}";
msg = MessageFormat.format(msg, getRealmName(), getExceptionMessage(e), sb.toString());
msg = format(msg, getRealmName(), getExceptionMessage(e), sb.toString());
StrolchTransactionException ex = new StrolchTransactionException(msg, e);
if (throwEx)
@ -1922,10 +2010,10 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (this.auditTrail != null && !isSuppressAuditsForAudits()) {
if (this.realm.isAuditTrailEnabledForRead())
auditsForAudits(audits, AccessType.READ, Tags.AUDIT, this.auditTrail.getRead());
auditsForAudits(audits, AccessType.CREATE, Tags.AUDIT, this.auditTrail.getCreated());
auditsForAudits(audits, AccessType.UPDATE, Tags.AUDIT, this.auditTrail.getUpdated());
auditsForAudits(audits, AccessType.DELETE, Tags.AUDIT, this.auditTrail.getDeleted());
auditsForAudits(audits, AccessType.READ, this.auditTrail.getRead());
auditsForAudits(audits, AccessType.CREATE, this.auditTrail.getCreated());
auditsForAudits(audits, AccessType.UPDATE, this.auditTrail.getUpdated());
auditsForAudits(audits, AccessType.DELETE, this.auditTrail.getDeleted());
}
if (!audits.isEmpty())
@ -1940,9 +2028,9 @@ public abstract class AbstractTransaction implements StrolchTransaction {
}
}
private void auditsForAudits(List<Audit> audits, AccessType accessType, String elementType, Set<Audit> elements) {
private void auditsForAudits(List<Audit> audits, AccessType accessType, Set<Audit> elements) {
for (Audit element : elements) {
audits.add(auditFrom(accessType, elementType, StringHelper.DASH, element.getId().toString()));
audits.add(auditFrom(accessType, Tags.AUDIT, StringHelper.DASH, element.getId().toString()));
}
}

View File

@ -1571,6 +1571,21 @@ public interface StrolchTransaction extends AutoCloseable {
*/
void removeFromCache(Locator locator);
/**
* Returns true if the given resource is currently cached
*/
boolean isResourceCached(String type, String id);
/**
* Returns true if the given order is currently cached
*/
boolean isOrderCached(String type, String id);
/**
* Returns true if the given activity is currently cached
*/
boolean isActivityCached(String type, String id);
/**
* Returns the cached resource with the given type and id, or null if not yet fetched
*
@ -1607,6 +1622,21 @@ public interface StrolchTransaction extends AutoCloseable {
*/
Activity getCachedActivity(String type, String id);
/**
* Returns a stream of resources in the cache
*/
Stream<Resource> streamCachedResources(String... types);
/**
* Returns a stream of orders in the cache
*/
Stream<Order> streamCachedOrders(String... types);
/**
* Returns a stream of activities in the cache
*/
Stream<Activity> streamCachedActivities(String... types);
/**
* Returns true if the @{@link Resource} exists with the given type and ID
*

View File

@ -61,7 +61,8 @@ public class InMemoryStrolchDao<T extends StrolchRootElement> implements Strolch
public List<T> queryAll(String... types) throws StrolchPersistenceException {
List<T> values = new ArrayList<>();
for (String type : types) {
values.addAll(this.elements.getList(type));
if (this.elements.containsList(type))
values.addAll(this.elements.getList(type));
}
return values;
}

View File

@ -0,0 +1,59 @@
package li.strolch.policy.notifications;
import li.strolch.model.Resource;
import li.strolch.model.StrolchModelConstants;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.utils.collections.DateRange;
import java.time.ZonedDateTime;
import java.util.List;
import static li.strolch.model.StrolchModelConstants.*;
public class DefaultNotificationsPolicy extends NotificationsPolicy {
public DefaultNotificationsPolicy(StrolchTransaction tx) {
super(tx);
}
@Override
public List<Resource> findUserNotifications() {
return tx().streamResources(StrolchModelConstants.TYPE_NOTIFICATION).filter(this::isForUser).toList();
}
@Override
public boolean canView(Resource notification) {
return isForAll(notification) || isForRole(notification) || isForGroup(notification);
}
protected boolean isForUser(Resource notification) {
if (!isActive(notification))
return false;
return isEnabled(notification) && (
isForAll(notification) || isForRole(notification) || isForGroup(notification));
}
protected boolean isActive(Resource notification) {
return new DateRange()
.from(notification.getDate(BAG_VISIBILITY, PARAM_VISIBLE_FROM), true)
.to(notification.getDate(BAG_VISIBILITY, PARAM_VISIBLE_TO), true)
.contains(ZonedDateTime.now());
}
protected boolean isEnabled(Resource notification) {
return notification.getBoolean(BAG_VISIBILITY, PARAM_ENABLED);
}
protected boolean isForAll(Resource notification) {
return notification.getBoolean(BAG_VISIBILITY, PARAM_FOR_ALL);
}
protected boolean isForRole(Resource notification) {
List<String> roles = notification.getStringList(BAG_VISIBILITY, PARAM_ROLES);
return roles.stream().anyMatch(r -> tx().getCertificate().hasRole(r));
}
protected boolean isForGroup(Resource notification) {
List<String> groups = notification.getStringList(BAG_VISIBILITY, PARAM_GROUPS);
return groups.stream().anyMatch(r -> tx().getCertificate().hasGroup(r));
}
}

View File

@ -0,0 +1,28 @@
package li.strolch.policy.notifications;
import li.strolch.model.Resource;
import li.strolch.model.policy.PolicyDef;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.policy.StrolchPolicy;
import java.util.List;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.POLICY_DEFAULT;
import static li.strolch.model.policy.PolicyDef.getJavaPolicy;
import static li.strolch.model.policy.PolicyDef.getKeyPolicy;
public abstract class NotificationsPolicy extends StrolchPolicy {
public NotificationsPolicy(StrolchTransaction tx) {
super(tx);
}
public abstract List<Resource> findUserNotifications();
public abstract boolean canView(Resource notification);
public static NotificationsPolicy getDefaultPolicy(StrolchTransaction tx) {
PolicyDef defaultDef = getKeyPolicy(NotificationsPolicy.class, POLICY_DEFAULT);
PolicyDef fallbackDef = getJavaPolicy(NotificationsPolicy.class, DefaultNotificationsPolicy.class);
return tx.getPolicy(NotificationsPolicy.class, defaultDef, fallbackDef);
}
}

View File

@ -96,5 +96,8 @@ public class StrolchConstants extends StrolchModelConstants {
public static final String PRIVILEGE_UPDATE_PREFIX = "Update";
public static final String PRIVILEGE_REMOVE_PREFIX = "Remove";
public static final String PRIVILEGE_GET_PREFIX = "Get";
public static final String PRIVILEGE_GET_NOTIFICATIONS = "GetNotifications";
public static final String PRIVILEGE_GET_NOTIFICATION = "GetNotification";
public static final String PRIVILEGE_GET_NOTIFICATIONS_ALL = "GetNotificationsAll";
}
}

View File

@ -15,19 +15,6 @@
*/
package li.strolch.runtime.privilege;
import static java.lang.Boolean.parseBoolean;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS;
import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS_PATH;
import static li.strolch.privilege.helper.XmlConstants.PARAM_BASE_PATH;
import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.*;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.Map;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent;
import li.strolch.agent.api.StrolchRealm;
@ -48,6 +35,21 @@ import li.strolch.runtime.configuration.ComponentConfiguration;
import li.strolch.runtime.configuration.RuntimeConfiguration;
import li.strolch.utils.helper.XmlHelper;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.Map;
import static java.lang.Boolean.parseBoolean;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static li.strolch.persistence.api.TransactionThreadLocal.getTx;
import static li.strolch.persistence.api.TransactionThreadLocal.hasTx;
import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS;
import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS_PATH;
import static li.strolch.privilege.helper.XmlConstants.PARAM_BASE_PATH;
import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.*;
public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements PrivilegeHandler {
public static final String PROP_PRIVILEGE_CONFIG_FILE = "privilegeConfigFile";
@ -89,8 +91,7 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
/**
* Initializes the {@link DefaultPrivilegeHandler} from the configuration file
*
* @param privilegeXmlFile
* a {@link File} reference to the XML file containing the configuration for Privilege
* @param privilegeXmlFile a {@link File} reference to the XML file containing the configuration for Privilege
*
* @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and
* {@link PersistenceHandler} are set and initialized as well
@ -185,13 +186,17 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
private void writeAudit(Certificate certificate, String login, AccessType accessType, String username) {
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, login, false).silentThreshold(1, NANOSECONDS)) {
try (StrolchTransaction tx = hasTx() ? getTx() : openTx(certificate, login, realm)) {
tx.setSuppressAudits(true);
Audit audit = tx.auditFrom(accessType, PRIVILEGE, CERTIFICATE, username);
tx.getAuditTrail().add(tx, audit);
}
}
private static StrolchTransaction openTx(Certificate certificate, String login, StrolchRealm realm) {
return realm.openTx(certificate, login, false).silentThreshold(1, NANOSECONDS);
}
@Override
public PrivilegeContext validate(Certificate certificate) throws PrivilegeException {
return this.privilegeHandler.validate(certificate);

View File

@ -302,7 +302,7 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
return certificate;
}
private void checkSessionsForTimeout() {
protected void checkSessionsForTimeout() {
ZonedDateTime maxKeepAliveTime = ZonedDateTime.now().minusMinutes(this.maxKeepAliveMinutes);
ZonedDateTime timeOutTime = ZonedDateTime.now().minusMinutes(this.sessionTtlMinutes);
@ -326,7 +326,7 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
}
}
private void sessionTimeout(Certificate certificate) {
protected void sessionTimeout(Certificate certificate) {
DBC.PRE.assertNotNull("Certificate must be given!", certificate);
Certificate removedCert = this.certificateMap.remove(certificate.getAuthToken());

View File

@ -1,11 +1,13 @@
package li.strolch.search;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.PARAM_ORDER;
import li.strolch.model.Order;
import li.strolch.model.activity.Activity;
import li.strolch.persistence.api.StrolchTransaction;
import java.util.stream.Stream;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.PARAM_ORDER;
/**
* Performs a search for {@link Activity} elements
*/
@ -20,7 +22,13 @@ public class ActivitySearch extends StrolchSearch<Activity> {
@Override
public ActivitySearch types(String... types) {
this.navigator = tx -> tx.streamActivities(types);
this.navigator = tx -> {
Stream<Activity> cachedStream = tx.streamCachedActivities(types);
Stream<Activity> stream = tx
.streamActivities(types)
.filter(e -> !tx.isActivityCached(e.getType(), e.getId()));
return Stream.concat(cachedStream, stream);
};
return this;
}

View File

@ -63,10 +63,16 @@ public interface ExpressionBuilder {
default <T extends StrolchRootElement> SearchExpression<T> containsIgnoreCase(Object right) {
return element -> PredicatesSupport.containsIgnoreCase(right).matches(extract(element));
}
default <T extends StrolchRootElement> SearchExpression<T> containsIgnoreCaseMatchAny(Object right) {
return element -> PredicatesSupport.containsIgnoreCaseMatchAny(right).matches(extract(element));
}
default <T extends StrolchRootElement> SearchExpression<T> isIn(Object right) {
return element -> PredicatesSupport.isIn(right).matches(extract(element));
}
default <T extends StrolchRootElement> SearchExpression<T> isInArray(Object... right) {
return element -> PredicatesSupport.isIn(right).matches(extract(element));
}
default <T extends StrolchRootElement> SearchExpression<T> isInIgnoreCase(Object right) {
return element -> PredicatesSupport.isInIgnoreCase(right).matches(extract(element));

View File

@ -1,10 +1,5 @@
package li.strolch.search;
import static li.strolch.model.StrolchModelConstants.*;
import java.util.function.Function;
import java.util.function.Supplier;
import li.strolch.model.*;
import li.strolch.model.activity.Activity;
import li.strolch.model.parameter.Parameter;
@ -12,6 +7,12 @@ import li.strolch.model.parameter.StringParameter;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import static li.strolch.model.StrolchModelConstants.*;
/**
* Implements search expressions to be statically imported when writing searches
*/
@ -148,6 +149,26 @@ public class ExpressionsSupport {
};
}
public static <T extends StrolchRootElement> ExpressionBuilder paramOnBagType(String bagType, String paramId) {
return element -> {
List<Object> result = element
.streamOfParameterBagsByType(bagType)
.filter(b -> b.isParamSet(paramId))
.map(b -> b.getParameter(paramId, true).getValue())
.toList();
if (result.size() == 1)
return result.getFirst();
return result.toArray();
};
}
public static <T extends StrolchRootElement> ExpressionBuilder extract(Function<T, Object> extractor) {
return element -> {
@SuppressWarnings("unchecked") T e = (T) element;
return extractor.apply(e);
};
}
public static <T extends StrolchRootElement> SearchExpression<T> paramNull(String paramId) {
return paramNull(BAG_PARAMETERS, paramId);
}

View File

@ -6,6 +6,7 @@ import li.strolch.persistence.api.StrolchTransaction;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
@ -23,7 +24,11 @@ public class OrderSearch extends StrolchSearch<Order> {
@Override
public OrderSearch types(String... types) {
this.navigator = tx -> tx.streamOrders(types);
this.navigator = tx -> {
Stream<Order> cachedStream = tx.streamCachedOrders(types);
Stream<Order> stream = tx.streamOrders(types).filter(e -> !tx.isOrderCached(e.getType(), e.getId()));
return Stream.concat(cachedStream, stream);
};
return this;
}

View File

@ -47,7 +47,11 @@ public class PredicatesSupport {
}
public static SearchPredicate contains(Object right) {
return new ContainsPredicate(right, false);
return new ContainsPredicate(right, false, false);
}
public static SearchPredicate containsMatchAny(Object right) {
return new ContainsPredicate(right, false, true);
}
public static SearchPredicate collectionContains(Object right) {
@ -55,7 +59,11 @@ public class PredicatesSupport {
}
public static SearchPredicate containsIgnoreCase(Object right) {
return new ContainsPredicate(right, true);
return new ContainsPredicate(right, true, false);
}
public static SearchPredicate containsIgnoreCaseMatchAny(Object right) {
return new ContainsPredicate(right, true, true);
}
public static SearchPredicate isIn(Object right) {

View File

@ -3,6 +3,8 @@ package li.strolch.search;
import li.strolch.model.Resource;
import li.strolch.persistence.api.StrolchTransaction;
import java.util.stream.Stream;
/**
* Performs a search for {@link Resource} elements
*/
@ -17,7 +19,13 @@ public class ResourceSearch extends StrolchSearch<Resource> {
@Override
public ResourceSearch types(String... types) {
this.navigator = tx -> tx.streamResources(types);
this.navigator = tx -> {
Stream<Resource> cachedStream = tx.streamCachedResources(types);
Stream<Resource> stream = tx
.streamResources(types)
.filter(e -> !tx.isResourceCached(e.getType(), e.getId()));
return Stream.concat(cachedStream, stream);
};
return this;
}

View File

@ -1,17 +1,17 @@
package li.strolch.search;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.model.StrolchModelConstants.BAG_RELATIONS;
import java.util.function.Function;
import java.util.function.Supplier;
import li.strolch.model.Order;
import li.strolch.model.Resource;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.activity.Activity;
import li.strolch.persistence.api.StrolchTransaction;
import java.util.function.Function;
import java.util.function.Supplier;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.model.StrolchModelConstants.BAG_RELATIONS;
/**
* Declares specific search expressions, i.e. extracting the relevant data for a where clause
*/
@ -77,6 +77,10 @@ public interface SearchExpressions {
return ExpressionsSupport.param(BAG_PARAMETERS, paramId);
}
default <T extends StrolchRootElement> ExpressionBuilder extract(Function<T, Object> extractor) {
return ExpressionsSupport.extract(extractor);
}
default ExpressionBuilder relation(String paramId) {
return ExpressionsSupport.param(BAG_RELATIONS, paramId);
}
@ -106,6 +110,10 @@ public interface SearchExpressions {
return ExpressionsSupport.paramNull(bagId, paramId);
}
default ExpressionBuilder paramOnBagType(String bagType, String paramId) {
return ExpressionsSupport.paramOnBagType(bagType, paramId);
}
default ExpressionBuilder relationName(StrolchTransaction tx, String paramId) {
return ExpressionsSupport.relationName(tx, paramId);
}

View File

@ -3,18 +3,20 @@ package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
/**
* Implements the contains predicate, delegating to {@link ObjectHelper#contains(Object, Object, boolean)}
* Implements the contains predicate, delegating to {@link ObjectHelper#contains(Object, Object, boolean, boolean)}
*/
public class ContainsPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;
private final boolean matchAny;
public ContainsPredicate(Object right, boolean ignoreCase) {
public ContainsPredicate(Object right, boolean ignoreCase, boolean matchAny) {
super(right);
this.ignoreCase = ignoreCase;
this.matchAny = matchAny;
}
@Override
public boolean matches(Object left) {
return ObjectHelper.contains(left, this.right, this.ignoreCase);
return ObjectHelper.contains(left, this.right, this.ignoreCase, !this.matchAny);
}
}

View File

@ -24,6 +24,7 @@ import java.util.Locale;
import java.util.ResourceBundle;
import com.google.gson.JsonObject;
import li.strolch.exception.StrolchException;
import li.strolch.exception.StrolchUserMessageException;
import li.strolch.model.Tags;
import li.strolch.model.i18n.I18nMessageToJsonVisitor;
@ -229,10 +230,8 @@ public class ServiceResult {
json.addProperty(EXCEPTION_MSG, getExceptionMessageWithCauses(this.throwable, false));
json.addProperty(THROWABLE, formatException(this.throwable));
if (this.throwable instanceof StrolchUserMessageException
&& ((StrolchUserMessageException) this.throwable).hasI18n())
json.add(I_18_N, ((StrolchUserMessageException) this.throwable).getI18n()
.accept(new I18nMessageToJsonVisitor()));
if (this.throwable instanceof StrolchException ex && ex.hasI18n())
json.add(I_18_N, ex.getI18n().accept(new I18nMessageToJsonVisitor()));
}
if (!json.has(I_18_N) && this.i18nMessage != null)

View File

@ -66,6 +66,11 @@ public class StrolchSearchTest {
bag.addParameter(new StringParameter("status", "Status", "bla"));
bag.addParameter(new StringParameter("color", "Color", "yellow"));
ball.addParameterBag(new ParameterBag("owner1", "Owner", "Owner"));
ball.setString("owner1", "name", "Felix");
ball.addParameterBag(new ParameterBag("owner2", "Owner", "Owner"));
ball.setString("owner2", "name", "Fox");
tx.add(ball);
}
@ -82,6 +87,11 @@ public class StrolchSearchTest {
bag.addParameter(new StringListParameter("stateList", "Status",
asList(State.EXECUTION.name(), State.EXECUTED.name())));
ball.addParameterBag(new ParameterBag("owner1", "Owner", "Owner"));
ball.setString("owner1", "name", "Jill");
ball.addParameterBag(new ParameterBag("owner2", "Owner", "Owner"));
ball.setString("owner2", "name", "Jane");
tx.add(ball);
}
@ -139,8 +149,10 @@ public class StrolchSearchTest {
try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) {
List<JsonObject> result = new BallSearch("the-id", "STATUS", "yellow").where(
element -> element.hasTimedState(STATE_FLOAT_ID)).search(tx).map(a -> a.accept(toJsonVisitor))
List<JsonObject> result = new BallSearch("the-id", "STATUS", "yellow")
.where(element -> element.hasTimedState(STATE_FLOAT_ID))
.search(tx)
.map(a -> a.accept(toJsonVisitor))
.toList();
assertEquals(2, result.size());
@ -174,12 +186,18 @@ public class StrolchSearchTest {
StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert);
try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) {
assertEquals(4,
new ResourceSearch().types().where(param(BAG_ID, PARAM_STRING_ID, contains("rol"))).search(tx)
.toList().size());
assertEquals(4,
new ResourceSearch().types().where(param(BAG_ID, PARAM_STRING_ID, startsWithIgnoreCase("STR")))
.search(tx).toList().size());
assertEquals(4, new ResourceSearch()
.types()
.where(param(BAG_ID, PARAM_STRING_ID, contains("rol")))
.search(tx)
.toList()
.size());
assertEquals(4, new ResourceSearch()
.types()
.where(param(BAG_ID, PARAM_STRING_ID, startsWithIgnoreCase("STR")))
.search(tx)
.toList()
.size());
}
}
@ -230,11 +248,14 @@ public class StrolchSearchTest {
@Override
public void define() {
DateRange dateRange = new DateRange().from(ISO8601.parseToZdt("2012-01-01T00:00:00.000+01:00"),
true).to(ISO8601.parseToZdt("2013-01-01T00:00:00.000+01:00"), true);
DateRange dateRange = new DateRange()
.from(ISO8601.parseToZdt("2012-01-01T00:00:00.000+01:00"), true)
.to(ISO8601.parseToZdt("2013-01-01T00:00:00.000+01:00"), true);
types().where(date().isEqualTo(Instant.ofEpochMilli(1384929777699L).atZone(systemDefault()))
.or(state().isEqualTo(State.CREATED)
types().where(date()
.isEqualTo(Instant.ofEpochMilli(1384929777699L).atZone(systemDefault()))
.or(state()
.isEqualTo(State.CREATED)
.and(param(BAG_ID, PARAM_STRING_ID).isEqualTo("Strolch"))
.and(param(BAG_ID, PARAM_DATE_ID).inRange(dateRange))));
}
@ -286,18 +307,30 @@ public class StrolchSearchTest {
try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) {
ZonedDateTime dateTime = ISO8601.parseToZdt("2013-11-20T07:42:57.699+01:00");
assertEquals(0,
new OrderSearch().types("TestType").where(date().isBefore(dateTime, false)).search(tx).toList()
.size());
assertEquals(1,
new OrderSearch().types("TestType").where(date().isBefore(dateTime, true)).search(tx).toList()
.size());
assertEquals(0,
new OrderSearch().types("TestType").where(date().isAfter(dateTime, false)).search(tx).toList()
.size());
assertEquals(1,
new OrderSearch().types("TestType").where(date().isAfter(dateTime, true)).search(tx).toList()
.size());
assertEquals(0, new OrderSearch()
.types("TestType")
.where(date().isBefore(dateTime, false))
.search(tx)
.toList()
.size());
assertEquals(1, new OrderSearch()
.types("TestType")
.where(date().isBefore(dateTime, true))
.search(tx)
.toList()
.size());
assertEquals(0, new OrderSearch()
.types("TestType")
.where(date().isAfter(dateTime, false))
.search(tx)
.toList()
.size());
assertEquals(1, new OrderSearch()
.types("TestType")
.where(date().isAfter(dateTime, true))
.search(tx)
.toList()
.size());
}
}
@ -349,8 +382,12 @@ public class StrolchSearchTest {
StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert);
try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) {
assertEquals(1, new ActivitySearch().types("sdf", "ActivityType")
.where(element -> element.getActionsByType("Use").size() == 4).search(tx).toList().size());
assertEquals(1, new ActivitySearch()
.types("sdf", "ActivityType")
.where(element -> element.getActionsByType("Use").size() == 4)
.search(tx)
.toList()
.size());
}
}
@ -405,8 +442,8 @@ public class StrolchSearchTest {
public void define() {
types("Ball").where(id(isEqualTo(this.id)).or( //
param("parameters", "status", isEqualTo(this.status)).and(
not(param("parameters", "color", isEqualTo(this.color))))
param("parameters", "status", isEqualTo(this.status))
.and(not(param("parameters", "color", isEqualTo(this.color))))
.and(param("parameters", "state", isEqualTo(State.EXECUTION)))
.and(param("parameters", "state", isEqualToIgnoreCase(State.EXECUTION)))
@ -469,6 +506,8 @@ public class StrolchSearchTest {
.and(param(BAG_ID, PARAM_LIST_STRING_ID, listContains("World")))
.and(param(BAG_ID, PARAM_LIST_STRING_ID, listContains("World1")).not())
.and(paramOnBagType("Owner", "name").isInArray("Felix", "Jill"))
.and(paramNull(BAG_ID, "non-existant"))
//
));

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -26,14 +26,12 @@ public class StrolchAccessDeniedException extends StrolchException {
private final Certificate certificate;
private final Restrictable restrictable;
private final I18nMessage i18n;
public StrolchAccessDeniedException(Certificate certificate, Restrictable restrictable, I18nMessage i18n,
Throwable cause) {
super(i18n.getMessage(), cause);
super(i18n, cause);
this.certificate = certificate;
this.restrictable = restrictable;
this.i18n = i18n;
}
public Certificate getCertificate() {
@ -43,8 +41,4 @@ public class StrolchAccessDeniedException extends StrolchException {
public Restrictable getRestrictable() {
return restrictable;
}
public I18nMessage getI18n() {
return this.i18n;
}
}

View File

@ -15,16 +15,17 @@
*/
package li.strolch.exception;
import java.util.Locale;
import li.strolch.utils.I18nMessage;
import java.util.Locale;
import java.util.ResourceBundle;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class StrolchException extends RuntimeException {
private I18nMessage i18n;
protected I18nMessage i18n;
public StrolchException(String message, Throwable cause) {
super(message, cause);
@ -36,6 +37,41 @@ public class StrolchException extends RuntimeException {
public StrolchException(I18nMessage i18n) {
super(i18n.getMessage(Locale.getDefault()));
this.i18n = i18n;
if (i18n.hasException())
initCause(i18n.getException());
}
public StrolchException(I18nMessage i18n, Throwable cause) {
super(i18n.getMessage(Locale.getDefault()), cause);
this.i18n = i18n;
}
public StrolchException(ResourceBundle bundle, String key) {
this(new I18nMessage(bundle, key));
}
public StrolchException(ResourceBundle bundle, String key, String prop, Object value) {
this(new I18nMessage(bundle, key).value(prop, value));
}
public StrolchException(ResourceBundle bundle, String key, String prop1, Object value1, String prop2,
Object value2) {
this(new I18nMessage(bundle, key).value(prop1, value1).value(prop2, value2));
}
public StrolchException(ResourceBundle bundle, String key, String prop1, Object value1, String prop2, Object value2,
String prop3, Object value3) {
this(new I18nMessage(bundle, key).value(prop1, value1).value(prop2, value2).value(prop3, value3));
}
public StrolchException(ResourceBundle bundle, String key, String prop1, Object value1, String prop2, Object value2,
String prop3, Object value3, String prop4, Object value4) {
this(new I18nMessage(bundle, key)
.value(prop1, value1)
.value(prop2, value2)
.value(prop3, value3)
.value(prop4, value4));
}
public boolean hasI18n() {
@ -54,4 +90,9 @@ public class StrolchException extends RuntimeException {
this.i18n = i18n;
return this;
}
public StrolchException cause(Throwable cause) {
initCause(cause);
return this;
}
}

View File

@ -1,78 +1,40 @@
package li.strolch.exception;
import li.strolch.utils.I18nMessage;
import java.util.Locale;
import java.util.ResourceBundle;
import li.strolch.utils.I18nMessage;
public class StrolchUserMessageException extends StrolchException {
private I18nMessage i18n;
public StrolchUserMessageException(I18nMessage i18n) {
super(i18n.getMessage(Locale.getDefault()));
this.i18n = i18n;
if (i18n.hasException())
initCause(i18n.getException());
super(i18n);
}
public StrolchUserMessageException(I18nMessage i18n, Throwable cause) {
super(i18n.getMessage(Locale.getDefault()), cause);
this.i18n = i18n;
super(i18n, cause);
}
public StrolchUserMessageException(ResourceBundle bundle, String key) {
this(new I18nMessage(bundle, key));
super(bundle, key);
}
public StrolchUserMessageException(ResourceBundle bundle, String key, String prop, Object value) {
this(new I18nMessage(bundle, key) //
.value(prop, value));
super(bundle, key, prop, value);
}
public StrolchUserMessageException(ResourceBundle bundle, String key, String prop1, Object value1, String prop2,
Object value2) {
this(new I18nMessage(bundle, key) //
.value(prop1, value1) //
.value(prop2, value2));
super(bundle, key, prop1, value1, prop2, value2);
}
public StrolchUserMessageException(ResourceBundle bundle, String key, String prop1, Object value1, String prop2,
Object value2, String prop3, Object value3) {
this(new I18nMessage(bundle, key) //
.value(prop1, value1) //
.value(prop2, value2) //
.value(prop3, value3));
super(bundle, key, prop1, value1, prop2, value2, prop3, value3);
}
public StrolchUserMessageException(ResourceBundle bundle, String key, String prop1, Object value1, String prop2,
Object value2, String prop3, Object value3, String prop4, Object value4) {
this(new I18nMessage(bundle, key) //
.value(prop1, value1) //
.value(prop2, value2) //
.value(prop3, value3) //
.value(prop4, value4));
}
public StrolchUserMessageException cause(Throwable cause) {
initCause(cause);
return this;
}
public boolean hasI18n() {
return this.i18n != null;
}
public I18nMessage getI18n() {
return this.i18n;
}
public void setI18n(I18nMessage i18n) {
this.i18n = i18n;
}
public StrolchUserMessageException i18n(I18nMessage i18n) {
this.i18n = i18n;
return this;
super(bundle, key, prop1, value1, prop2, value2, prop3, value3, prop4, value4);
}
}

View File

@ -42,10 +42,8 @@ public abstract class AbstractStrolchElement implements StrolchElement {
/**
* Default constructor
*
* @param id
* id of this {@link StrolchElement}
* @param name
* name of this {@link StrolchElement}
* @param id id of this {@link StrolchElement}
* @param name name of this {@link StrolchElement}
*/
public AbstractStrolchElement(String id, String name) {
setId(id);
@ -98,16 +96,15 @@ public abstract class AbstractStrolchElement implements StrolchElement {
* Used to build a {@link Locator} for this {@link StrolchElement}. It must be implemented by the concrete
* implemented as parents must first add their {@link Locator} information
*
* @param locatorBuilder
* the {@link LocatorBuilder} to which the {@link StrolchElement} must add its locator information
* @param locatorBuilder the {@link LocatorBuilder} to which the {@link StrolchElement} must add its locator
* information
*/
protected abstract void fillLocator(LocatorBuilder locatorBuilder);
/**
* fills the {@link StrolchElement} clone with the id, name and type
*
* @param clone
* the clone to fill
* @param clone the clone to fill
*/
protected void fillClone(AbstractStrolchElement clone) {
clone.id = this.id;
@ -139,5 +136,7 @@ public abstract class AbstractStrolchElement implements StrolchElement {
public abstract int hashCode();
@Override
public abstract String toString();
public String toString() {
return getLocator().toString();
}
}

View File

@ -22,7 +22,6 @@ import li.strolch.model.policy.PolicyDefs;
import li.strolch.model.visitor.StrolchElementVisitor;
import li.strolch.model.xml.StrolchXmlHelper;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.iso8601.ISO8601;
import java.text.MessageFormat;
import java.time.*;
@ -331,9 +330,9 @@ public class Order extends AbstractStrolchRootElement implements StrolchRootElem
@Override
public String toString() {
return "Order [id=" + this.id + ", name=" + this.name + ", type=" + this.type + ", state=" + this.state +
", date=" + ISO8601.toString(this.date) + ", version=" + this.version + "]";
if (this.version == null)
return getLocator().toString();
return getLocator() + ", Version: " + this.version.getVersion();
}
@Override

View File

@ -191,7 +191,9 @@ public class Resource extends AbstractStrolchRootElement implements StrolchRootE
if (this.timedStateMap == null || this.timedStateMap.isEmpty())
return Stream.empty();
return this.timedStateMap.values().stream()
return this.timedStateMap
.values()
.stream()
.filter(s -> s.getInterpretation().equals(interpretation) && s.getUom().equals(uom));
}
@ -399,8 +401,9 @@ public class Resource extends AbstractStrolchRootElement implements StrolchRootE
@Override
public String toString() {
return "Resource [id=" + this.id + ", name=" + this.name + ", type=" + this.type + ", version=" + this.version;
if (this.version == null)
return getLocator().toString();
return getLocator() + ", Version: " + this.version.getVersion();
}
@Override

View File

@ -72,6 +72,7 @@ public class StrolchModelConstants {
public static final String BAG_RELATIONS = "relations";
public static final String BAG_PARAMETERS = "parameters";
public static final String BAG_VISIBILITY = "visibility";
public static final String TYPE_PARAMETERS = "Parameters";
public static final String TYPE_VERSION = "Version";
public static final String TYPE_MEMORY = "Memory";
@ -82,6 +83,10 @@ public class StrolchModelConstants {
public static final String TYPE_CONFIGURATION = "Configuration";
public static final String TYPE_OBJECTIVES = "Objectives";
public static final String TYPE_METRIC = "Metric";
public static final String TYPE_NOTIFICATION = "Notification";
public static final String TYPE_VISIBILITY = "Visibility";
public static final String TYPE_TEXT = "Text";
public static final String TYPE_LOCATION = "Location";
public static final String RES_CONFIGURATION = "configuration";
@ -99,6 +104,17 @@ public class StrolchModelConstants {
public static final String PARAM_START_DATE = "startDate";
public static final String PARAM_MODE = "mode";
public static final String PARAM_GROUP = "group";
public static final String PARAM_ENABLED = "enabled";
public static final String PARAM_VISIBLE_FROM = "visibleFrom";
public static final String PARAM_VISIBLE_TO = "visibleTo";
public static final String PARAM_TITLE = "title";
public static final String PARAM_TEXT = "text";
public static final String PARAM_FOR_ALL = "forAll";
public static final String PARAM_ROLES = "roles";
public static final String PARAM_LOCATIONS = "locations";
public static final String PARAM_LOCATION_NAMES = "locationNames";
public static final String PARAM_LANGUAGES = "languages";
public static final String PARAM_GROUPS = "groups";
public static class PolicyConstants {
public static final String POLICY_DEFAULT = "Default";

View File

@ -16,15 +16,6 @@
package li.strolch.model.activity;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.model.StrolchModelConstants.BAG_RELATIONS;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.BAG_OBJECTIVES;
import static li.strolch.utils.helper.StringHelper.isNotEmpty;
import static li.strolch.utils.helper.StringHelper.trimOrEmpty;
import java.text.MessageFormat;
import java.util.*;
import li.strolch.exception.StrolchModelException;
import li.strolch.exception.StrolchPolicyException;
import li.strolch.model.*;
@ -37,6 +28,15 @@ import li.strolch.model.timevalue.IValueChange;
import li.strolch.model.visitor.StrolchElementVisitor;
import li.strolch.utils.dbc.DBC;
import java.text.MessageFormat;
import java.util.*;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.model.StrolchModelConstants.BAG_RELATIONS;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.BAG_OBJECTIVES;
import static li.strolch.utils.helper.StringHelper.isNotEmpty;
import static li.strolch.utils.helper.StringHelper.trimOrEmpty;
/**
* An {@link Action} represents a single step within an {@link Activity}, that is, one that is not further decomposed
* within the {@link Activity}. A {@link Activity} applies {@link IValueChange} objects at the start and end time of the
@ -107,8 +107,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
}
/**
* @param resourceId
* the id of the {@link Resource} the {@link Action} acts on
* @param resourceId the id of the {@link Resource} the {@link Action} acts on
*/
public void setResourceId(String resourceId) {
assertNotReadonly();
@ -124,8 +123,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
}
/**
* @param state
* the target {@code State} of the a {@code Action}
* @param state the target {@code State} of the a {@code Action}
*/
public void setState(State state) {
assertNotReadonly();
@ -140,8 +138,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
}
/**
* @param resourceType
* the resource type
* @param resourceType the resource type
*/
public void setResourceType(String resourceType) {
assertNotReadonly();
@ -151,8 +148,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
/**
* Sets the resource type and id from the given {@link Resource}
*
* @param resource
* the resource from which to get the type and id
* @param resource the resource from which to get the type and id
*/
public void setResource(Resource resource) {
assertNotReadonly();
@ -182,8 +178,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
*
* @return the {@link Locator} for the {@link Resource} for this action
*
* @throws IllegalStateException
* if the resource is not defined
* @throws IllegalStateException if the resource is not defined
*/
public Locator getResourceLocator() {
if (!isResourceDefined())
@ -201,8 +196,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
}
/**
* @param change
* {@code IValueChange} to be applied to the {@code Resource}
* @param change {@code IValueChange} to be applied to the {@code Resource}
*
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
@ -383,8 +377,7 @@ public class Action extends GroupedParameterizedElement implements IActivityElem
@Override
public String toString() {
return "Action [id=" + this.id + ", name=" + this.name + ", type=" + this.type + ", resourceId="
+ this.resourceId + ", state=" + this.state + "]";
return getLocator() + ", resourceId: " + this.resourceId + ", state=" + this.state;
}
@Override

View File

@ -495,7 +495,8 @@ public class Activity extends AbstractStrolchRootElement
public <T extends IActivityElement> T findElement(Predicate<IActivityElement> predicate,
Supplier<String> msgSupplier) {
@SuppressWarnings("unchecked") T t = (T) streamElements().filter(predicate)
@SuppressWarnings("unchecked") T t = (T) streamElements()
.filter(predicate)
.collect(singletonCollector(msgSupplier));
return t;
}
@ -778,23 +779,17 @@ public class Activity extends AbstractStrolchRootElement
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Activity [id=");
builder.append(this.id);
builder.append(", name=");
builder.append(this.name);
builder.append(", type=");
builder.append(this.type);
builder.append(getLocator());
builder.append(", state=");
builder.append(getState());
builder.append(", start=");
builder.append(getStart());
builder.append(", end=");
builder.append(getEnd());
if (isRootElement()) {
if (isRootElement() && this.version != null) {
builder.append(", version=");
builder.append(this.version);
builder.append(this.version.getVersion());
}
builder.append("]");
return builder.toString();
}

View File

@ -4,6 +4,8 @@ import li.strolch.model.PolicyContainer;
import li.strolch.model.activity.Action;
import li.strolch.utils.dbc.DBC;
import static li.strolch.model.builder.BuilderHelper.buildParamName;
public class ActionBuilder extends PolicyContainerBuilder<ActionBuilder> implements ActivityElementBuilder {
private final ActivityBuilder builder;
@ -11,11 +13,19 @@ public class ActionBuilder extends PolicyContainerBuilder<ActionBuilder> impleme
private String resourceId;
private String resourceType;
public ActionBuilder(String id, String type) {
this(id, buildParamName(id), type);
}
public ActionBuilder(String id, String name, String type) {
super(id, name, type);
this.builder = null;
}
public ActionBuilder(ActivityBuilder builder, String id, String type) {
this(builder, id, buildParamName(id), type);
}
public ActionBuilder(ActivityBuilder builder, String id, String name, String type) {
super(id, name, type);
this.builder = builder;

View File

@ -7,6 +7,8 @@ import li.strolch.model.activity.Activity;
import li.strolch.model.activity.TimeOrdering;
import li.strolch.utils.dbc.DBC;
import static li.strolch.model.builder.BuilderHelper.*;
public class ActivityBuilder extends RootElementBuilder<ActivityBuilder> implements ActivityElementBuilder {
private final StrolchElementBuilder builder;
@ -15,6 +17,10 @@ public class ActivityBuilder extends RootElementBuilder<ActivityBuilder> impleme
private final List<ActivityElementBuilder> builders;
public ActivityBuilder(String id, String type, TimeOrdering timeOrdering) {
this(id, buildParamName(id), type, timeOrdering);
}
public ActivityBuilder(String id, String name, String type, TimeOrdering timeOrdering) {
super(id, name, type);
this.builder = null;
@ -23,6 +29,10 @@ public class ActivityBuilder extends RootElementBuilder<ActivityBuilder> impleme
this.builders = new ArrayList<>();
}
public ActivityBuilder(StrolchElementBuilder builder, String id, String type, TimeOrdering timeOrdering) {
this(builder, id, buildParamName(id), type, timeOrdering);
}
public ActivityBuilder(StrolchElementBuilder builder, String id, String name, String type,
TimeOrdering timeOrdering) {
super(id, name, type);
@ -32,6 +42,11 @@ public class ActivityBuilder extends RootElementBuilder<ActivityBuilder> impleme
this.builders = new ArrayList<>();
}
public ActivityBuilder(StrolchElementBuilder builder, ActivityBuilder parentBuilder, String id, String type,
TimeOrdering timeOrdering) {
this(builder, parentBuilder, id, buildParamName(id), type, timeOrdering);
}
public ActivityBuilder(StrolchElementBuilder builder, ActivityBuilder parentBuilder, String id, String name,
String type, TimeOrdering timeOrdering) {
super(id, name, type);
@ -41,12 +56,20 @@ public class ActivityBuilder extends RootElementBuilder<ActivityBuilder> impleme
this.builders = new ArrayList<>();
}
public ActivityBuilder subActivity(String id, String type, TimeOrdering timeOrdering) {
return subActivity(id, buildParamName(id), type, timeOrdering);
}
public ActivityBuilder subActivity(String id, String name, String type, TimeOrdering timeOrdering) {
ActivityBuilder builder = new ActivityBuilder(this.builder, this, id, name, type, timeOrdering);
this.builders.add(builder);
return builder;
}
public ActionBuilder action(String id, String type) {
return action(id, buildParamName(id), type);
}
public ActionBuilder action(String id, String name, String type) {
ActionBuilder builder = new ActionBuilder(this, id, name, type);
this.builders.add(builder);

View File

@ -7,6 +7,8 @@ import li.strolch.model.ParameterBag;
import li.strolch.model.ParameterBagContainer;
import li.strolch.model.builder.params.*;
import static li.strolch.model.builder.BuilderHelper.buildParamName;
public class BagBuilder<T extends ParameterBagContainerBuilder<T>> {
private final T builder;
@ -15,6 +17,10 @@ public class BagBuilder<T extends ParameterBagContainerBuilder<T>> {
private final String type;
private final List<ParameterBuilder<?, ?, ?>> parameters;
public BagBuilder(T builder, String id, String type) {
this(builder, id, buildParamName(id), type);
}
public BagBuilder(T builder, String id, String name, String type) {
this.builder = builder;
this.id = id;
@ -23,72 +29,120 @@ public class BagBuilder<T extends ParameterBagContainerBuilder<T>> {
this.parameters = new ArrayList<>();
}
public StringParamBuilder<T> string(String id) {
return string(id, buildParamName(id));
}
public StringParamBuilder<T> string(String id, String name) {
StringParamBuilder<T> builder = new StringParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public TextParamBuilder<T> text(String id) {
return text(id, buildParamName(id));
}
public TextParamBuilder<T> text(String id, String name) {
TextParamBuilder<T> builder = new TextParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public BooleanParamBuilder<T> booleanB(String id) {
return booleanB(id, buildParamName(id));
}
public BooleanParamBuilder<T> booleanB(String id, String name) {
BooleanParamBuilder<T> builder = new BooleanParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public IntegerParamBuilder<T> integer(String id) {
return integer(id, buildParamName(id));
}
public IntegerParamBuilder<T> integer(String id, String name) {
IntegerParamBuilder<T> builder = new IntegerParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public LongParamBuilder<T> longB(String id) {
return longB(id, buildParamName(id));
}
public LongParamBuilder<T> longB(String id, String name) {
LongParamBuilder<T> builder = new LongParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public FloatParamBuilder<T> floatB(String id) {
return floatB(id, buildParamName(id));
}
public FloatParamBuilder<T> floatB(String id, String name) {
FloatParamBuilder<T> builder = new FloatParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public DateParamBuilder<T> date(String id) {
return date(id, buildParamName(id));
}
public DateParamBuilder<T> date(String id, String name) {
DateParamBuilder<T> builder = new DateParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public DurationParamBuilder<T> duration(String id) {
return duration(id, buildParamName(id));
}
public DurationParamBuilder<T> duration(String id, String name) {
DurationParamBuilder<T> builder = new DurationParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public StringListParamBuilder<T> stringList(String id) {
return stringList(id, buildParamName(id));
}
public StringListParamBuilder<T> stringList(String id, String name) {
StringListParamBuilder<T> builder = new StringListParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public IntegerListParamBuilder<T> integerList(String id) {
return integerList(id, buildParamName(id));
}
public IntegerListParamBuilder<T> integerList(String id, String name) {
IntegerListParamBuilder<T> builder = new IntegerListParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public LongListParamBuilder<T> longList(String id) {
return longList(id, buildParamName(id));
}
public LongListParamBuilder<T> longList(String id, String name) {
LongListParamBuilder<T> builder = new LongListParamBuilder<>(this, id, name);
this.parameters.add(builder);
return builder;
}
public FloatListParamBuilder<T> floatList(String id) {
return floatList(id, buildParamName(id));
}
public FloatListParamBuilder<T> floatList(String id, String name) {
FloatListParamBuilder<T> builder = new FloatListParamBuilder<>(this, id, name);
this.parameters.add(builder);

View File

@ -5,15 +5,25 @@ import li.strolch.model.State;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.iso8601.ISO8601;
import static li.strolch.model.builder.BuilderHelper.*;
public class OrderBuilder extends RootElementBuilder<OrderBuilder> {
private final StrolchElementBuilder builder;
public OrderBuilder(String id, String type) {
this(id, buildParamName(id), type);
}
public OrderBuilder(String id, String name, String type) {
super(id, name, type);
this.builder = null;
}
public OrderBuilder(StrolchElementBuilder builder, String id, String type) {
this(builder, id, buildParamName(id), type);
}
public OrderBuilder(StrolchElementBuilder builder, String id, String name, String type) {
super(id, name, type);
this.builder = builder;

View File

@ -1,19 +1,20 @@
package li.strolch.model.builder;
import static java.util.Collections.emptyList;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.BAG_OBJECTIVES;
import static li.strolch.model.builder.BuilderHelper.buildParamId;
import java.util.HashMap;
import java.util.Map;
import li.strolch.model.ParameterBag;
import li.strolch.model.ParameterBagContainer;
import li.strolch.model.Tags;
import li.strolch.model.parameter.StringListParameter;
import li.strolch.model.parameter.StringParameter;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyList;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.BAG_OBJECTIVES;
import static li.strolch.model.builder.BuilderHelper.buildParamId;
import static li.strolch.model.builder.BuilderHelper.buildParamName;
public abstract class ParameterBagContainerBuilder<T extends ParameterBagContainerBuilder<T>> {
private final String id;
@ -24,6 +25,10 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
private final Map<String, String[]> singleRelations;
private final Map<String, String[]> multiRelations;
public ParameterBagContainerBuilder(String id, String type) {
this(id, buildParamName(id), type);
}
public ParameterBagContainerBuilder(String id, String name, String type) {
this.id = id;
this.name = name;
@ -58,9 +63,12 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return bag(BAG_RELATIONS, TYPE_RELATIONS, TYPE_RELATIONS);
}
public BagBuilder<T> bag(String id, String type) {
return bag(id, buildParamName(id), type);
}
public BagBuilder<T> bag(String id, String name, String type) {
@SuppressWarnings("unchecked")
BagBuilder<T> bagBuilder = new BagBuilder<>((T) this, id, name, type);
@SuppressWarnings("unchecked") BagBuilder<T> bagBuilder = new BagBuilder<>((T) this, id, name, type);
if (this.parametersBags.put(id, bagBuilder) != null)
throw new IllegalArgumentException("Bag builder for " + id + " already exists!");
return bagBuilder;
@ -70,11 +78,14 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return resourceRelation(buildParamId(type), type, type);
}
public T resourceRelation(String paramId, String type) {
return resourceRelation(paramId, buildParamName(paramId), type);
}
public T resourceRelation(String paramId, String paramName, String type) {
assertNotMapped(paramId);
this.singleRelations.put(paramId, new String[] { paramName, type, Tags.RESOURCE });
@SuppressWarnings("unchecked")
T t = (T) this;
this.singleRelations.put(paramId, new String[]{paramName, type, Tags.RESOURCE});
@SuppressWarnings("unchecked") T t = (T) this;
return t;
}
@ -82,11 +93,14 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return resourceRelations(buildParamId(type), type + "s", type);
}
public T resourceRelations(String paramId, String type) {
return resourceRelations(paramId, buildParamName(paramId), type);
}
public T resourceRelations(String paramId, String paramName, String type) {
assertNotMapped(paramId);
this.multiRelations.put(paramId, new String[] { paramName, type, Tags.RESOURCE });
@SuppressWarnings("unchecked")
T t = (T) this;
this.multiRelations.put(paramId, new String[]{paramName, type, Tags.RESOURCE});
@SuppressWarnings("unchecked") T t = (T) this;
return t;
}
@ -94,11 +108,14 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return orderRelation(buildParamId(type), type, type);
}
public T orderRelation(String paramId, String type) {
return orderRelation(paramId, buildParamName(paramId), type);
}
public T orderRelation(String paramId, String paramName, String type) {
assertNotMapped(paramId);
this.singleRelations.put(paramId, new String[] { paramName, type, Tags.ORDER });
@SuppressWarnings("unchecked")
T t = (T) this;
this.singleRelations.put(paramId, new String[]{paramName, type, Tags.ORDER});
@SuppressWarnings("unchecked") T t = (T) this;
return t;
}
@ -106,11 +123,14 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return orderRelations(buildParamId(type), type + "s", type);
}
public T orderRelations(String paramId, String type) {
return orderRelations(paramId, buildParamName(paramId), type);
}
public T orderRelations(String paramId, String paramName, String type) {
assertNotMapped(paramId);
this.multiRelations.put(paramId, new String[] { paramName, type, Tags.ORDER });
@SuppressWarnings("unchecked")
T t = (T) this;
this.multiRelations.put(paramId, new String[]{paramName, type, Tags.ORDER});
@SuppressWarnings("unchecked") T t = (T) this;
return t;
}
@ -118,11 +138,14 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return activityRelation(buildParamId(type), type, type);
}
public T activityRelation(String paramId, String type) {
return activityRelation(paramId, buildParamName(paramId), type);
}
public T activityRelation(String paramId, String paramName, String type) {
assertNotMapped(paramId);
this.singleRelations.put(paramId, new String[] { paramName, type, Tags.ACTIVITY });
@SuppressWarnings("unchecked")
T t = (T) this;
this.singleRelations.put(paramId, new String[]{paramName, type, Tags.ACTIVITY});
@SuppressWarnings("unchecked") T t = (T) this;
return t;
}
@ -130,11 +153,14 @@ public abstract class ParameterBagContainerBuilder<T extends ParameterBagContain
return activityRelations(buildParamId(type), type + "s", type);
}
public T activityRelations(String paramId, String type) {
return activityRelations(paramId, buildParamName(paramId), type);
}
public T activityRelations(String paramId, String paramName, String type) {
assertNotMapped(paramId);
this.multiRelations.put(paramId, new String[] { paramName, type, Tags.ACTIVITY });
@SuppressWarnings("unchecked")
T t = (T) this;
this.multiRelations.put(paramId, new String[]{paramName, type, Tags.ACTIVITY});
@SuppressWarnings("unchecked") T t = (T) this;
return t;
}

View File

@ -3,18 +3,23 @@ package li.strolch.model.builder;
import li.strolch.model.ParameterBagContainer;
import li.strolch.model.PolicyContainer;
import static li.strolch.model.builder.BuilderHelper.buildParamName;
public class PolicyContainerBuilder<T extends ParameterBagContainerBuilder<T>> extends ParameterBagContainerBuilder<T> {
private PoliciesBuilder<T> policies;
public PolicyContainerBuilder(String id, String type) {
super(id, buildParamName(id), type);
}
public PolicyContainerBuilder(String id, String name, String type) {
super(id, name, type);
}
public PoliciesBuilder<T> policies() {
if (this.policies == null) {
@SuppressWarnings("unchecked")
T t = (T) this;
@SuppressWarnings("unchecked") T t = (T) this;
this.policies = new PoliciesBuilder<>(t);
}
return policies;

View File

@ -1,65 +1,104 @@
package li.strolch.model.builder;
import java.util.ArrayList;
import java.util.List;
import li.strolch.model.Resource;
import li.strolch.model.builder.states.*;
import li.strolch.utils.dbc.DBC;
import java.util.ArrayList;
import java.util.List;
import static li.strolch.model.builder.BuilderHelper.buildParamName;
import static li.strolch.utils.helper.StringHelper.getUniqueId;
public class ResourceBuilder extends RootElementBuilder<ResourceBuilder> {
private final StrolchElementBuilder builder;
private final List<TimedStateBuilder<?>> timedStates;
public ResourceBuilder(String name, String type) {
this(getUniqueId(), name, type);
}
public ResourceBuilder(String id, String name, String type) {
super(id, name, type);
this.builder = null;
this.timedStates = new ArrayList<>();
}
public ResourceBuilder(StrolchElementBuilder builder, String name, String type) {
this(builder, getUniqueId(), name, type);
}
public ResourceBuilder(StrolchElementBuilder builder, String id, String name, String type) {
super(id, name, type);
this.builder = builder;
this.timedStates = new ArrayList<>();
}
public BooleanStateBuilder booleanState(String id) {
return booleanState(id, buildParamName(id));
}
public BooleanStateBuilder booleanState(String id, String name) {
BooleanStateBuilder builder = new BooleanStateBuilder(this, id, name);
this.timedStates.add(builder);
return builder;
}
public FloatStateBuilder floatState(String id) {
return floatState(id, buildParamName(id));
}
public FloatStateBuilder floatState(String id, String name) {
FloatStateBuilder builder = new FloatStateBuilder(this, id, name);
this.timedStates.add(builder);
return builder;
}
public IntegerStateBuilder integerState(String id) {
return integerState(id, buildParamName(id));
}
public IntegerStateBuilder integerState(String id, String name) {
IntegerStateBuilder builder = new IntegerStateBuilder(this, id, name);
this.timedStates.add(builder);
return builder;
}
public LongStateBuilder longState(String id) {
return longState(id, buildParamName(id));
}
public LongStateBuilder longState(String id, String name) {
LongStateBuilder builder = new LongStateBuilder(this, id, name);
this.timedStates.add(builder);
return builder;
}
public FloatListStateBuilder floatListState(String id) {
return floatListState(id, buildParamName(id));
}
public FloatListStateBuilder floatListState(String id, String name) {
FloatListStateBuilder builder = new FloatListStateBuilder(this, id, name);
this.timedStates.add(builder);
return builder;
}
public IntegerListStateBuilder integerListState(String id) {
return integerListState(id, buildParamName(id));
}
public IntegerListStateBuilder integerListState(String id, String name) {
IntegerListStateBuilder builder = new IntegerListStateBuilder(this, id, name);
this.timedStates.add(builder);
return builder;
}
public StringSetStateBuilder stringSetState(String id) {
return stringSetState(id, buildParamName(id));
}
public StringSetStateBuilder stringSetState(String id, String name) {
StringSetStateBuilder builder = new StringSetStateBuilder(this, id, name);
this.timedStates.add(builder);

View File

@ -1,10 +1,15 @@
package li.strolch.model.builder;
import li.strolch.model.PolicyContainer;
import li.strolch.model.StrolchRootElement;
import static li.strolch.model.builder.BuilderHelper.buildParamName;
public abstract class RootElementBuilder<T extends ParameterBagContainerBuilder<T>> extends PolicyContainerBuilder<T> {
public RootElementBuilder(String id, String type) {
super(id, buildParamName(id), type);
}
public RootElementBuilder(String id, String name, String type) {
super(id, name, type);
}

View File

@ -27,19 +27,19 @@ public class StrolchElementBuilder {
this.activityBuilders = new HashMap<>();
}
public ResourceBuilder resource(String name, String type) {
public ResourceBuilder resourceTemplate(String name, String type) {
ResourceBuilder builder = new ResourceBuilder(this, type, name, TEMPLATE);
this.resourceBuilders.put(type, builder);
return builder;
}
public OrderBuilder order(String name, String type) {
public OrderBuilder orderTemplate(String name, String type) {
OrderBuilder builder = new OrderBuilder(this, type, name, TEMPLATE);
this.orderBuilders.put(type, builder);
return builder;
}
public ActivityBuilder activity(String name, String type, TimeOrdering timeOrdering) {
public ActivityBuilder activityTemplate(String name, String type, TimeOrdering timeOrdering) {
ActivityBuilder builder = new ActivityBuilder(this, type, name, TEMPLATE, timeOrdering);
this.activityBuilders.put(type, builder);
return builder;
@ -62,7 +62,7 @@ public class StrolchElementBuilder {
public List<StrolchRootElement> buildTemplates() {
return concat(concat(this.resourceBuilders.values().stream(), //
this.orderBuilders.values().stream()), //
this.orderBuilders.values().stream()), //
this.activityBuilders.values().stream()) //
.map(RootElementBuilder::build).collect(toList());
}
@ -71,24 +71,24 @@ public class StrolchElementBuilder {
ResourceBuilder builder = this.resourceBuilders.get(type);
if (builder == null)
throw new IllegalArgumentException("No resource template defined for type " + type);
return updateFields(type, newName, builder.build());
return setInitialFields(type, newName, builder.build());
}
public Order newOrder(String type, String newName) {
OrderBuilder builder = this.orderBuilders.get(type);
if (builder == null)
throw new IllegalArgumentException("No resource template defined for type " + type);
return updateFields(type, newName, builder.build());
return setInitialFields(type, newName, builder.build());
}
public Activity newActivity(String type, String newName) {
ActivityBuilder builder = this.activityBuilders.get(type);
if (builder == null)
throw new IllegalArgumentException("No resource template defined for type " + type);
return updateFields(type, newName, builder.build());
return setInitialFields(type, newName, builder.build());
}
private <T extends StrolchRootElement> T updateFields(String type, String newName, T element) {
private <T extends StrolchRootElement> T setInitialFields(String type, String newName, T element) {
element.setId(StringHelper.getUniqueId());
element.setName(newName);
element.setType(type);

View File

@ -33,7 +33,7 @@ public class StrolchElementBuilderTest {
*/
// person
.resource("Person Template", "Person") //
.resourceTemplate("Person Template", "Person") //
.defaultBag() //
.date("birthdate", "Birthdate").value(ZonedDateTime.now()).end() //
.string("case", "Case").interpretation("Case").uom("Simple").end() //
@ -47,7 +47,7 @@ public class StrolchElementBuilderTest {
.endResource() //
// cars
.resource("Car Template", "Car") //
.resourceTemplate("Car Template", "Car") //
.defaultBag() //
.string("color", "Color").value("white").end() //
.endBag() //
@ -56,7 +56,7 @@ public class StrolchElementBuilderTest {
.endResource() //
// machines
.resource("Machine Template", "Machine") //
.resourceTemplate("Machine Template", "Machine") //
.defaultBag() //
.string("color", "Color").end() //
.endBag() //
@ -68,7 +68,7 @@ public class StrolchElementBuilderTest {
*/
// orders
.order("Order Template", "Order") //
.orderTemplate("Order Template", "Order") //
.defaultBag() //
.string("description", "Description").end() //
.endBag() //
@ -83,7 +83,7 @@ public class StrolchElementBuilderTest {
*/
// ToStock
.activity("ToStock Template", "ToStock", TimeOrdering.SERIES) //
.activityTemplate("ToStock Template", "ToStock", TimeOrdering.SERIES) //
.defaultBag() //
.string("description", "Description").end() //
.endBag() //
@ -211,7 +211,7 @@ public class StrolchElementBuilderTest {
Resource car1 = new StrolchElementBuilder() //
.resource("Car Template", "Car") //
.resourceTemplate("Car Template", "Car") //
.defaultBag() //
.string("color", "Color").value("white").end() //
.endBag() //

View File

@ -5,7 +5,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

151
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<name>strolch</name>
<description>Module build for strolch</description>
@ -80,10 +80,10 @@
<!-- compile time dependencies -->
<slf4j.version>2.0.5</slf4j.version>
<logback.version>1.4.8</logback.version>
<logback.version>1.4.14</logback.version>
<gson.version>2.10</gson.version>
<hikaricp.version>5.0.1</hikaricp.version>
<postgresql.version>42.5.1</postgresql.version>
<postgresql.version>42.7.2</postgresql.version>
<antlr.version>4.9.3</antlr.version>
<jakarta-mail.version>2.1.0</jakarta-mail.version>
<angus-mail.version>2.0.1</angus-mail.version>
@ -91,16 +91,15 @@
<cron.version>1.6.2</cron.version>
<owasp-encoder-esapi.version>1.2.3</owasp-encoder-esapi.version>
<jakarta.xml.bind-api.version>4.0.0</jakarta.xml.bind-api.version>
<jakarta.xml.bind-api.version>4.0.2</jakarta.xml.bind-api.version>
<jakarta.annotation.version>2.1.1</jakarta.annotation.version>
<jakarta.activation.version>2.1.0</jakarta.activation.version>
<jakarta.xml.bind-api.version>4.0.0</jakarta.xml.bind-api.version>
<jakarta.activation.version>2.1.3</jakarta.activation.version>
<jakarta.ws.rs-api.version>3.1.0</jakarta.ws.rs-api.version>
<jakarta.servlet-api.version>6.0.0</jakarta.servlet-api.version>
<jakarta.websocket.version>2.1.0</jakarta.websocket.version>
<jersey.version>3.1.2</jersey.version>
<jakarta.websocket.version>2.1.1</jakarta.websocket.version>
<jersey.version>3.1.5</jersey.version>
<camel.version>3.19.0</camel.version>
<camel.version>3.22.1</camel.version>
<hapi.version>2.3</hapi.version>
<pi4j.version>1.4</pi4j.version>
@ -124,7 +123,7 @@
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-war-plugin.version>3.4.0</maven-war-plugin.version>
<maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version>
<maven-deploy-plugin.version>3.0.0-M2</maven-deploy-plugin.version>
<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-dependency-plugin.version>3.2.0</maven-dependency-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
@ -388,6 +387,7 @@
</dependencyManagement>
<build>
<finalName>strolch-${artifactId}</finalName>
<pluginManagement>
<plugins>
@ -672,21 +672,6 @@
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>${nexus-staging-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<serverId>oss.sonatype.org</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
</configuration>
<executions>
<execution>
<id>deploy-to-sonatype</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
<goal>release</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
@ -703,12 +688,12 @@
<distributionManagement>
<repository>
<id>oss.sonatype.org</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
<id>repo.strolch.li</id>
<url>https://repo.strolch.li/repository/strolch-releases/</url>
</repository>
<snapshotRepository>
<id>oss.sonatype.org</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<id>repo.strolch.li</id>
<url>https://repo.strolch.li/repository/strolch-snapshots/</url>
</snapshotRepository>
<site>
<id>localhost</id>
@ -744,7 +729,7 @@
</build>
</profile>
<profile>
<id>deploy</id>
<id>deploy-snapshots</id>
<build>
<plugins>
<plugin>
@ -762,10 +747,116 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<serverId>repo.strolch.li</serverId>
<nexusUrl>https://repo.strolch.li/</nexusUrl>
</configuration>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>deploy-releases</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<serverId>repo.strolch.li</serverId>
<nexusUrl>https://repo.strolch.li/</nexusUrl>
</configuration>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>deploy-maven-central</id>
<distributionManagement>
<repository>
<id>oss.sonatype.org</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
<snapshotRepository>
<id>oss.sonatype.org</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<site>
<id>localhost</id>
<url>file://${project.basedir}/target</url>
</site>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<serverId>oss.sonatype.org</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
</configuration>
<executions>
<execution>
<id>deploy-to-sonatype</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
<goal>release</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<reporting>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -30,7 +30,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXParseException;
import javax.crypto.SecretKey;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.IOException;
@ -292,14 +291,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
() -> crudHandler.removeRole(certificate, roleName));
}
void invalidSessionsFor(User user) {
List<PrivilegeContext> contexts = new ArrayList<>(this.privilegeContextMap.values());
for (PrivilegeContext ctx : contexts) {
if (ctx.getUserRep().getUsername().equals(user.getUsername()))
invalidate(ctx.getCertificate());
}
}
@Override
public void initiateChallengeFor(Usage usage, String username) {
initiateChallengeFor(usage, username, SOURCE_UNKNOWN);
@ -355,8 +346,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
false).getCertificate();
if (!source.equals("unknown") && !source.equals(userChallenge.getSource())) {
logger.warn("Challenge request and response source's are different: request: " + userChallenge.getSource() +
" to " + source);
logger.warn(format("Challenge request and response source''s are different: request: {0} to {1}",
userChallenge.getSource(), source));
}
persistSessionsAsync();
@ -444,9 +435,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
* @return a stream of role names
*/
public static Stream<String> streamAllRolesForUser(PersistenceHandler persistenceHandler, User user) {
return Stream.concat(user.getRoles().stream(),
user.groups().stream().map(persistenceHandler::getGroup).filter(Objects::nonNull)
.flatMap(g -> g.roles().stream()));
return Stream.concat(user.getRoles().stream(), user
.groups()
.stream()
.map(persistenceHandler::getGroup)
.filter(Objects::nonNull)
.flatMap(g -> g.roles().stream()));
}
@Override
@ -554,34 +548,37 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// async execution, max. once per second
if (this.persistSessionsTask != null)
this.persistSessionsTask.cancel(true);
this.persistSessionsTask = this.executorService.schedule(() -> {
// get sessions reference
AtomicReference<List<Certificate>> sessions = new AtomicReference<>();
this.lockingHandler.lockedExecute("persist-sessions", () -> sessions.set(
new ArrayList<>(this.privilegeContextMap.values()).stream().map(PrivilegeContext::getCertificate)
.filter(c -> !c.getUserState().isSystem()).collect(toList())));
// write the sessions
try (OutputStream out = Files.newOutputStream(this.persistSessionsPath.toPath());
OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, out)) {
CertificateStubsSaxWriter writer = new CertificateStubsSaxWriter(sessions.get(), outputStream);
writer.write();
outputStream.flush();
} catch (Exception e) {
logger.error("Failed to persist sessions!", e);
if (this.persistSessionsPath.exists() && !this.persistSessionsPath.delete()) {
logger.error("Failed to delete sessions file after failing to write to it, at " +
this.persistSessionsPath.getAbsolutePath());
}
}
}, 1, TimeUnit.SECONDS);
this.persistSessionsTask = this.executorService.schedule(this::internalPersistSessions, 1, TimeUnit.SECONDS);
return true;
}
private void internalPersistSessions() {
// get sessions reference
AtomicReference<List<Certificate>> sessions = new AtomicReference<>();
this.lockingHandler.lockedExecute("persist-sessions", () -> sessions.set(
new ArrayList<>(this.privilegeContextMap.values())
.stream()
.map(PrivilegeContext::getCertificate)
.filter(c -> !c.getUserState().isSystem())
.collect(toList())));
// write the sessions
try (OutputStream out = Files.newOutputStream(this.persistSessionsPath.toPath());
OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, out)) {
CertificateStubsSaxWriter writer = new CertificateStubsSaxWriter(sessions.get(), outputStream);
writer.write();
outputStream.flush();
} catch (Exception e) {
logger.error("Failed to persist sessions!", e);
if (this.persistSessionsPath.exists() && !this.persistSessionsPath.delete()) {
logger.error("Failed to delete sessions file after failing to write to it, at "
+ this.persistSessionsPath.getAbsolutePath());
}
}
}
private void loadSessions() {
if (!this.persistSessions) {
logger.info("Persisting of sessions not enabled, so not loading!.");
@ -694,8 +691,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
PasswordCrypt requestPasswordCrypt;
if (userPasswordCrypt.salt() == null) {
requestPasswordCrypt = this.encryptionHandler.hashPasswordWithoutSalt(password);
} else if (userPasswordCrypt.hashAlgorithm() == null || userPasswordCrypt.hashIterations() == -1 ||
userPasswordCrypt.hashKeyLength() == -1) {
} else if (userPasswordCrypt.hashAlgorithm() == null
|| userPasswordCrypt.hashIterations() == -1
|| userPasswordCrypt.hashKeyLength() == -1) {
requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.salt());
} else {
requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.salt(),
@ -1075,12 +1073,27 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
}
/**
* Invalidates all the sessions for the given user and persists the sessions async
*
* @param user the user for which to invalidate the sessions
*/
void invalidateSessionsFor(User user) {
List<PrivilegeContext> contexts = new ArrayList<>(this.privilegeContextMap.values());
for (PrivilegeContext ctx : contexts) {
if (ctx.getUserRep().getUsername().equals(user.getUsername()))
invalidate(ctx.getCertificate());
}
persistSessionsAsync();
}
/**
* Replaces any existing {@link PrivilegeContext} for the given user by updating with the new user object
*
* @param newUser the new user to update with
*/
void updateExistingSessionsForUser(User newUser) {
void updateExistingSessionsForUser(User newUser, boolean persistSessions) {
List<PrivilegeContext> contexts = new ArrayList<>(this.privilegeContextMap.values());
for (PrivilegeContext ctx : contexts) {
if (!ctx.getUserRep().getUsername().equals(newUser.getUsername()))
@ -1088,7 +1101,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
replacePrivilegeContextForCert(newUser, ctx.getCertificate());
}
persistSessionsAsync();
if (persistSessions)
persistSessionsAsync();
}
/**

View File

@ -172,8 +172,15 @@ public class PrivilegeCrudHandler {
// properties
propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties());
boolean selected = userIdSelected && usernameSelected && firstNameSelected && lastNameSelected &&
userStateSelected && localeSelected && groupSelected && roleSelected && propertySelected;
boolean selected = userIdSelected
&& usernameSelected
&& firstNameSelected
&& lastNameSelected
&& userStateSelected
&& localeSelected
&& groupSelected
&& roleSelected
&& propertySelected;
if (selected)
result.add(user.asUserRep());
@ -376,7 +383,10 @@ public class PrivilegeCrudHandler {
// delegate to persistence handler
toCreate.forEach(this.persistenceHandler::addUser);
toUpdate.forEach(this.persistenceHandler::replaceUser);
for (User user : toUpdate) {
this.persistenceHandler.replaceUser(user);
this.privilegeHandler.updateExistingSessionsForUser(user, false);
}
this.privilegeHandler.persistModelAsync();
DefaultPrivilegeHandler.logger.info("Created " + toCreate.size() + " users");
@ -530,7 +540,7 @@ public class PrivilegeCrudHandler {
// delegate to persistence handler
this.persistenceHandler.replaceUser(newUser);
this.privilegeHandler.persistModelAsync();
this.privilegeHandler.updateExistingSessionsForUser(newUser, true);
DefaultPrivilegeHandler.logger.info("Replaced user " + newUser.getUsername());
@ -559,9 +569,8 @@ public class PrivilegeCrudHandler {
new SimpleRestrictable(DefaultPrivilegeHandler.PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser)));
// delegate user removal to persistence handler
this.privilegeHandler.invalidSessionsFor(existingUser);
this.privilegeHandler.invalidateSessionsFor(existingUser);
this.persistenceHandler.removeUser(username);
this.privilegeHandler.persistModelAsync();
DefaultPrivilegeHandler.logger.info("Removed user " + username);
@ -715,7 +724,7 @@ public class PrivilegeCrudHandler {
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
this.privilegeHandler.persistModelAsync();
this.privilegeHandler.invalidateSessionsFor(newUser);
DefaultPrivilegeHandler.logger.info("Set state of user " + newUser.getUsername() + " to " + state);
@ -789,11 +798,11 @@ public class PrivilegeCrudHandler {
this.persistenceHandler.replaceRole(newRole);
this.privilegeHandler.persistModelAsync();
DefaultPrivilegeHandler.logger.info("Replaced role " + newRole.getName());
// update any existing certificates with new role
this.privilegeHandler.updateExistingSessionsWithNewRole(newRole);
DefaultPrivilegeHandler.logger.info("Replaced role " + newRole.getName());
return newRole.asRoleRep();
}

View File

@ -0,0 +1,27 @@
package li.strolch.privilege.model;
public class CertificateThreadLocal extends ThreadLocal<Certificate> {
private static final CertificateThreadLocal instance = new CertificateThreadLocal();
public static boolean hasCert() {
return instance.get() != null;
}
public static Certificate getCert() {
Certificate cert = instance.get();
if (cert == null)
throw new IllegalStateException("No Cert available on thread " + Thread.currentThread().getName());
return cert;
}
public static void setCert(Certificate cert) {
if (instance.get() != null)
throw new IllegalStateException("THIS THREAD HAS ALREADY HAS A CERT!");
instance.set(cert);
}
public static void removeCert() {
instance.remove();
}
}

View File

@ -15,11 +15,6 @@
*/
package li.strolch.privilege.policy;
import static li.strolch.privilege.policy.PrivilegePolicyHelper.checkByAllowDenyValues;
import static li.strolch.privilege.policy.PrivilegePolicyHelper.preValidate;
import java.text.MessageFormat;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.handler.PrivilegeHandler;
@ -31,6 +26,11 @@ import li.strolch.privilege.model.internal.Role;
import li.strolch.utils.collections.Tuple;
import li.strolch.utils.dbc.DBC;
import java.text.MessageFormat;
import static li.strolch.privilege.policy.PrivilegePolicyHelper.checkByAllowDenyValues;
import static li.strolch.privilege.policy.PrivilegePolicyHelper.preValidate;
/**
* This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()}. The Tuple must
* contain {@link Role} as first and second value. Then the policy decides depending on the user specific privileges
@ -67,8 +67,8 @@ public class RoleAccessPrivilege implements PrivilegePolicy {
// RoleAccessPrivilege policy expects the privilege value to be a role
if (!(object instanceof Tuple tuple)) {
String msg = Restrictable.class.getName() + PrivilegeMessages
.getString("Privilege.illegalArgument.nontuple");
String msg = Restrictable.class.getName() + PrivilegeMessages.getString(
"Privilege.illegalArgument.nontuple");
msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName());
throw new PrivilegeException(msg);
}
@ -78,32 +78,30 @@ public class RoleAccessPrivilege implements PrivilegePolicy {
return true;
// get role name as privilege value
Role oldRole = tuple.getFirst();
Role newRole = tuple.getSecond();
String oldRole = tuple.getFirst() instanceof Role r ? r.getName() : tuple.getFirst();
String newRole = tuple.getSecond() instanceof Role r ? r.getName() : tuple.getSecond();
switch (privilegeName) {
case PrivilegeHandler.PRIVILEGE_GET_ROLE, PrivilegeHandler.PRIVILEGE_ADD_ROLE, PrivilegeHandler.PRIVILEGE_REMOVE_ROLE -> {
DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole);
DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole);
case PrivilegeHandler.PRIVILEGE_GET_ROLE, PrivilegeHandler.PRIVILEGE_ADD_ROLE, PrivilegeHandler.PRIVILEGE_REMOVE_ROLE -> {
DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole);
DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole);
String privilegeValue = newRole.getName();
return checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue, assertHasPrivilege);
}
case PrivilegeHandler.PRIVILEGE_MODIFY_ROLE -> {
DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldRole);
DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole);
return checkByAllowDenyValues(ctx, privilege, restrictable, newRole, assertHasPrivilege);
}
case PrivilegeHandler.PRIVILEGE_MODIFY_ROLE -> {
DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldRole);
DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole);
String privilegeValue = newRole.getName();
DBC.INTERIM.assertEquals("oldRole and newRole names must be the same", oldRole.getName(), privilegeValue);
DBC.INTERIM.assertEquals("oldRole and newRole names must be the same", oldRole, newRole);
return checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue, assertHasPrivilege);
}
default -> {
String msg = Restrictable.class.getName() + PrivilegeMessages.getString(
"Privilege.roleAccessPrivilege.unknownPrivilege");
msg = MessageFormat.format(msg, privilegeName);
throw new PrivilegeException(msg);
}
return checkByAllowDenyValues(ctx, privilege, restrictable, newRole, assertHasPrivilege);
}
default -> {
String msg = Restrictable.class.getName() + PrivilegeMessages.getString(
"Privilege.roleAccessPrivilege.unknownPrivilege");
msg = MessageFormat.format(msg, privilegeName);
throw new PrivilegeException(msg);
}
}
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.text.MessageFormat.format;
import static java.util.Collections.synchronizedMap;
import static li.strolch.execution.EventBasedExecutionHandler.PROP_LOCK_RETRIES;
@ -95,7 +96,9 @@ public class Controller {
public ExecutionPolicy refreshExecutionPolicy(StrolchTransaction tx, Action action) {
ExecutionPolicy executionPolicy = this.inExecution.computeIfAbsent(action.getLocator(), e -> {
Resource resource = tx.readLock(tx.getResourceFor(action, true));
return tx.getPolicy(resource, ExecutionPolicy.class);
ExecutionPolicy policy = tx.getPolicy(resource, ExecutionPolicy.class);
policy.initialize(action);
return policy;
});
// always update the TX and controller
@ -403,6 +406,8 @@ public class Controller {
return;
Action action = this.activity.getElementByLocator(actionLoc);
if (action.getState().isExecuted())
return;
// set this action to warning
internalToWarning(tx, action);
@ -425,6 +430,9 @@ public class Controller {
return;
Action action = this.activity.getElementByLocator(actionLoc);
if (action.getState().isExecuted())
return;
internalToWarning(tx, action);
}
@ -450,8 +458,9 @@ public class Controller {
} catch (StrolchLockException e) {
tries++;
if (tries >= this.lockRetries) {
logger.error("Failed to lock " + this.locator + ". Max retries " + tries +
" reached, throwing exception!");
logger.error(
format("Failed to lock {0}. Max retries {1} reached, throwing exception!", this.locator,
tries));
throw e;
}

View File

@ -23,9 +23,11 @@ public abstract class ActionExecutionCommand extends BasePlanningAndExecutionCom
}
protected ExecutionPolicy getExecutionPolicy(Action action) {
if (this.executionPolicy != null)
return this.executionPolicy;
return tx().getPolicy(action, ExecutionPolicy.class);
if (this.executionPolicy == null) {
this.executionPolicy = tx().getPolicy(action, ExecutionPolicy.class);
this.executionPolicy.initialize(action);
}
return this.executionPolicy;
}
@Override

View File

@ -97,7 +97,6 @@ public class PlanAndExecuteActivityCommand extends BasePlanningAndExecutionComma
executionPolicy = getExecutionPolicy(action);
}
executionPolicy.initialize(action);
if (!executionPolicy.isExecutable(action)) {
logger.info("Action " + action.getLocator() + " is not yet executable.");
return;
@ -195,16 +194,7 @@ public class PlanAndExecuteActivityCommand extends BasePlanningAndExecutionComma
}
// stop execution if at least one action is not executable from this entire tree
boolean anyActionNotExecutable = activity.streamActionsDeep().anyMatch(a -> {
if (!a.getState().canSetToExecution())
return false;
ExecutionPolicy executionPolicy = getExecutionPolicy(a);
executionPolicy.initialize(a);
boolean executable = executionPolicy.isExecutable(a);
if (!executable)
logger.info("Action " + a.getLocator() + " is not executable yet!");
return !executable;
});
boolean anyActionNotExecutable = activity.streamActionsDeep().anyMatch(this::isActionExecutable);
if (anyActionNotExecutable)
return;
}
@ -219,4 +209,13 @@ public class PlanAndExecuteActivityCommand extends BasePlanningAndExecutionComma
element.accept(this);
}
}
private boolean isActionExecutable(Action action) {
if (!action.getState().canSetToExecution())
return false;
boolean executable = getExecutionPolicy(action).isExecutable(action);
if (!executable)
logger.info("Action " + action.getLocator() + " is not yet executable!");
return !executable;
}
}

View File

@ -10,11 +10,12 @@ import li.strolch.model.timevalue.impl.FloatValue;
import li.strolch.model.timevalue.impl.ValueChange;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.utils.CheckedBiConsumer;
import li.strolch.utils.time.PeriodDuration;
import java.util.Locale;
import java.util.concurrent.ScheduledFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@ -39,8 +40,9 @@ public class SimpleExecution extends ExecutionPolicy {
protected void startWarningTask(PeriodDuration duration, Action action, Supplier<LogMessage> handler) {
if (this.warningTask != null) {
logger.warn("There is already a warning task registered, for action " + action.getLocator() +
". Cancelling and creating a new task...");
logger.warn("There is already a warning task registered, for action "
+ action.getLocator()
+ ". Cancelling and creating a new task...");
this.warningTask.cancel(true);
this.warningTask = null;
}
@ -150,18 +152,18 @@ public class SimpleExecution extends ExecutionPolicy {
return getContainer().getRealm(ctx.getCertificate()).openTx(ctx.getCertificate(), getClass(), readOnly);
}
protected void runWithFreshActionReadonly(BiConsumer<StrolchTransaction, Action> consumer,
Supplier<String> failMsgSupplier) {
runWithFreshAction(true, consumer, failMsgSupplier);
protected void runWithFreshActionReadonly(CheckedBiConsumer<StrolchTransaction, Action> consumer,
Consumer<Throwable> failHandler) {
runWithFreshAction(true, consumer, failHandler);
}
protected void runWithFreshActionWritable(BiConsumer<StrolchTransaction, Action> consumer,
Supplier<String> failMsgSupplier) {
runWithFreshAction(false, consumer, failMsgSupplier);
protected void runWithFreshActionWritable(CheckedBiConsumer<StrolchTransaction, Action> consumer,
Consumer<Throwable> failHandler) {
runWithFreshAction(false, consumer, failHandler);
}
private void runWithFreshAction(boolean readOnly, BiConsumer<StrolchTransaction, Action> consumer,
Supplier<String> failMsgSupplier) {
private void runWithFreshAction(boolean readOnly, CheckedBiConsumer<StrolchTransaction, Action> consumer,
Consumer<Throwable> failHandler) {
try {
runAsAgent(ctx -> {
try (StrolchTransaction tx = openLocalTx(ctx, readOnly)) {
@ -174,7 +176,7 @@ public class SimpleExecution extends ExecutionPolicy {
}
});
} catch (Exception e) {
logger.error(failMsgSupplier.get(), e);
failHandler.accept(e);
}
}
}

View File

@ -91,12 +91,12 @@ public class Report implements AutoCloseable {
return this.reportPolicy.doReportWithPage(offset, limit);
}
public MapOfSets<String, StrolchRootElement> generateFilterCriteria(int limit) {
public MapOfSets<String, JsonObject> generateFilterCriteria(int limit) {
return this.reportPolicy.generateFilterCriteria(limit);
}
public Stream<StrolchRootElement> generateFilterCriteria(String type) {
return this.reportPolicy.generateFilterCriteria(type);
public Stream<JsonObject> generateFilterCriteria(String type, int limit, String query) {
return this.reportPolicy.generateFilterCriteria(type,limit, query);
}
public long getCounter() {

View File

@ -1,17 +1,5 @@
package li.strolch.report.policy;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toList;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.report.ReportConstants.*;
import static li.strolch.utils.helper.StringHelper.EMPTY;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
import com.google.gson.JsonObject;
import li.strolch.model.*;
import li.strolch.model.parameter.AbstractParameter;
@ -33,6 +21,20 @@ import li.strolch.utils.collections.TypedTuple;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.iso8601.ISO8601;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
import static java.text.MessageFormat.format;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toList;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.report.ReportConstants.*;
import static li.strolch.utils.ObjectHelper.*;
import static li.strolch.utils.helper.StringHelper.EMPTY;
/**
* A Generic Report defines a report as is described at <a href="https://strolch.li/documentation-reports.html">Strolch
* Reports</a>
@ -88,9 +90,11 @@ public class GenericReport extends ReportPolicy {
this.columnsBag = this.reportRes.getParameterBag(BAG_COLUMNS, true);
this.columnIds = this.columnsBag.getParameters().stream() //
.sorted(comparingInt(Parameter::getIndex)) //
.map(StrolchElement::getId) //
this.columnIds = this.columnsBag
.getParameters()
.stream()
.sorted(comparingInt(Parameter::getIndex))
.map(StrolchElement::getId)
.collect(toList());
this.parallel = this.reportRes.getBoolean(PARAM_PARALLEL);
@ -167,36 +171,21 @@ public class GenericReport extends ReportPolicy {
List<ParameterBag> filterBags = this.reportRes.getParameterBagsByType(TYPE_FILTER);
for (ParameterBag filterBag : filterBags) {
if (filterBag.hasParameter(PARAM_FIELD_REF) && (filterBag.hasParameter(PARAM_FIELD_REF1)
|| filterBag.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException("Filter "
+ filterBag.getLocator()
+ " can not have combination of "
+ PARAM_FIELD_REF
+ " and any of "
+ PARAM_FIELD_REF1
+ ", "
+ PARAM_FIELD_REF2);
if (filterBag.hasParameter(PARAM_FIELD_REF) && (
filterBag.hasParameter(PARAM_FIELD_REF1) || filterBag.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException(
format("Filter {0} can not have combination of {1} and any of {2}, {3}", filterBag.getLocator(),
PARAM_FIELD_REF, PARAM_FIELD_REF1, PARAM_FIELD_REF2));
} else if ((filterBag.hasParameter(PARAM_FIELD_REF1) && !filterBag.hasParameter(PARAM_FIELD_REF2))
|| (!filterBag.hasParameter(PARAM_FIELD_REF1) && filterBag.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException("Filter "
+ filterBag.getLocator()
+ " must have both "
+ PARAM_FIELD_REF1
+ " and "
+ PARAM_FIELD_REF2);
} else if (!filterBag.hasParameter(PARAM_FIELD_REF) && (!filterBag.hasParameter(PARAM_FIELD_REF1)
|| !filterBag.hasParameter(
PARAM_FIELD_REF2))) {
throw new IllegalArgumentException("Filter "
+ filterBag.getLocator()
+ " is missing the "
+ PARAM_FIELD_REF
+ " or "
+ PARAM_FIELD_REF1
+ ", "
+ PARAM_FIELD_REF2
+ " combination!");
throw new IllegalArgumentException(
format("Filter {0} must have both {1} and {2}", filterBag.getLocator(), PARAM_FIELD_REF1,
PARAM_FIELD_REF2));
} else if (!filterBag.hasParameter(PARAM_FIELD_REF) && (
!filterBag.hasParameter(PARAM_FIELD_REF1) || !filterBag.hasParameter(PARAM_FIELD_REF2))) {
throw new IllegalArgumentException(
format("Filter {0} is missing the {1} or {2}, {3} combination!", filterBag.getLocator(),
PARAM_FIELD_REF, PARAM_FIELD_REF1, PARAM_FIELD_REF2));
}
// prepare filter function policy
@ -379,7 +368,7 @@ public class GenericReport extends ReportPolicy {
Stream<Map<String, StrolchRootElement>> stream;
// query the main objects and return a stream
stream = queryRows() //
stream = queryRows()
// transform each element into a map of Type,Value pairs
.map(this::evaluateRow);
@ -458,10 +447,9 @@ public class GenericReport extends ReportPolicy {
StringParameter joinParamP = additionalTypeBag.getStringP(PARAM_JOIN_PARAM);
String[] locatorParts = joinParamP.getValue().split(Locator.PATH_SEPARATOR);
if (locatorParts.length != 3)
throw new IllegalStateException("Parameter reference ("
+ joinParamP.getValue()
+ ") is invalid as it does not have 3 parts for "
+ joinParamP.getLocator());
throw new IllegalStateException(
format("Parameter reference ({0}) is invalid as it does not have 3 parts for {1}",
joinParamP.getValue(), joinParamP.getLocator()));
String bagKey = locatorParts[1];
String paramKey = locatorParts[2];
@ -476,19 +464,15 @@ public class GenericReport extends ReportPolicy {
StrolchRootElement joinElement = row.get(joinWithP.getUom());
if (joinElement == null)
throw new IllegalStateException("Additional join type "
+ joinWithP.getUom()
+ " is not available on row for "
+ joinWithP.getLocator());
throw new IllegalStateException(
format("Additional join type {0} is not available on row for {1}", joinWithP.getUom(),
joinWithP.getLocator()));
Optional<Parameter<?>> refP = lookupParameter(joinWithP, joinElement, false);
if (refP.isEmpty()) {
throw new IllegalStateException("Parameter reference ("
+ joinWithP.getValue()
+ ") for "
+ joinWithP.getLocator()
+ " not found on "
+ joinElement.getLocator());
throw new IllegalStateException(
format("Parameter reference ({0}) for {1} not found on {2}", joinWithP.getValue(),
joinWithP.getLocator(), joinElement.getLocator()));
}
StringParameter joinP = (StringParameter) refP.get();
@ -520,14 +504,12 @@ public class GenericReport extends ReportPolicy {
protected String formatColumn(Map<String, StrolchRootElement> row, String columnId) {
StringParameter columnDefP = this.columnsBag.getParameter(columnId, true);
Object value = evaluateColumnValue(columnDefP, row, false);
if (value instanceof ZonedDateTime) {
return ISO8601.toString((ZonedDateTime) value);
} else if (value instanceof Date) {
return ISO8601.toString((Date) value);
} else if (value instanceof Parameter) {
return formatColumn((Parameter<?>) value);
} else
return value.toString();
return switch (value) {
case ZonedDateTime zonedDateTime -> ISO8601.toString(zonedDateTime);
case Date date -> ISO8601.toString(date);
case Parameter<?> parameter -> formatColumn(parameter);
default -> value.toString();
};
}
protected String formatColumn(Parameter<?> param) {
@ -570,76 +552,29 @@ public class GenericReport extends ReportPolicy {
* @return the filter criteria as a map of sets
*/
@Override
public MapOfSets<String, StrolchRootElement> generateFilterCriteria(int limit) {
public MapOfSets<String, JsonObject> generateFilterCriteria(int limit) {
int maxFacetValues = getMaxFacetValues(limit);
if (limit <= 0 || limit >= MAX_FACET_VALUE_LIMIT) {
logger.warn("Overriding invalid limit " + limit + " with " + MAX_FACET_VALUE_LIMIT);
limit = 100;
}
int maxFacetValues;
int reportMaxFacetValues = this.reportRes.getInteger(PARAM_MAX_FACET_VALUES);
if (reportMaxFacetValues != 0 && reportMaxFacetValues != limit) {
logger.warn("Report "
+ this.reportRes.getId()
+ " has "
+ PARAM_MAX_FACET_VALUES
+ " defined as "
+ reportMaxFacetValues
+ ". Ignoring requested limit "
+ limit);
maxFacetValues = reportMaxFacetValues;
} else {
maxFacetValues = limit;
}
MapOfSets<String, StrolchRootElement> result = new MapOfSets<>(true);
MapOfSets<String, JsonObject> result = new MapOfSets<>(true);
// we need the list of possible element types, which designate the criteria
List<String> criteria = this.filterCriteriaParams.values().stream() //
.filter(p -> {
if (p.getUom().equals(UOM_NONE))
throw new IllegalStateException(
"Join UOM " + p.getUom() + " invalid: " + p.getId() + " for " + p.getLocator());
if (p.getId().equals(PARAM_OBJECT_TYPE))
return filterCriteriaAllowed(p.getUom());
return filterCriteriaAllowed(p.getId());
}) //
.sorted(comparing(StringParameter::getIndex)) //
.map(StringParameter::getUom) //
.collect(toList());
List<String> criteria = this.filterCriteriaParams.values().stream().filter(p -> {
if (p.getUom().equals(UOM_NONE))
throw new IllegalStateException(
format("Join UOM {0} invalid: {1} for {2}", p.getUom(), p.getId(), p.getLocator()));
if (p.getId().equals(PARAM_OBJECT_TYPE))
return filterCriteriaAllowed(p.getUom());
return filterCriteriaAllowed(p.getId());
}).sorted(comparing(StringParameter::getIndex)).map(StringParameter::getUom).collect(toList());
criteria.addAll(this.directCriteria);
int maxRowsForFacetGeneration = this.reportRes.getInteger(PARAM_MAX_ROWS_FOR_FACET_GENERATION);
if (!this.directCriteria.isEmpty()) {
criteria.forEach(type -> {
if (!this.directCriteria.contains(type))
return;
StringParameter filterCriteriaP = this.filterCriteriaParams.get(type);
if (filterCriteriaP == null) {
logger.warn("Filter criteria not found for " + type);
return;
}
Stream<? extends StrolchRootElement> stream = switch (filterCriteriaP.getInterpretation()) {
case INTERPRETATION_RESOURCE_REF -> tx().streamResources(filterCriteriaP.getUom());
case INTERPRETATION_ORDER_REF -> tx().streamOrders(filterCriteriaP.getUom());
case INTERPRETATION_ACTIVITY_REF -> tx().streamActivities(filterCriteriaP.getUom());
default -> throw new IllegalArgumentException("Unhandled filter criteria interpretation "
+ filterCriteriaP.getInterpretation()
+ " for "
+ filterCriteriaP.getLocator());
};
stream = stream.map(this::mapFilterCriteria).filter(this::filterDirectCriteria);
if (hasOrdering())
stream = stream.sorted(this::sortDirectCriteria);
if (maxFacetValues > 0)
stream = stream.limit(maxFacetValues);
stream.forEachOrdered(e -> result.addElement(e.getType(), e));
if (this.directCriteria.contains(type))
prepareStreamForDirectCriteria(type, maxFacetValues).forEachOrdered(
e -> result.addElement(e.getType(), mapCriteriaToJson(e)));
});
criteria.removeAll(this.directCriteria);
@ -656,7 +591,7 @@ public class GenericReport extends ReportPolicy {
for (String criterion : criteria) {
if (row.containsKey(criterion) && result.size(criterion) < maxFacetValues)
result.addElement(criterion, row.get(criterion));
result.addElement(criterion, mapCriteriaToJson(row.get(criterion)));
}
// stop if we have enough data
@ -671,13 +606,80 @@ public class GenericReport extends ReportPolicy {
return result;
}
protected StrolchRootElement mapFilterCriteria(StrolchRootElement element) {
return element;
@Override
public Stream<JsonObject> generateFilterCriteria(String type, int limit, String query) {
int maxFacetValues = getMaxFacetValues(limit);
Stream<StrolchRootElement> stream;
if (this.directCriteria.contains(type)) {
stream = prepareStreamForDirectCriteria(type, 0).map(e -> (StrolchRootElement) e);
} else {
stream = buildStream().filter(row -> row.containsKey(type)).map(row -> row.get(type)).distinct();
}
Stream<JsonObject> resultStream = filterAndMapFilterCriteria(stream, query);
if (maxFacetValues != 0)
resultStream = resultStream.limit(maxFacetValues);
return resultStream;
}
@Override
public Stream<StrolchRootElement> generateFilterCriteria(String type) {
return buildStream().filter(row -> row.containsKey(type)).map(row -> row.get(type)).distinct();
protected Stream<JsonObject> filterAndMapFilterCriteria(Stream<StrolchRootElement> stream, String query) {
Stream<JsonObject> resultStream = stream.map(this::mapCriteriaToJson);
String[] queryParts = query == null || query.isEmpty() ? null : query.split(" ");
if (queryParts != null)
resultStream = resultStream.filter(f -> contains(f.get(Tags.Json.NAME).getAsString(), queryParts, true));
return resultStream;
}
protected JsonObject mapCriteriaToJson(StrolchRootElement criterion) {
JsonObject result = new JsonObject();
result.addProperty(Tags.Json.ID, criterion.getId());
result.addProperty(Tags.Json.NAME, criterion.getName());
return result;
}
protected Stream<? extends StrolchRootElement> prepareStreamForDirectCriteria(String type, int maxFacetValues) {
StringParameter filterCriteriaP = this.filterCriteriaParams.get(type);
if (filterCriteriaP == null) {
logger.warn("Filter criteria not found for {}", type);
return Stream.empty();
}
Stream<? extends StrolchRootElement> stream = switch (filterCriteriaP.getInterpretation()) {
case INTERPRETATION_RESOURCE_REF -> tx().streamResources(filterCriteriaP.getUom());
case INTERPRETATION_ORDER_REF -> tx().streamOrders(filterCriteriaP.getUom());
case INTERPRETATION_ACTIVITY_REF -> tx().streamActivities(filterCriteriaP.getUom());
default -> throw new IllegalArgumentException(
format("Unhandled filter criteria interpretation {0} for {1}", filterCriteriaP.getInterpretation(),
filterCriteriaP.getLocator()));
};
stream = stream.filter(this::filterDirectCriteria);
if (hasOrdering())
stream = stream.sorted(this::sortDirectCriteria);
if (maxFacetValues > 0)
stream = stream.limit(maxFacetValues);
return stream;
}
private int getMaxFacetValues(int limit) {
if (limit <= 0 || limit >= MAX_FACET_VALUE_LIMIT) {
logger.warn("Overriding invalid limit {} with " + MAX_FACET_VALUE_LIMIT, limit);
limit = 100;
}
int maxFacetValues;
int reportMaxFacetValues = this.reportRes.getInteger(PARAM_MAX_FACET_VALUES);
if (reportMaxFacetValues != 0 && reportMaxFacetValues != limit) {
logger.warn("Report {} has " + PARAM_MAX_FACET_VALUES + " defined as {}. Ignoring requested limit {}",
this.reportRes.getId(), reportMaxFacetValues, limit);
maxFacetValues = reportMaxFacetValues;
} else {
maxFacetValues = limit;
}
return maxFacetValues;
}
/**
@ -705,31 +707,9 @@ public class GenericReport extends ReportPolicy {
int sortVal;
if (fieldRefP.getValue().startsWith("$")) {
Object columnValue1 = evaluateColumnValue(fieldRefP, Map.of(column1.getType(), column1), false);
Object columnValue2 = evaluateColumnValue(fieldRefP, Map.of(column2.getType(), column2), false);
if (this.descending) {
sortVal = ObjectHelper.compare(columnValue2, columnValue1, true);
} else {
sortVal = ObjectHelper.compare(columnValue1, columnValue2, true);
}
sortVal = compareFieldRef(fieldRefP, column1, column2);
} else {
Optional<Parameter<?>> param1 = lookupParameter(fieldRefP, column1, false);
Optional<Parameter<?>> param2 = lookupParameter(fieldRefP, column2, false);
if (param1.isEmpty() && param2.isEmpty())
continue;
if (param1.isPresent() && param2.isEmpty())
return 1;
else if (param1.isEmpty())
return -1;
if (this.descending)
sortVal = param2.get().compareTo(param1.get());
else
sortVal = param1.get().compareTo(param2.get());
sortVal = compareParamFieldRefP(fieldRefP, column1, column2);
}
if (sortVal != 0)
@ -765,31 +745,9 @@ public class GenericReport extends ReportPolicy {
int sortVal;
if (fieldRefP.getValue().startsWith("$")) {
Object columnValue1 = evaluateColumnValue(fieldRefP, row1, false);
Object columnValue2 = evaluateColumnValue(fieldRefP, row2, false);
if (this.descending) {
sortVal = ObjectHelper.compare(columnValue2, columnValue1, true);
} else {
sortVal = ObjectHelper.compare(columnValue1, columnValue2, true);
}
sortVal = compareFieldRef(fieldRefP, row1, row2);
} else {
Optional<Parameter<?>> param1 = lookupParameter(fieldRefP, column1, false);
Optional<Parameter<?>> param2 = lookupParameter(fieldRefP, column2, false);
if (param1.isEmpty() && param2.isEmpty())
continue;
if (param1.isPresent() && param2.isEmpty())
return 1;
else if (param1.isEmpty())
return -1;
if (this.descending)
sortVal = param2.get().compareTo(param1.get());
else
sortVal = param1.get().compareTo(param2.get());
sortVal = compareParamFieldRefP(fieldRefP, column1, column2);
}
if (sortVal != 0)
@ -799,6 +757,41 @@ public class GenericReport extends ReportPolicy {
return 0;
}
private int compareFieldRef(StringParameter fieldRefP, Map<String, StrolchRootElement> row1,
Map<String, StrolchRootElement> row2) {
Object columnValue1 = evaluateColumnValue(fieldRefP, row1, false);
Object columnValue2 = evaluateColumnValue(fieldRefP, row2, false);
if (this.descending)
return compare(columnValue2, columnValue1, true);
return compare(columnValue1, columnValue2, true);
}
private int compareFieldRef(StringParameter fieldRefP, StrolchRootElement column1, StrolchRootElement column2) {
Object columnValue1 = evaluateColumnValue(fieldRefP, Map.of(column1.getType(), column1), false);
Object columnValue2 = evaluateColumnValue(fieldRefP, Map.of(column2.getType(), column2), false);
if (this.descending)
return compare(columnValue2, columnValue1, true);
return compare(columnValue1, columnValue2, true);
}
private int compareParamFieldRefP(StringParameter fieldRefP, StrolchRootElement column1,
StrolchRootElement column2) {
Optional<Parameter<?>> param1 = lookupParameter(fieldRefP, column1, false);
Optional<Parameter<?>> param2 = lookupParameter(fieldRefP, column2, false);
if (param1.isEmpty() && param2.isEmpty())
return 0;
if (param1.isPresent() && param2.isEmpty())
return 1;
else if (param1.isEmpty())
return -1;
if (this.descending)
return param2.get().compareTo(param1.get());
return param1.get().compareTo(param2.get());
}
/**
* Returns true if a filter is defined, i.e. {@link ParameterBag ParameterBags} of type
* {@link ReportConstants#TYPE_FILTER}, a date range
@ -806,8 +799,8 @@ public class GenericReport extends ReportPolicy {
* @return true if a filter is defined
*/
protected boolean hasFilter() {
return !this.filtersByPolicy.isEmpty() || this.dateRange != null || (this.filtersById != null
&& !this.filtersById.isEmpty());
return !this.filtersByPolicy.isEmpty() || this.dateRange != null || (
this.filtersById != null && !this.filtersById.isEmpty());
}
protected boolean filterDirectCriteria(StrolchRootElement element) {
@ -893,7 +886,7 @@ public class GenericReport extends ReportPolicy {
Optional<Parameter<?>> param = lookupParameter(this.dateRangeSelP, element, false);
if (param.isEmpty() || param.get().getValueType() != StrolchValueType.DATE)
throw new IllegalStateException(
"Date Range selector is invalid, as referenced parameter is not a Date but " + (
format("Date Range selector is invalid, as referenced parameter is not a Date but {0}",
param.isPresent() ? param.get().getValueType() : "null"));
date = ((DateParameter) param.get()).getValueZdt();
@ -961,8 +954,8 @@ public class GenericReport extends ReportPolicy {
else
columnValue = parameter;
} else {
columnValue = lookupParameter(columnDefP, column, allowNull) //
.orElseGet(() -> allowNull ? null : new StringParameter(columnDefP.getValue(), columnDef, ""));
columnValue = lookupParameter(columnDefP, column, allowNull).orElseGet(
() -> allowNull ? null : new StringParameter(columnDefP.getValue(), columnDef, ""));
}
return columnValue;
@ -982,20 +975,18 @@ public class GenericReport extends ReportPolicy {
String[] searchParts = columnDef.split(SEARCH_SEPARATOR);
if (searchParts.length != 3)
throw new IllegalStateException("Parameter search reference ("
+ columnDef
+ ") is invalid as it does not have 3 parts for "
+ columnDefP.getLocator());
throw new IllegalStateException(
format("Parameter search reference ({0}) is invalid as it does not have 3 parts for {1}", columnDef,
columnDefP.getLocator()));
String parentParamId = searchParts[1];
String paramRef = searchParts[2];
String[] locatorParts = paramRef.split(Locator.PATH_SEPARATOR);
if (locatorParts.length != 3)
throw new IllegalStateException("Parameter search reference ("
+ paramRef
+ ") is invalid as it does not have 3 parts for "
+ columnDefP.getLocator());
throw new IllegalStateException(
format("Parameter search reference ({0}) is invalid as it does not have 3 parts for {1}", paramRef,
columnDefP.getLocator()));
String bagKey = locatorParts[1];
String paramKey = locatorParts[2];
@ -1018,22 +1009,18 @@ public class GenericReport extends ReportPolicy {
String[] locatorParts = paramRef.split(Locator.PATH_SEPARATOR);
if (locatorParts.length != 3)
throw new IllegalStateException("Parameter reference ("
+ paramRef
+ ") is invalid as it does not have 3 parts for "
+ paramRefP.getLocator());
throw new IllegalStateException(
format("Parameter reference ({0}) is invalid as it does not have 3 parts for {1}", paramRef,
paramRefP.getLocator()));
String bagKey = locatorParts[1];
String paramKey = locatorParts[2];
Parameter<?> param = element.getParameter(bagKey, paramKey);
if (!overrideAllowMissingColumns && !this.allowMissingColumns && param == null)
throw new IllegalStateException("Parameter reference ("
+ paramRef
+ ") for "
+ paramRefP.getLocator()
+ " not found on "
+ element.getLocator());
throw new IllegalStateException(
format("Parameter reference ({0}) for {1} not found on {2}", paramRef, paramRefP.getLocator(),
element.getLocator()));
return Optional.ofNullable(param);
}
@ -1058,15 +1045,17 @@ public class GenericReport extends ReportPolicy {
}
protected boolean hasJoinOnType(String type) {
return (this.reportRes.hasParameterBag(BAG_JOINS) //
&& this.reportRes.getParameterBag(BAG_JOINS).hasParameter(type)) //
|| (this.reportRes.hasParameterBag(BAG_ADDITIONAL_TYPE) //
&& this.reportRes
.getParameterBag(BAG_ADDITIONAL_TYPE)
.getString(PARAM_OBJECT_TYPE)
.equals(type)) //
|| (this.reportRes.hasParameterBag(BAG_ADDITIONAL_JOINS) //
&& this.reportRes.getParameterBag(BAG_ADDITIONAL_JOINS).hasParameter(type));
return (
this.reportRes.hasParameterBag(BAG_JOINS) && this.reportRes
.getParameterBag(BAG_JOINS)
.hasParameter(type)) || (
this.reportRes.hasParameterBag(BAG_ADDITIONAL_TYPE) && this.reportRes
.getParameterBag(BAG_ADDITIONAL_TYPE)
.getString(PARAM_OBJECT_TYPE)
.equals(type)) || (
this.reportRes.hasParameterBag(BAG_ADDITIONAL_JOINS) && this.reportRes
.getParameterBag(BAG_ADDITIONAL_JOINS)
.hasParameter(type));
}
protected Stream<? extends StrolchRootElement> getStreamFor(StringParameter objectTypeP) {
@ -1146,38 +1135,7 @@ public class GenericReport extends ReportPolicy {
return null;
}
ParameterBag relationsBag = dependency.getParameterBag(BAG_RELATIONS);
if (relationsBag == null)
throw new IllegalStateException("Invalid join definition value: "
+ joinP.getValue()
+ " on: "
+ joinP.getLocator()
+ " as "
+ dependency.getLocator()
+ " has no ParameterBag "
+ BAG_RELATIONS);
List<Parameter<?>> relationParams = relationsBag
.getParametersByInterpretationAndUom(interpretation, joinType)
.stream()
.filter(p -> p.getValueType() == StrolchValueType.STRING)
.toList();
if (relationParams.isEmpty())
throw new IllegalStateException("Found no relation parameters with UOM "
+ joinType
+ " of type "
+ StrolchValueType.STRING.getType()
+ " on dependency "
+ dependency.getLocator());
if (relationParams.size() > 1)
throw new IllegalStateException("Found multiple possible relation parameters for UOM "
+ joinType
+ " on dependency "
+ dependency.getLocator());
Parameter<?> relationParam = relationParams.get(0);
StringParameter relationP = (StringParameter) relationParam;
StringParameter relationP = getJoinRelationParam(dependency, joinP, joinType, interpretation);
if (relationP.getValue().isEmpty() && optional)
return null;
@ -1189,4 +1147,33 @@ public class GenericReport extends ReportPolicy {
refs.put(joinType, joinElem);
return joinElem;
}
private static StringParameter getJoinRelationParam(StrolchRootElement dependency, StringParameter joinP,
String joinType, String interpretation) {
ParameterBag relationsBag = dependency.getParameterBag(BAG_RELATIONS);
if (relationsBag == null)
throw new IllegalStateException(
format("Invalid join definition value: {0} on: {1} as {2} has no ParameterBag {3}",
joinP.getValue(), joinP.getLocator(), dependency.getLocator(), BAG_RELATIONS));
List<Parameter<?>> relationParams = relationsBag
.getParametersByInterpretationAndUom(interpretation, joinType)
.stream()
.filter(p -> p.getValueType() == StrolchValueType.STRING)
.toList();
if (relationParams.isEmpty())
throw new IllegalStateException(
format("Found no relation parameters with UOM {0} of type {1} on dependency {2}", joinType,
StrolchValueType.STRING.getType(), dependency.getLocator()));
if (relationParams.size() > 1)
throw new IllegalStateException("Found multiple possible relation parameters for UOM "
+ joinType
+ " on dependency "
+ dependency.getLocator());
Parameter<?> relationParam = relationParams.getFirst();
return (StringParameter) relationParam;
}
}

View File

@ -1,10 +1,5 @@
package li.strolch.report.policy;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import com.google.gson.JsonObject;
import li.strolch.model.Resource;
import li.strolch.model.StrolchRootElement;
@ -14,6 +9,11 @@ import li.strolch.report.ReportElement;
import li.strolch.utils.collections.DateRange;
import li.strolch.utils.collections.MapOfSets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public abstract class ReportPolicy extends StrolchPolicy {
public ReportPolicy(StrolchTransaction tx) {
@ -48,9 +48,9 @@ public abstract class ReportPolicy extends StrolchPolicy {
public abstract Stream<ReportElement> doReportWithPage(int offset, int limit);
public abstract MapOfSets<String, StrolchRootElement> generateFilterCriteria(int limit);
public abstract MapOfSets<String, JsonObject> generateFilterCriteria(int limit);
public abstract Stream<StrolchRootElement> generateFilterCriteria(String type);
public abstract Stream<JsonObject> generateFilterCriteria(String type, int limit, String query);
public abstract long getCounter();

View File

@ -0,0 +1,132 @@
package li.strolch.service.notifications;
import com.google.gson.JsonObject;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.model.ParameterBag;
import li.strolch.model.Resource;
import li.strolch.model.builder.ResourceBuilder;
import li.strolch.persistence.api.Operation;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.privilege.model.SimpleRestrictable;
import li.strolch.runtime.configuration.SupportedLanguage;
import li.strolch.service.JsonServiceArgument;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceResult;
import li.strolch.utils.collections.Tuple;
import li.strolch.utils.dbc.DBC;
import java.util.Set;
import java.util.stream.Collectors;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.privilege.handler.DefaultPrivilegeHandler.PRIVILEGE_GET_ROLE;
import static li.strolch.utils.iso8601.ISO8601.parseToZdt;
public class CreateNotificationService extends AbstractService<JsonServiceArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public JsonServiceArgument getArgumentInstance() {
return new JsonServiceArgument();
}
@Override
protected ServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
DBC.PRE.assertNotNull("JsonElement must be set", arg.jsonElement);
DBC.PRE.assertNotNull("JsonElement must be a JsonObject", arg.jsonElement.isJsonObject());
JsonObject jsonObject = arg.jsonElement.getAsJsonObject();
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
Resource notification = buildNotification(tx, jsonObject, getSupportedLanguages(getAgent()));
tx.add(notification);
tx.commitOnClose();
}
return ServiceResult.success();
}
protected static Resource buildNotification(StrolchTransaction tx, JsonObject jsonObject,
Set<String> supportedLanguages) {
Resource notification = newNotification();
PrivilegeContext ctx = tx.getPrivilegeContext();
JsonObject visibilityJ = jsonObject.get(BAG_VISIBILITY).getAsJsonObject();
ParameterBag visibility = notification.getParameterBag(BAG_VISIBILITY);
visibility.setBoolean(PARAM_ENABLED,
visibilityJ.has(PARAM_ENABLED) && visibilityJ.get(PARAM_ENABLED).getAsBoolean());
visibility.setBoolean(PARAM_FOR_ALL,
visibilityJ.has(PARAM_FOR_ALL) && visibilityJ.get(PARAM_FOR_ALL).getAsBoolean());
if (visibilityJ.has(PARAM_VISIBLE_FROM))
visibility.setDate(PARAM_VISIBLE_FROM, parseToZdt(visibilityJ.get(PARAM_VISIBLE_FROM).getAsString()));
if (visibilityJ.has(PARAM_VISIBLE_TO))
visibility.setDate(PARAM_VISIBLE_TO, parseToZdt(visibilityJ.get(PARAM_VISIBLE_TO).getAsString()));
if (visibilityJ.has(PARAM_ROLES)) {
String rolesJ = visibilityJ.get(PARAM_ROLES).getAsString();
visibility.getStringListP(PARAM_ROLES).setValueFromString(rolesJ);
for (String role : visibility.getStringList(PARAM_ROLES)) {
ctx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role)));
}
}
if (visibilityJ.has(PARAM_LOCATIONS)) {
String locationsJ = visibilityJ.get(PARAM_LOCATIONS).getAsString();
visibility.getStringListP(PARAM_LOCATIONS).setValueFromString(locationsJ);
for (String locationId : visibility.getStringList(PARAM_LOCATIONS)) {
tx.assertHasPrivilege(Operation.GET, tx.getResourceBy(TYPE_LOCATION, locationId, true));
}
}
for (String language : supportedLanguages) {
if (!jsonObject.has(language))
continue;
JsonObject languageJ = jsonObject.get(language).getAsJsonObject();
String title = languageJ.get(PARAM_TITLE).getAsString();
String text = languageJ.get(PARAM_TEXT).getAsString();
ParameterBag languageBag = new ParameterBag(language, language, TYPE_TEXT);
languageBag.setString(PARAM_TITLE, title);
languageBag.setString(PARAM_TEXT, text);
notification.addParameterBag(languageBag);
}
return notification;
}
public static Set<String> getSupportedLanguages(StrolchAgent agent) {
return agent
.getStrolchConfiguration()
.getRuntimeConfiguration()
.getSupportedLanguages()
.stream()
.map(SupportedLanguage::locale)
.collect(Collectors.toSet());
}
public static Resource newNotification() {
ResourceBuilder notificationBuilder = new ResourceBuilder(TYPE_NOTIFICATION, TYPE_NOTIFICATION)
.bag(BAG_VISIBILITY, TYPE_VISIBILITY)
.booleanB(PARAM_ENABLED).end()
.date(PARAM_VISIBLE_FROM).end()
.date(PARAM_VISIBLE_TO).end()
.booleanB(PARAM_FOR_ALL).end()
.stringList(PARAM_ROLES).end()
.stringList(PARAM_LOCATIONS).end()
.endBag();
return notificationBuilder.build();
}
}

View File

@ -0,0 +1,35 @@
package li.strolch.service.notifications;
import li.strolch.model.Resource;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.service.StringArgument;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceResult;
import li.strolch.utils.dbc.DBC;
import static li.strolch.model.StrolchModelConstants.TYPE_NOTIFICATION;
public class RemoveNotificationService extends AbstractService<StringArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public StringArgument getArgumentInstance() {
return new StringArgument();
}
@Override
protected ServiceResult internalDoService(StringArgument arg) throws Exception {
DBC.PRE.assertNotEmpty("value must be set", arg.value);
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
Resource notification = tx.getResourceBy(TYPE_NOTIFICATION, arg.value, true);
tx.remove(notification);
tx.commitOnClose();
}
return ServiceResult.success();
}
}

View File

@ -0,0 +1,47 @@
package li.strolch.service.notifications;
import com.google.gson.JsonObject;
import li.strolch.model.Resource;
import li.strolch.model.Tags;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.service.JsonServiceArgument;
import li.strolch.service.api.AbstractService;
import li.strolch.service.api.ServiceResult;
import li.strolch.utils.dbc.DBC;
import static li.strolch.service.notifications.CreateNotificationService.buildNotification;
import static li.strolch.service.notifications.CreateNotificationService.getSupportedLanguages;
public class UpdateNotificationService extends AbstractService<JsonServiceArgument, ServiceResult> {
@Override
protected ServiceResult getResultInstance() {
return new ServiceResult();
}
@Override
public JsonServiceArgument getArgumentInstance() {
return new JsonServiceArgument();
}
@Override
protected ServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
DBC.PRE.assertNotEmpty("objectId must be set", arg.objectId);
DBC.PRE.assertNotNull("JsonElement must be set", arg.jsonElement);
DBC.PRE.assertNotNull("JsonElement must be a JsonObject", arg.jsonElement.isJsonObject());
JsonObject jsonObject = arg.jsonElement.getAsJsonObject();
DBC.PRE.assertEquals("arg ID and jsonObject ID must be the same", arg.objectId,
jsonObject.get(Tags.Json.ID).getAsString());
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
Resource notification = buildNotification(tx, jsonObject, getSupportedLanguages(getAgent()));
notification.setId(arg.objectId);
tx.update(notification);
tx.commitOnClose();
}
return ServiceResult.success();
}
}

View File

@ -1,18 +1,7 @@
package li.strolch.report;
import static java.util.stream.Collectors.toSet;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.gson.JsonObject;
import li.strolch.model.StrolchElement;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.Tags;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.model.Certificate;
import li.strolch.testbase.runtime.RuntimeMock;
@ -23,6 +12,16 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.stream.Collectors.toSet;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
public class GenericReportTest {
private static final String RUNTIME_PATH = "target/GenericReportTest/";
@ -48,49 +47,49 @@ public class GenericReportTest {
public void shouldGenerateJsonReport() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
report.doReportAsJson() //
.forEach(e -> {
switch (e.get("slot").getAsString()) {
case "Slot 1" -> {
assertEquals("Product 01", e.get("product").getAsString());
assertEquals("20.0", e.get("quantity").getAsString());
assertEquals("40.0", e.get("maxQuantity").getAsString());
assertEquals("4.0", e.get("minQuantity").getAsString());
assertEquals("Section 001", e.get("section").getAsString());
assertEquals("Storage 01", e.get("storage").getAsString());
assertEquals("Location 01", e.get("location").getAsString());
}
case "Slot 2" -> {
assertEquals("Product 02", e.get("product").getAsString());
assertEquals("18.0", e.get("quantity").getAsString());
assertEquals("20.0", e.get("maxQuantity").getAsString());
assertEquals("4.0", e.get("minQuantity").getAsString());
assertEquals("Section 001", e.get("section").getAsString());
assertEquals("Storage 01", e.get("storage").getAsString());
assertEquals("Location 01", e.get("location").getAsString());
}
case "Slot 3" -> {
assertEquals("Product 01", e.get("product").getAsString());
assertEquals("11.0", e.get("quantity").getAsString());
assertEquals("40.0", e.get("maxQuantity").getAsString());
assertEquals("6.0", e.get("minQuantity").getAsString());
assertEquals("Section 002", e.get("section").getAsString());
assertEquals("Storage 02", e.get("storage").getAsString());
assertEquals("Location 02", e.get("location").getAsString());
}
case "Slot 4" -> {
assertEquals("Product 02", e.get("product").getAsString());
assertEquals("16.0", e.get("quantity").getAsString());
assertEquals("20.0", e.get("maxQuantity").getAsString());
assertEquals("6.0", e.get("minQuantity").getAsString());
assertEquals("Section 002", e.get("section").getAsString());
assertEquals("Storage 02", e.get("storage").getAsString());
assertEquals("Location 02", e.get("location").getAsString());
}
default -> fail("Unhandled result element: \n" + e);
case "Slot 1" -> {
assertEquals("Product 01", e.get("product").getAsString());
assertEquals("20.0", e.get("quantity").getAsString());
assertEquals("40.0", e.get("maxQuantity").getAsString());
assertEquals("4.0", e.get("minQuantity").getAsString());
assertEquals("Section 001", e.get("section").getAsString());
assertEquals("Storage 01", e.get("storage").getAsString());
assertEquals("Location 01", e.get("location").getAsString());
}
case "Slot 2" -> {
assertEquals("Product 02", e.get("product").getAsString());
assertEquals("18.0", e.get("quantity").getAsString());
assertEquals("20.0", e.get("maxQuantity").getAsString());
assertEquals("4.0", e.get("minQuantity").getAsString());
assertEquals("Section 001", e.get("section").getAsString());
assertEquals("Storage 01", e.get("storage").getAsString());
assertEquals("Location 01", e.get("location").getAsString());
}
case "Slot 3" -> {
assertEquals("Product 01", e.get("product").getAsString());
assertEquals("11.0", e.get("quantity").getAsString());
assertEquals("40.0", e.get("maxQuantity").getAsString());
assertEquals("6.0", e.get("minQuantity").getAsString());
assertEquals("Section 002", e.get("section").getAsString());
assertEquals("Storage 02", e.get("storage").getAsString());
assertEquals("Location 02", e.get("location").getAsString());
}
case "Slot 4" -> {
assertEquals("Product 02", e.get("product").getAsString());
assertEquals("16.0", e.get("quantity").getAsString());
assertEquals("20.0", e.get("maxQuantity").getAsString());
assertEquals("6.0", e.get("minQuantity").getAsString());
assertEquals("Section 002", e.get("section").getAsString());
assertEquals("Storage 02", e.get("storage").getAsString());
assertEquals("Location 02", e.get("location").getAsString());
}
default -> fail("Unhandled result element: \n" + e);
}
});
}
@ -100,7 +99,7 @@ public class GenericReportTest {
public void shouldFilterReport1() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
report.filter("Product", "product01") //
.doReportAsJson() //
@ -108,12 +107,12 @@ public class GenericReportTest {
String slotName = e.get("slot").getAsString();
switch (slotName) {
case "Slot 1":
case "Slot 3":
break;
default:
fail("Unexpected slot name " + slotName + ", should have been filtered!");
break;
case "Slot 1":
case "Slot 3":
break;
default:
fail("Unexpected slot name " + slotName + ", should have been filtered!");
break;
}
});
}
@ -123,7 +122,7 @@ public class GenericReportTest {
public void shouldFilterReport2() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
report.filter("Product", "product01") //
.filter("Location", "location02") //
@ -144,7 +143,7 @@ public class GenericReportTest {
Date from = new Date(LocalDate.of(2016, 1, 1).toEpochDay() * 86400000);
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
Date to = new Date(LocalDate.of(2017, 1, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
@ -158,7 +157,7 @@ public class GenericReportTest {
}
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
Date to = new Date(LocalDate.of(2017, 3, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
@ -185,7 +184,10 @@ public class GenericReportTest {
Date to = new Date(LocalDate.of(2017, 1, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
List<JsonObject> result = report.filter("Product", "product01").dateRange(dateRange).doReportAsJson()
List<JsonObject> result = report
.filter("Product", "product01")
.dateRange(dateRange)
.doReportAsJson()
.toList();
assertTrue(result.isEmpty());
}
@ -196,7 +198,10 @@ public class GenericReportTest {
Date to = new Date(LocalDate.of(2017, 3, 1).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
List<JsonObject> result = report.filter("Product", "product01").dateRange(dateRange).doReportAsJson()
List<JsonObject> result = report
.filter("Product", "product01")
.dateRange(dateRange)
.doReportAsJson()
.toList();
assertEquals(2, result.size());
}
@ -207,8 +212,11 @@ public class GenericReportTest {
Date to = new Date(LocalDate.of(2017, 3, 2).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
List<JsonObject> result = report.filter("Product", "product01", "product02").dateRange(dateRange)
.doReportAsJson().toList();
List<JsonObject> result = report
.filter("Product", "product01", "product02")
.dateRange(dateRange)
.doReportAsJson()
.toList();
assertEquals(4, result.size());
}
}
@ -218,21 +226,27 @@ public class GenericReportTest {
public void shouldCreateFilterCriteria() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
MapOfSets<String, StrolchRootElement> filterCriteria = report.generateFilterCriteria(1000);
MapOfSets<String, JsonObject> filterCriteria = report.generateFilterCriteria(1000);
assertFalse(filterCriteria.containsSet("Location"));
assertFalse(filterCriteria.containsSet("Slot"));
MatcherAssert
.assertThat(filterCriteria.getSet("Product").stream().map(StrolchElement::getId).collect(toSet()),
containsInAnyOrder("product01", "product02"));
MatcherAssert
.assertThat(filterCriteria.getSet("Storage").stream().map(StrolchElement::getId).collect(toSet()),
containsInAnyOrder("storage01", "storage02"));
MatcherAssert
.assertThat(filterCriteria.getSet("Section").stream().map(StrolchElement::getId).collect(toSet()),
containsInAnyOrder("section001", "section002"));
MatcherAssert.assertThat(filterCriteria
.getSet("Product")
.stream()
.map(e -> e.get(Tags.Json.ID).getAsString())
.collect(toSet()), containsInAnyOrder("product01", "product02"));
MatcherAssert.assertThat(filterCriteria
.getSet("Storage")
.stream()
.map(e -> e.get(Tags.Json.ID).getAsString())
.collect(toSet()), containsInAnyOrder("storage01", "storage02"));
MatcherAssert.assertThat(filterCriteria
.getSet("Section")
.stream()
.map(e -> e.get(Tags.Json.ID).getAsString())
.collect(toSet()), containsInAnyOrder("section001", "section002"));
}
}
@ -240,9 +254,10 @@ public class GenericReportTest {
public void shouldCreateFilterCriteriaFiltered1() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
MapOfSets<String, StrolchRootElement> filterCriteria = report.filter("Product", "product01")
MapOfSets<String, JsonObject> filterCriteria = report
.filter("Product", "product01")
.generateFilterCriteria(1000);
// assert sequence of filter criteria is correct
@ -254,15 +269,21 @@ public class GenericReportTest {
assertFalse(filterCriteria.containsSet("Location"));
assertFalse(filterCriteria.containsSet("Slot"));
MatcherAssert
.assertThat(filterCriteria.getSet("Product").stream().map(StrolchElement::getId).collect(toSet()),
containsInAnyOrder("product01"));
MatcherAssert
.assertThat(filterCriteria.getSet("Storage").stream().map(StrolchElement::getId).collect(toSet()),
containsInAnyOrder("storage01", "storage02"));
MatcherAssert
.assertThat(filterCriteria.getSet("Section").stream().map(StrolchElement::getId).collect(toSet()),
containsInAnyOrder("section001", "section002"));
MatcherAssert.assertThat(filterCriteria
.getSet("Product")
.stream()
.map(e -> e.get(Tags.Json.ID).getAsString())
.collect(toSet()), containsInAnyOrder("product01"));
MatcherAssert.assertThat(filterCriteria
.getSet("Storage")
.stream()
.map(e -> e.get(Tags.Json.ID).getAsString())
.collect(toSet()), containsInAnyOrder("storage01", "storage02"));
MatcherAssert.assertThat(filterCriteria
.getSet("Section")
.stream()
.map(e -> e.get(Tags.Json.ID).getAsString())
.collect(toSet()), containsInAnyOrder("section001", "section002"));
}
}
@ -270,13 +291,13 @@ public class GenericReportTest {
public void shouldCreateFilterCriteriaFiltered2() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "stockReport")) {
Report report = new Report(tx, "stockReport")) {
Date from = new Date(LocalDate.of(2017, 3, 1).toEpochDay() * 86400000);
Date to = new Date(LocalDate.of(2017, 3, 5).toEpochDay() * 86400000);
DateRange dateRange = new DateRange().from(from, true).to(to, false);
MapOfSets<String, StrolchRootElement> filterCriteria = report //
MapOfSets<String, JsonObject> filterCriteria = report //
.dateRange(dateRange) //
.filter("Product", "product01") //
.generateFilterCriteria(1000);
@ -289,26 +310,26 @@ public class GenericReportTest {
public void shouldDoAdditionalJoin() {
try (StrolchTransaction tx = runtimeMock.openUserTx(certificate, true);
Report report = new Report(tx, "slotsByOrderUsageReport")) {
Report report = new Report(tx, "slotsByOrderUsageReport")) {
AtomicInteger slotsFound = new AtomicInteger();
report.doReportAsJson().forEach(e -> {
switch (e.get("slot").getAsString()) {
case "Slot 1", "Slot 3" -> {
assertEquals("Harry", e.get("firstName").getAsString());
assertEquals("Barns", e.get("lastName").getAsString());
slotsFound.getAndIncrement();
}
case "Slot 2", "Slot 4", "Slot 5" -> {
assertEquals("Geoffrey", e.get("firstName").getAsString());
assertEquals("Bobcat", e.get("lastName").getAsString());
slotsFound.getAndIncrement();
}
case "Slot 6" -> {
assertEquals("", e.get("firstName").getAsString());
slotsFound.getAndIncrement();
}
case "Slot 1", "Slot 3" -> {
assertEquals("Harry", e.get("firstName").getAsString());
assertEquals("Barns", e.get("lastName").getAsString());
slotsFound.getAndIncrement();
}
case "Slot 2", "Slot 4", "Slot 5" -> {
assertEquals("Geoffrey", e.get("firstName").getAsString());
assertEquals("Bobcat", e.get("lastName").getAsString());
slotsFound.getAndIncrement();
}
case "Slot 6" -> {
assertEquals("", e.get("firstName").getAsString());
slotsFound.getAndIncrement();
}
}
});

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -104,8 +104,8 @@ public class I18nMessage {
} catch (MissingResourceException e) {
if (!missingKeysMap.containsSet(baseName + "_" + locale.toLanguageTag())) {
logger.error("Failed to find resource bundle " + baseName + " " + locale.toLanguageTag()
+ ", returning current bundle " + this.bundle.getLocale().toLanguageTag());
logger.error("Failed to find resource bundle {} {}, returning current bundle {}", baseName,
locale.toLanguageTag(), this.bundle.getLocale().toLanguageTag());
missingKeysMap.addSet(baseName + "_" + locale.toLanguageTag(), emptySet());
}
return this.bundle;
@ -123,10 +123,10 @@ public class I18nMessage {
if (isEmpty(this.bundleName))
return getMessage();
if (!missingKeysMap.containsSet(this.bundleName + "_" + locale.toLanguageTag())) {
logger.warn("No bundle found for " + this.bundleName + " " + locale + ". Available are: ");
logger.warn("No bundle found for {} {}. Available are: ", this.bundleName, locale);
getBundleMap().forEach((s, map) -> {
logger.info(" " + s);
map.forEach((l, resourceBundle) -> logger.info(" " + l + ": " + map.keySet()));
logger.info(" {}", s);
map.forEach((l, resourceBundle) -> logger.info(" {}: {}", l, map.keySet()));
});
missingKeysMap.addSet(this.bundleName + "_" + locale.toLanguageTag(), emptySet());
}
@ -192,7 +192,7 @@ public class I18nMessage {
String languageTag = bundle.getLocale().toLanguageTag();
String bundleKey = baseName + "_" + languageTag;
if (!missingKeysMap.containsElement(bundleKey, this.key)) {
logger.error("Key " + this.key + " is missing in bundle " + baseName + " for locale " + languageTag);
logger.error("Key {} is missing in bundle {} for locale {}", this.key, baseName, languageTag);
missingKeysMap.addElement(bundleKey, this.key);
}
@ -265,14 +265,14 @@ public class I18nMessage {
try {
CodeSource src = I18nMessage.class.getProtectionDomain().getCodeSource();
if (src == null) {
logger.error(
"Failed to find CodeSource for ProtectionDomain " + I18nMessage.class.getProtectionDomain());
logger.error("Failed to find CodeSource for ProtectionDomain {}",
I18nMessage.class.getProtectionDomain());
return;
}
File jarLocationF = new File(src.getLocation().toURI());
if (!(jarLocationF.exists() && jarLocationF.getParentFile().isDirectory())) {
logger.info("Found JAR repository at " + jarLocationF.getParentFile());
logger.info("Found JAR repository at {}", jarLocationF.getParentFile());
return;
}
@ -292,16 +292,10 @@ public class I18nMessage {
JarEntry je = entries.nextElement();
String entryName = je.getName();
if (entryName.startsWith("META-INF") //
|| entryName.equals("ENV.properties") //
|| entryName.equals("agentVersion.properties") //
|| entryName.equals("appVersion.properties") //
|| entryName.equals("componentVersion.properties") //
|| entryName.equals("strolch_db_version.properties"))
if (!entryName.endsWith(".properties"))
continue;
if (!entryName.endsWith(".properties"))
if (shouldIgnorePropertyFile(entryName))
continue;
TypedTuple<String, Locale> tuple = parsePropertyName(entryName);
@ -316,23 +310,21 @@ public class I18nMessage {
bundleMap.addElement(bundle.getBaseBundleName(), bundle.getLocale(), bundle);
String propertyName = entryName.replace('/', '.');
logger.info(
" Loaded bundle " + bundle.getBaseBundleName() + " " + bundle.getLocale() + " from "
+ propertyName + " from JAR " + file.getName());
logger.info(" Loaded bundle {} {} from {} from JAR {}", bundle.getBaseBundleName(),
bundle.getLocale(), propertyName, file.getName());
}
}
}
File classesD = new File(jarD.getParentFile(), "classes");
if (classesD.isDirectory()) {
File[] propertyFiles = classesD.listFiles(
(dir, name) -> name.endsWith(".properties") && !(name.equals("appVersion.properties")
|| name.equals("ENV.properties")));
File[] propertyFiles = classesD.listFiles((dir, name) -> name.endsWith(".properties") && !(
name.equals("appVersion.properties") || name.equals("ENV.properties")));
if (propertyFiles != null) {
for (File propertyFile : propertyFiles) {
logger.info(" Found property file " + propertyFile.getName() + " in classes "
+ classesD.getAbsolutePath());
logger.info(" Found property file {} in classes {}", propertyFile.getName(),
classesD.getAbsolutePath());
TypedTuple<String, Locale> tuple = parsePropertyName(propertyFile.getName());
if (tuple == null)
@ -346,8 +338,8 @@ public class I18nMessage {
}
bundleMap.addElement(bundle.getBaseBundleName(), bundle.getLocale(), bundle);
logger.info(" Loaded bundle " + bundle.getBaseBundleName() + " " + bundle.getLocale()
+ " from file " + propertyFile.getName());
logger.info(" Loaded bundle {} {} from file {}", bundle.getBaseBundleName(),
bundle.getLocale(), propertyFile.getName());
}
}
}
@ -378,17 +370,17 @@ public class I18nMessage {
int languageI = Arrays.binarySearch(Locale.getISOLanguages(), language);
int countryI = Arrays.binarySearch(Locale.getISOCountries(), country);
if (languageI >= 0 && countryI >= 0)
locale = new Locale(language, country);
locale = Locale.of(language, country);
else {
logger.warn("Ignoring bad bundle locale for " + entryName);
logger.warn("Ignoring malformed bad bundle locale for {}", entryName);
return null;
}
} else {
int languageI = Arrays.binarySearch(Locale.getISOLanguages(), localeS);
if (languageI >= 0)
locale = new Locale(localeS);
locale = Locale.forLanguageTag(localeS);
else {
logger.warn("Ignoring bad bundle locale for " + entryName);
logger.warn("Ignoring bad bundle locale for {}", entryName);
return null;
}
}
@ -401,6 +393,7 @@ public class I18nMessage {
}
private static class CustomControl extends ResourceBundle.Control {
private final InputStream stream;
public CustomControl(InputStream stream) {
@ -414,65 +407,76 @@ public class I18nMessage {
}
}
private static boolean shouldIgnorePropertyFile(String name) {
return name.startsWith("META-INF")
|| name.equals("ENV.properties")
|| name.equals("agentVersion.properties")
|| name.equals("appVersion.properties")
|| name.equals("componentVersion.properties")
|| name.contains("_db_version");
}
private static boolean shouldIgnoreFile(File file) {
return file.getName().contains("aopalliance") //
|| file.getName().contains("activation") //
|| file.getName().contains("antlr") //
|| file.getName().contains("assertj-core") //
|| file.getName().startsWith("com.sun") //
|| file.getName().startsWith("commonj.") //
|| file.getName().startsWith("commons-") //
|| file.getName().startsWith("jackson-") //
|| file.getName().startsWith("hapi-") //
|| file.getName().startsWith("jaxb-") //
|| file.getName().startsWith("org.hl7.") //
|| file.getName().startsWith("listenablefuture-") //
|| file.getName().startsWith("j2objc-annotations") //
|| file.getName().startsWith("failureaccess-") //
|| file.getName().startsWith("error_prone_") //
|| file.getName().startsWith("guava-") //
|| file.getName().startsWith("org.eclipse") //
|| file.getName().startsWith("javax") //
|| file.getName().startsWith("jaxws") //
|| file.getName().startsWith("jaxrs") //
|| file.getName().startsWith("jaxb") //
|| file.getName().contains("jsr305") //
|| file.getName().contains("c3p0") //
|| file.getName().contains("camel") //
|| file.getName().contains("checker-qual") //
|| file.getName().contains("cron") //
|| file.getName().contains("FastInfoset") //
|| file.getName().contains("gmbal") //
|| file.getName().contains("grizzly") //
|| file.getName().contains("gson") //
|| file.getName().contains("ha-api") //
|| file.getName().contains("HikariCP") //
|| file.getName().contains("hk2") //
|| file.getName().contains("icu4j") //
|| file.getName().contains("jakarta") //
|| file.getName().contains("javassist") //
|| file.getName().contains("jersey") //
|| file.getName().contains("joda-time") //
|| file.getName().contains("logback") //
|| file.getName().contains("management-api") //
|| file.getName().contains("mchange-commons-java") //
|| file.getName().contains("mimepull") //
|| file.getName().contains("org.abego.treelayout") //
|| file.getName().contains("osgi") //
|| file.getName().contains("pfl-basic") //
|| file.getName().contains("pfl-tf") //
|| file.getName().contains("policy-2.7.10") //
|| file.getName().contains("postgresql") //
|| file.getName().contains("quartz") //
|| file.getName().contains("saaj-impl") //
|| file.getName().contains("sax") //
|| file.getName().contains("slf4j") //
|| file.getName().contains("ST4") //
|| file.getName().contains("stax-ex") //
|| file.getName().contains("stax2-api") //
|| file.getName().contains("streambuffer") //
|| file.getName().contains("tyrus") //
|| file.getName().contains("validation-api") //
|| file.getName().contains("yasson");
String name = file.getName();
return name.contains("aopalliance")
|| name.contains("activation")
|| name.contains("antlr")
|| name.contains("assertj-core")
|| name.startsWith("com.sun")
|| name.startsWith("commonj.")
|| name.startsWith("commons-")
|| name.startsWith("jackson-")
|| name.startsWith("hapi-")
|| name.startsWith("jaxb-")
|| name.startsWith("org.hl7.")
|| name.startsWith("org.glassfish.")
|| name.startsWith("listenablefuture-")
|| name.startsWith("j2objc-annotations")
|| name.startsWith("failureaccess-")
|| name.startsWith("error_prone_")
|| name.startsWith("guava-")
|| name.startsWith("org.eclipse")
|| name.startsWith("javax")
|| name.startsWith("jaxws")
|| name.startsWith("jaxrs")
|| name.startsWith("jaxb")
|| name.contains("jsr305")
|| name.contains("c3p0")
|| name.contains("camel")
|| name.contains("checker-qual")
|| name.contains("cron")
|| name.contains("FastInfoset")
|| name.contains("gmbal")
|| name.contains("grizzly")
|| name.contains("gson")
|| name.contains("ha-api")
|| name.contains("HikariCP")
|| name.contains("hk2")
|| name.contains("icu4j")
|| name.contains("jakarta")
|| name.contains("javassist")
|| name.contains("jersey")
|| name.contains("joda-time")
|| name.contains("logback")
|| name.contains("management-api")
|| name.contains("mchange-commons-java")
|| name.contains("mimepull")
|| name.contains("org.abego.treelayout")
|| name.contains("osgi")
|| name.contains("pfl-basic")
|| name.contains("pfl-tf")
|| name.contains("policy-2.7.10")
|| name.contains("postgresql")
|| name.contains("quartz")
|| name.contains("saaj-impl")
|| name.contains("sax")
|| name.contains("slf4j")
|| name.contains("ST4")
|| name.contains("stax-ex")
|| name.contains("stax2-api")
|| name.contains("streambuffer")
|| name.contains("tyrus")
|| name.contains("validation-api")
|| name.contains("yasson");
}
}

View File

@ -104,6 +104,10 @@ public class ObjectHelper {
}
public static boolean contains(Object left, Object right, boolean ignoreCase) {
return contains(left, right, ignoreCase, true);
}
public static boolean contains(Object left, Object right, boolean ignoreCase, boolean matchAll) {
if (left == null && right == null)
return true;
if (left == null)
@ -116,7 +120,7 @@ public class ObjectHelper {
if (right instanceof Collection<?> rightCollection) {
for (Object l : leftCollection) {
for (Object r : rightCollection) {
if (contains(l, r, ignoreCase))
if (contains(l, r, ignoreCase, matchAll))
return true;
}
}
@ -127,7 +131,7 @@ public class ObjectHelper {
if (right instanceof String[] rightArr) {
for (Object l : leftCollection) {
for (Object r : rightArr) {
if (contains(l, r, ignoreCase))
if (contains(l, r, ignoreCase, matchAll))
return true;
}
}
@ -136,7 +140,7 @@ public class ObjectHelper {
}
for (Object l : leftCollection) {
if (contains(l, right, ignoreCase))
if (contains(l, right, ignoreCase, matchAll))
return true;
}
@ -150,18 +154,22 @@ public class ObjectHelper {
if (ignoreCase) {
leftString = leftString.toLowerCase();
for (String s : rightArr) {
if (leftString.contains(s.toLowerCase()))
if (!matchAll && leftString.contains(s.toLowerCase()))
return true;
else if (matchAll && !leftString.contains(s.toLowerCase()))
return false;
}
} else {
for (String s : rightArr) {
if (leftString.contains(s))
if (!matchAll && leftString.contains(s))
return true;
else if (matchAll && !leftString.contains(s))
return false;
}
}
return false;
return matchAll;
}
if (right.getClass().isEnum())
@ -224,7 +232,7 @@ public class ObjectHelper {
return false;
} else if (left instanceof String[] leftArr) {
} else if (left instanceof Object[] leftArr) {
for (Object r : collectionRight) {
for (Object l : leftArr) {
if (equals(r, l, ignoreCase))
@ -244,10 +252,35 @@ public class ObjectHelper {
}
}
if (right instanceof Object[] arr) {
for (Object o : arr) {
if (equals(left, o, ignoreCase))
return true;
if (right instanceof Object[] arrayRight) {
if (left instanceof Collection<?> collectionLeft) {
for (Object o : arrayRight) {
for (Object l : collectionLeft) {
if (equals(l, o, ignoreCase))
return true;
}
}
return false;
} else if (left instanceof Object[] leftArr) {
for (Object o : arrayRight) {
for (Object l : leftArr) {
if (equals(l, o, ignoreCase))
return true;
}
}
return false;
} else {
for (Object o : arrayRight) {
if (equals(left, o, ignoreCase))
return true;
}
}
return false;

View File

@ -40,7 +40,8 @@ public class ExceptionHelper {
public static String getCallerMethod(int depth) {
return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) //
.walk(frames -> frames.map((StackWalker.StackFrame sf) -> sf.getClassName() + "." + sf.getMethodName())
.walk(frames -> frames
.map((StackWalker.StackFrame sf) -> sf.getClassName() + "." + sf.getMethodName())
.skip(depth)
.findFirst()).orElse("UnknownClass.unknownMethod!");
}
@ -51,6 +52,17 @@ public class ExceptionHelper {
.orElse("UnknownClass.unknownMethod!");
}
/**
* Returns the message of the root cause of the given exception
*
* @param e the exception for which to get the root cause
*
* @return the message of the root cause of the given exception
*/
public static String getRootCauseExceptionMessage(Throwable e) {
return getExceptionMessage(getRootCause(e), false);
}
/**
* <p>
* Returns a message for the given {@link Throwable}
@ -61,8 +73,7 @@ public class ExceptionHelper {
* in such a case
* </p>
*
* @param t
* the {@link Throwable}
* @param t the {@link Throwable}
*
* @return the exception as string
*/
@ -80,11 +91,9 @@ public class ExceptionHelper {
* in such a case
* </p>
*
* @param t
* the {@link Throwable}
* @param withClassName
* if true, then exception class name is prepended to the exception message, if the exception message is null,
* then this param is ignored
* @param t the {@link Throwable}
* @param withClassName if true, then exception class name is prepended to the exception message, if the exception
* message is null, then this param is ignored
*
* @return the exception as string
*/
@ -104,8 +113,7 @@ public class ExceptionHelper {
* in such a case
* </p>
*
* @param t
* the {@link Throwable}
* @param t the {@link Throwable}
*
* @return the exception as string
*/
@ -123,15 +131,15 @@ public class ExceptionHelper {
* in such a case
* </p>
*
* @param t
* the {@link Throwable}
* @param withClassName
* if true, then exception class name is prepended to the exception message, if the exception message is null,
* then this param is ignored
* @param t the {@link Throwable}
* @param withClassName if true, then exception class name is prepended to the exception message, if the exception
* message is null, then this param is ignored
*
* @return the exception as string
*/
public static String getExceptionMessageWithCauses(Throwable t, boolean withClassName) {
if (t == null)
return "(null)";
if (t.getCause() == null)
return getExceptionMessage(t, withClassName);
@ -142,13 +150,12 @@ public class ExceptionHelper {
/**
* Formats the given {@link Throwable}'s stack trace to a string
*
* @param t
* the throwable for which the stack trace is to be formatted to string
* @param t the throwable for which the stack trace is to be formatted to string
*
* @return a string representation of the given {@link Throwable}'s stack trace
*/
public static String formatException(Throwable t) {
String ls = System.getProperty(PROP_LINE_SEPARATOR);
String ls = System.lineSeparator();
if (!ls.equals(UNIX_LINE_SEP))
System.setProperty(PROP_LINE_SEPARATOR, UNIX_LINE_SEP);
try {
@ -165,8 +172,7 @@ public class ExceptionHelper {
/**
* Formats the given {@link Throwable}'s message including causes to a string
*
* @param t
* the throwable for which the messages are to be formatted to a string
* @param t the throwable for which the messages are to be formatted to a string
*
* @return a string representation of the given {@link Throwable}'s messages including causes
*/
@ -177,11 +183,9 @@ public class ExceptionHelper {
/**
* Formats the given {@link Throwable}'s message including causes to a string
*
* @param t
* the throwable for which the messages are to be formatted to a string
* @param withClassName
* if true, then exception class name is prepended to the exception message, if the exception message is null, *
* then this param is ignored
* @param t the throwable for which the messages are to be formatted to a string
* @param withClassName if true, then exception class name is prepended to the exception message, if the exception
* message is null, * then this param is ignored
*
* @return a string representation of the given {@link Throwable}'s messages including causes
*/
@ -196,8 +200,7 @@ public class ExceptionHelper {
/**
* Returns the root cause for the given {@link Throwable}
*
* @param throwable
* the {@link Throwable} for which to get the root cause
* @param throwable the {@link Throwable} for which to get the root cause
*
* @return the root cause of the given {@link Throwable}
*/
@ -213,8 +216,7 @@ public class ExceptionHelper {
/**
* Returns {@link #getExceptionMessage(Throwable, boolean)} for the root cause of the given {@link Throwable}
*
* @param throwable
* the throwable for which to get the message of the root cause
* @param throwable the throwable for which to get the message of the root cause
*
* @return {@link #getExceptionMessage(Throwable, boolean)} for the root cause of the given {@link Throwable}
*/
@ -222,11 +224,23 @@ public class ExceptionHelper {
return getExceptionMessage(getRootCause(throwable), true);
}
/**
* Returns {@link #getExceptionMessage(Throwable, boolean)} for the root cause of the given {@link Throwable}
*
* @param throwable the throwable for which to get the message of the root cause
* @param withClassName if true, then exception class name is prepended to the exception message, if the exception
* message is null, then this param is ignored
*
* @return {@link #getExceptionMessage(Throwable, boolean)} for the root cause of the given {@link Throwable}
*/
public static String getRootCauseMessage(Throwable throwable, boolean withClassName) {
return getExceptionMessage(getRootCause(throwable), withClassName);
}
/**
* Walks the causes for the given {@link Throwable} and sees if the given cause exists
*
* @param throwable
* the {@link Throwable} for which to find the given cause type
* @param throwable the {@link Throwable} for which to find the given cause type
*
* @return true if the cause was found, false if not
*/

View File

@ -82,6 +82,7 @@ public class FileHelper {
* <li>{@link TempFileOptions#SEPARATE_DATE_SEGMENTS}</li>
* <li>{@link TempFileOptions#SEPARATE_HOURS}</li>
* <li>{@link TempFileOptions#WITH_HOURS}</li>
* <li>{@link TempFileOptions#MILLIS_FIRST}</li>
* </ul>
*
* @param tempPath the directory to which to write the file
@ -92,8 +93,14 @@ public class FileHelper {
* @return the temporary file
*/
public static File getTempFile(File tempPath, String prefix, String suffix, Set<TempFileOptions> options) {
return getTempFile(tempPath, prefix, suffix, options, LocalDateTime.now(), System.currentTimeMillis());
}
static File getTempFile(File tempPath, String prefix, String suffix, Set<TempFileOptions> options,
LocalDateTime dateTime, long timestamp) {
prefix = StringHelper.trimOrEmpty(prefix);
suffix = StringHelper.trimOrEmpty(suffix);
LocalDateTime dateTime = LocalDateTime.now();
LocalDate localDate = dateTime.toLocalDate();
boolean separateDateSegments = options.contains(SEPARATE_DATE_SEGMENTS);
@ -113,21 +120,22 @@ public class FileHelper {
throw new IllegalStateException("Failed to create path " + path.getAbsolutePath());
boolean appendMillis = options.contains(APPEND_MILLIS);
if (appendMillis)
prefix = (isEmpty(prefix) ? "" : prefix + "_");
else
prefix = (isEmpty(prefix) ? "" : prefix);
boolean millisFirst = appendMillis && options.contains(MILLIS_FIRST);
if (appendMillis) {
if (isEmpty(prefix)) {
prefix = String.valueOf(timestamp);
} else {
if (millisFirst)
prefix = timestamp + "_" + prefix;
else
prefix = prefix + "_" + timestamp;
}
suffix = (isEmpty(suffix) ? "" : suffix);
if (!suffix.startsWith(".") && appendMillis)
suffix = "_" + suffix;
String fileName;
if (appendMillis)
fileName = prefix + System.currentTimeMillis() + suffix;
else
fileName = prefix + suffix;
if (!suffix.startsWith("."))
prefix = prefix + "_";
}
String fileName = prefix + suffix;
return new File(path, fileName);
}

View File

@ -10,5 +10,6 @@ public enum TempFileOptions {
SEPARATE_DATE_SEGMENTS,
WITH_HOURS,
SEPARATE_HOURS,
APPEND_MILLIS
APPEND_MILLIS,
MILLIS_FIRST
}

View File

@ -97,7 +97,7 @@ public class SynchronizedMapOfListsTest {
Future<Boolean> task5 = this.executorService.submit(iterateTask);
run.set(true);
Thread.sleep(20L);
Thread.sleep(100L);
run.set(false);
Boolean result0 = task0.get();

View File

@ -6,14 +6,18 @@ import static li.strolch.utils.helper.TempFileOptions.*;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Set;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FileHelperTest {
private static final Logger logger = LoggerFactory.getLogger(FileHelperTest.class);
private static File tempPath;
@ -25,6 +29,8 @@ public class FileHelperTest {
@Test
public void shouldCreateTempFileNoOptions() {
File path = getTempFile(tempPath, "NoOptions", ".txt");
logger.info("Temp path: " + path.getAbsolutePath());
LocalDate today = LocalDate.now();
String expected = today + "/NoOptions.txt";
assertEquals(new File(tempPath, expected), path);
@ -32,35 +38,82 @@ public class FileHelperTest {
@Test
public void shouldCreateTempFileSeparateDateSegments() {
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", Set.of(SEPARATE_DATE_SEGMENTS));
Set<TempFileOptions> options = Set.of(SEPARATE_DATE_SEGMENTS);
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", options);
logger.info("Temp path: " + path.getAbsolutePath());
LocalDate today = LocalDate.now();
String expected =
today.getYear() + "/" + normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0') + "/"
+ normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0')
+ "/SeparateDateSegments.txt";
String month = normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0');
String day = normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0');
String expected = MessageFormat.format("{0,number,#}/{1}/{2}/SeparateDateSegments.txt", today.getYear(), month,
day);
assertEquals(new File(tempPath, expected), path);
}
@Test
public void shouldCreateTempFileSeparateDateSegments_WithHours() {
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", Set.of(SEPARATE_DATE_SEGMENTS, WITH_HOURS));
Set<TempFileOptions> options = Set.of(SEPARATE_DATE_SEGMENTS, WITH_HOURS);
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", options);
logger.info("Temp path: " + path.getAbsolutePath());
LocalDateTime today = LocalDateTime.now();
String expected =
today.getYear() + "/" + normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0') + "/"
+ normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0') + "_" + normalizeLength(
String.valueOf(today.getHour()), 2, true, '0') + "/SeparateDateSegments.txt";
String month = normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0');
String day = normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0');
String hour = normalizeLength(String.valueOf(today.getHour()), 2, true, '0');
String expected = MessageFormat.format("{0,number,#}/{1}/{2}_{3}/SeparateDateSegments.txt", today.getYear(),
month, day, hour);
assertEquals(new File(tempPath, expected), path);
}
@Test
public void shouldCreateTempFileSeparateDateSegments_WithHours_SeparateHours() {
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt",
Set.of(SEPARATE_DATE_SEGMENTS, WITH_HOURS, SEPARATE_HOURS));
Set<TempFileOptions> options = Set.of(SEPARATE_DATE_SEGMENTS, WITH_HOURS, SEPARATE_HOURS);
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", options);
logger.info("Temp path: " + path.getAbsolutePath());
LocalDateTime today = LocalDateTime.now();
String expected =
today.getYear() + "/" + normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0') + "/"
+ normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0') + "/" + normalizeLength(
String.valueOf(today.getHour()), 2, true, '0') + "/SeparateDateSegments.txt";
String month = normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0');
String day = normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0');
String hour = normalizeLength(String.valueOf(today.getHour()), 2, true, '0');
String expected = MessageFormat.format("{0,number,#}/{1}/{2}/{3}/SeparateDateSegments.txt", today.getYear(),
month, day, hour);
assertEquals(new File(tempPath, expected), path);
}
@Test
public void shouldCreateTempFileSeparateDateSegments_WithHours_SeparateHoursWithMillis() {
LocalDateTime today = LocalDateTime.now();
long timestamp = System.currentTimeMillis();
Set<TempFileOptions> options = Set.of(SEPARATE_DATE_SEGMENTS, WITH_HOURS, SEPARATE_HOURS, APPEND_MILLIS);
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", options, today, timestamp);
logger.info("Temp path: " + path.getAbsolutePath());
String month = normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0');
String day = normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0');
String hour = normalizeLength(String.valueOf(today.getHour()), 2, true, '0');
int year = today.getYear();
String expected = MessageFormat.format("{0,number,#}/{1}/{2}/{3}/SeparateDateSegments_{4,number,#}.txt", year,
month, day, hour, timestamp);
assertEquals(new File(tempPath, expected), path);
}
@Test
public void shouldCreateTempFileSeparateDateSegments_WithHours_SeparateHoursWithMillisFirst() {
LocalDateTime today = LocalDateTime.now();
long timestamp = System.currentTimeMillis();
Set<TempFileOptions> options = Set.of(SEPARATE_DATE_SEGMENTS, WITH_HOURS, SEPARATE_HOURS, APPEND_MILLIS,
MILLIS_FIRST);
File path = getTempFile(tempPath, "SeparateDateSegments", ".txt", options, today, timestamp);
logger.info("Temp path: " + path.getAbsolutePath());
String month = normalizeLength(String.valueOf(today.getMonthValue()), 2, true, '0');
String day = normalizeLength(String.valueOf(today.getDayOfMonth()), 2, true, '0');
String hour = normalizeLength(String.valueOf(today.getHour()), 2, true, '0');
int year = today.getYear();
String expected = MessageFormat.format("{0,number,#}/{1}/{2}/{3}/{4,number,#}_SeparateDateSegments.txt", year,
month, day, hour, timestamp);
assertEquals(new File(tempPath, expected), path);
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -204,14 +204,12 @@ public class RestfulStrolchComponent extends StrolchComponent {
@Override
public void start() throws Exception {
DBC.PRE.assertNull("Instance is already set! This component is a singleton resource!", instance);
instance = this;
super.start();
}
@Override
public void stop() throws Exception {
instance = null;
super.stop();
}

View File

@ -46,6 +46,7 @@ public class StrolchRestfulClasses {
restfulClasses.add(LanguagesResource.class);
restfulClasses.add(OperationsLogResource.class);
restfulClasses.add(AgentResource.class);
restfulClasses.add(NotificationResource.class);
// privilege
restfulClasses.add(PrivilegeUsersResource.class);
@ -55,6 +56,7 @@ public class StrolchRestfulClasses {
restfulClasses.add(AuditsResource.class);
Set<Class<?>> providerClasses = new HashSet<>();
providerClasses.add(LogRequestFilter.class);
providerClasses.add(StrolchRestfulExceptionMapper.class);
providerClasses.add(AccessControlResponseFilter.class);
providerClasses.add(AuthenticationRequestFilter.class);

View File

@ -27,6 +27,8 @@ public class StrolchRestfulConstants {
public static final String STROLCH_CERTIFICATE = "strolch.certificate";
public static final String STROLCH_REQUEST_SOURCE= "strolch.requestSource";
public static final String STROLCH_AUTHORIZATION = "strolch.authorization";
public static final String STROLCH_REMOTE_IP = "strolch.remote.ip";
public static final String STROLCH_REQUEST_URL = "strolch.request.location";
public static final String STROLCH_AUTHORIZATION_EXPIRATION_DATE = "strolch.authorization.expirationDate";
public static final String MSG = "msg";

View File

@ -21,15 +21,14 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import java.text.MessageFormat;
import li.strolch.exception.StrolchAccessDeniedException;
import li.strolch.exception.StrolchNotAuthenticatedException;
import li.strolch.rest.helper.ResponseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.MessageFormat;
@Provider
public class StrolchRestfulExceptionMapper implements ExceptionMapper<Exception> {
@ -40,17 +39,14 @@ public class StrolchRestfulExceptionMapper implements ExceptionMapper<Exception>
logger.error(MessageFormat.format("Handling exception {0}", ex.getClass()), ex);
if (ex instanceof NotFoundException)
return ResponseUtil.toResponse(Status.NOT_FOUND, ex);
if (ex instanceof StrolchNotAuthenticatedException e) {
logger.error("User tried to access resource, but was not authenticated: " + ex.getMessage());
return Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
}
if (ex instanceof StrolchAccessDeniedException e)
return ResponseUtil.toResponse(Status.FORBIDDEN, e.getI18n());
return ResponseUtil.toResponse(ex);
return switch (ex) {
case NotFoundException ignored -> ResponseUtil.toResponse(Status.NOT_FOUND, ex);
case StrolchAccessDeniedException e -> ResponseUtil.toResponse(Status.FORBIDDEN, e.getI18n());
case StrolchNotAuthenticatedException e -> {
logger.error("User tried to access resource, but was not authenticated: {}", ex.getMessage());
yield Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
}
default -> ResponseUtil.toResponse(ex);
};
}
}

View File

@ -24,8 +24,10 @@ import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import jakarta.ws.rs.core.Response.Status;
import li.strolch.exception.StrolchException;
import li.strolch.exception.StrolchNotAuthenticatedException;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.InvalidCredentialsException;
import li.strolch.privilege.base.NotAuthenticatedException;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.Privilege;
@ -49,7 +51,7 @@ import java.util.concurrent.TimeUnit;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_AUTHORIZATION;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_AUTHORIZATION_EXPIRATION_DATE;
import static li.strolch.rest.filters.AuthenticationRequestFilter.getRemoteIp;
import static li.strolch.rest.helper.RestfulHelper.getRemoteIp;
import static li.strolch.utils.helper.ExceptionHelper.getRootCause;
import static li.strolch.utils.helper.ExceptionHelper.hasCause;
@ -193,7 +195,10 @@ public class AuthenticationResource {
}
private static Response handleSessionException(String context, Exception e) {
logger.error(e.getMessage(), e);
if (e instanceof StrolchNotAuthenticatedException || e instanceof NotAuthenticatedException)
logger.error("Session exception: " + e.getMessage());
else
logger.error(e.getMessage(), e);
Throwable rootCause = getRootCause(e);
String msg = MessageFormat.format("{0}: {1}", context, rootCause);
return evaluateResponseByCause(e, msg);

View File

@ -13,8 +13,10 @@ import li.strolch.runtime.configuration.SupportedLanguage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
import java.util.Set;
import static java.util.Comparator.*;
import static li.strolch.utils.helper.ExceptionHelper.getRootCauseMessage;
@Path("strolch/languages")
@ -27,14 +29,20 @@ public class LanguagesResource {
@Produces(MediaType.APPLICATION_JSON)
public Response getSupportedLanguages() {
try {
Set<SupportedLanguage> supportedLanguages = RestfulStrolchComponent.getInstance().getAgent()
.getStrolchConfiguration().getRuntimeConfiguration().getSupportedLanguages();
JsonArray result = supportedLanguages.stream().map(language -> {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(Tags.Json.LOCALE, language.locale());
jsonObject.addProperty(Tags.Json.NAME, language.name());
return jsonObject;
}).collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
JsonArray result = RestfulStrolchComponent
.getInstance()
.getAgent()
.getRuntimeConfiguration()
.getSupportedLanguages()
.stream()
.sorted(comparing(SupportedLanguage::name))
.map(language -> {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(Tags.Json.LOCALE, language.locale());
jsonObject.addProperty(Tags.Json.NAME, language.name());
return jsonObject;
})
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
return Response.ok().entity(result.toString()).build();
} catch (Exception e) {

View File

@ -0,0 +1,231 @@
/*
* Copyright 2024 Robert von Burg <eitch@eitchnet.ch>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package li.strolch.rest.endpoint;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import li.strolch.agent.api.StrolchAgent;
import li.strolch.model.ParameterBag;
import li.strolch.model.Resource;
import li.strolch.model.Tags;
import li.strolch.model.json.StrolchRootElementToJsonVisitor;
import li.strolch.persistence.api.Operation;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.policy.notifications.NotificationsPolicy;
import li.strolch.privilege.model.Certificate;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.service.JsonServiceArgument;
import li.strolch.service.StringArgument;
import li.strolch.service.api.ServiceHandler;
import li.strolch.service.api.ServiceResult;
import li.strolch.service.notifications.CreateNotificationService;
import li.strolch.service.notifications.RemoveNotificationService;
import li.strolch.service.notifications.UpdateNotificationService;
import li.strolch.utils.helper.StringHelper;
import li.strolch.utils.iso8601.ISO8601;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import static java.util.Optional.ofNullable;
import static li.strolch.model.StrolchModelConstants.*;
import static li.strolch.rest.RestfulStrolchComponent.getInstance;
import static li.strolch.rest.StrolchRestfulConstants.DATA;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE;
import static li.strolch.rest.helper.ResponseUtil.toResponse;
import static li.strolch.runtime.StrolchConstants.DEFAULT_REALM;
import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.*;
import static li.strolch.utils.helper.ExceptionHelper.getCallerMethod;
import static li.strolch.utils.helper.ExceptionHelper.getCallerMethodNoClass;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@Path("strolch/notifications")
public class NotificationResource {
private static final Logger logger = LoggerFactory.getLogger(NotificationResource.class);
private StrolchTransaction openTx(Certificate certificate) {
String realm = certificate.getRealm();
if (StringHelper.isEmpty(realm))
realm = DEFAULT_REALM;
return RestfulStrolchComponent.getInstance().openTx(certificate, realm, getCallerMethod(2));
}
private static Certificate validateCertificate(HttpServletRequest request, String privilege) {
Certificate cert = (Certificate) request.getAttribute(STROLCH_CERTIFICATE);
RestfulStrolchComponent rest = RestfulStrolchComponent.getInstance();
rest.validate(cert).validateAction(privilege, getCallerMethodNoClass(2));
return cert;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getUserNotifications(@Context HttpServletRequest request) {
Certificate cert = validateCertificate(request, PRIVILEGE_GET_NOTIFICATIONS);
try (StrolchTransaction tx = openTx(cert)) {
List<Resource> notifications = NotificationsPolicy.getDefaultPolicy(tx).findUserNotifications();
Function<Resource, JsonObject> visitor = notificationToJson(tx.getAgent(), cert);
return toResponse(DATA, notifications.stream().map(visitor).filter(Objects::nonNull).toList());
} catch (Exception e) {
logger.error(e.getMessage(), e);
return toResponse(e);
}
}
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response getNotification(@Context HttpServletRequest request, @PathParam("id") String id) {
Certificate cert = validateCertificate(request, PRIVILEGE_GET_NOTIFICATION);
try (StrolchTransaction tx = openTx(cert)) {
Resource notification = tx.getResourceBy(TYPE_NOTIFICATION, id, true);
tx.assertHasPrivilege(Operation.GET, notification);
return toResponse(DATA, notificationToJson(tx.getAgent(), cert).apply(notification));
} catch (Exception e) {
logger.error(e.getMessage(), e);
return toResponse(e);
}
}
@GET
@Path("all")
@Produces(MediaType.APPLICATION_JSON)
public Response getAllNotifications(@Context HttpServletRequest request) {
Certificate cert = validateCertificate(request, PRIVILEGE_GET_NOTIFICATIONS_ALL);
try (StrolchTransaction tx = openTx(cert)) {
StrolchRootElementToJsonVisitor visitor = new StrolchRootElementToJsonVisitor()
.withoutPolicies()
.withoutStateVariables()
.flatBagsByType(TYPE_TEXT, TYPE_VISIBILITY)
.resourceHook((notification, notificationJ) -> addLocationNames(notification, notificationJ, tx));
return toResponse(DATA, tx.streamResources(TYPE_NOTIFICATION).map(a -> a.accept(visitor)).toList());
} catch (Exception e) {
logger.error(e.getMessage(), e);
return toResponse(e);
}
}
private static void addLocationNames(Resource notification, JsonObject notificationJ, StrolchTransaction tx) {
if (!notification.hasParameter(BAG_VISIBILITY, PARAM_LOCATIONS))
return;
JsonArray locationNamesJ = notification
.getStringList(BAG_VISIBILITY, PARAM_LOCATIONS)
.stream()
.map(locationId -> {
Resource location = tx.getResourceBy(TYPE_LOCATION, locationId);
return location == null ? locationId : location.getName();
})
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
notificationJ.get(BAG_VISIBILITY).getAsJsonObject().add(PARAM_LOCATION_NAMES, locationNamesJ);
}
private static Function<Resource, JsonObject> notificationToJson(StrolchAgent agent, Certificate cert) {
return notification -> {
JsonObject notificationJ = new JsonObject();
notificationJ.addProperty(Tags.Json.ID, notification.getId());
String lang = cert.getLocale().getLanguage();
Optional<ParameterBag> textBagO = ofNullable(notification.getParameterBag(lang))
.or(() -> ofNullable(notification.getParameterBag(agent.getLocale().getLanguage())))
.or(() -> notification.streamOfParameterBagsByType(TYPE_TEXT).findFirst());
if (textBagO.isEmpty())
return null;
ParameterBag textBag = textBagO.get();
notificationJ.addProperty(PARAM_TITLE, textBag.getString(PARAM_TITLE));
notificationJ.addProperty(PARAM_TEXT, textBag.getString(PARAM_TEXT));
notificationJ.addProperty(PARAM_VISIBLE_FROM,
ISO8601.toString(notification.getDate(BAG_VISIBILITY, PARAM_VISIBLE_FROM)));
notificationJ.addProperty(PARAM_VISIBLE_TO,
ISO8601.toString(notification.getDate(BAG_VISIBILITY, PARAM_VISIBLE_TO)));
notificationJ.addProperty(PARAM_ENABLED, notification.getBoolean(BAG_VISIBILITY, PARAM_ENABLED));
return notificationJ;
};
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(@Context HttpServletRequest request, String data) {
Certificate cert = (Certificate) request.getAttribute(STROLCH_CERTIFICATE);
// get JSON
JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
// create service and argument
CreateNotificationService svc = new CreateNotificationService();
ServiceHandler svcHandler = getInstance().getServiceHandler();
JsonServiceArgument arg = svc.getArgumentInstance();
arg.jsonElement = jsonObject;
// call service
ServiceResult result = svcHandler.doService(cert, svc, arg);
return toResponse(result);
}
@PUT
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response update(@Context HttpServletRequest request, @PathParam("id") String id, String data) {
Certificate cert = (Certificate) request.getAttribute(STROLCH_CERTIFICATE);
// get JSON
JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
// create service and argument
UpdateNotificationService svc = new UpdateNotificationService();
ServiceHandler svcHandler = getInstance().getServiceHandler();
JsonServiceArgument arg = svc.getArgumentInstance();
arg.objectId = id;
arg.jsonElement = jsonObject;
// call service
ServiceResult result = svcHandler.doService(cert, svc, arg);
return toResponse(result);
}
@DELETE
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response remove(@Context HttpServletRequest request, @PathParam("id") String id) {
Certificate cert = (Certificate) request.getAttribute(STROLCH_CERTIFICATE);
// create service and argument
RemoveNotificationService svc = new RemoveNotificationService();
ServiceHandler svcHandler = getInstance().getServiceHandler();
StringArgument arg = svc.getArgumentInstance();
arg.value = id;
// call service
ServiceResult result = svcHandler.doService(cert, svc, arg);
return toResponse(result);
}
}

View File

@ -75,13 +75,21 @@ public class ReportResource {
try (StrolchTransaction tx = getInstance().openTx(cert, realm, getContext())) {
StrolchRootElementToJsonVisitor visitor = new StrolchRootElementToJsonVisitor().flat().withoutVersion()
.withoutObjectType().withoutPolicies().withoutStateVariables()
.ignoreBags(BAG_JOINS, BAG_COLUMNS, BAG_ORDERING, BAG_ADDITIONAL_TYPE).ignoreBagByType(TYPE_FILTER)
StrolchRootElementToJsonVisitor visitor = new StrolchRootElementToJsonVisitor()
.flat()
.withoutVersion()
.withoutObjectType()
.withoutPolicies()
.withoutStateVariables()
.ignoreBags(BAG_JOINS, BAG_COLUMNS, BAG_ORDERING, BAG_ADDITIONAL_TYPE)
.ignoreBagByType(TYPE_FILTER)
.resourceHook((reportRes, reportJ) -> reportJ.addProperty(PARAM_DATE_RANGE,
reportRes.hasParameter(BAG_PARAMETERS, PARAM_DATE_RANGE_SEL)));
JsonArray result = new ReportSearch(tx).search(tx).orderByName(false)
.map(resource -> resource.accept(visitor)).asStream()
JsonArray result = new ReportSearch(tx)
.search(tx)
.orderByName(false)
.map(resource -> resource.accept(visitor))
.asStream()
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
return ResponseUtil.toResponse(DATA, result);
@ -103,7 +111,8 @@ public class ReportResource {
File localesF = new File(request.getServletContext().getRealPath(LOCALES_JSON));
JsonObject localeJ = null;
if (localesF.exists()) {
JsonObject localesJ = JsonParser.parseString(new String(Files.readAllBytes(localesF.toPath())))
JsonObject localesJ = JsonParser
.parseString(new String(Files.readAllBytes(localesF.toPath())))
.getAsJsonObject();
if (localesJ.has(cert.getLocale().toLanguageTag()))
localeJ = localesJ.get(cert.getLocale().toLanguageTag()).getAsJsonObject();
@ -124,21 +133,19 @@ public class ReportResource {
JsonArray facetsJ = new JsonArray();
JsonObject finalLocaleJ = localeJ;
MapOfSets<String, StrolchRootElement> criteria = report.generateFilterCriteria(limit);
MapOfSets<String, JsonObject> criteria = report.generateFilterCriteria(limit);
criteria.keySet().stream().sorted(comparing(type -> {
JsonElement translatedJ = finalLocaleJ == null ? null : finalLocaleJ.get(type);
return translatedJ == null ? type : translatedJ.getAsString();
})).forEach(type -> {
Set<StrolchRootElement> elements = criteria.getSet(type);
Set<JsonObject> elements = criteria.getSet(type);
JsonObject filter = new JsonObject();
filter.addProperty(Tags.Json.TYPE, type);
filter.add(Tags.Json.VALUES, elements.stream().sorted(comparing(StrolchElement::getName)).map(f -> {
JsonObject o = new JsonObject();
o.addProperty(Tags.Json.ID, f.getId());
o.addProperty(Tags.Json.NAME, f.getName());
return o;
}).collect(JsonArray::new, JsonArray::add, JsonArray::addAll));
filter.add(Tags.Json.VALUES, elements
.stream()
.sorted(comparing(e -> e.get(Tags.Json.NAME).getAsString()))
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll));
facetsJ.add(filter);
});
@ -148,7 +155,7 @@ public class ReportResource {
result.addProperty(PARAM_DURATION, duration);
result.addProperty(PARAM_PARALLEL, report.isParallel());
logger.info("Facet Generation for " + id + " took: " + duration);
logger.info("Facet Generation for {} took: {}", id, duration);
return ResponseUtil.toResponse(DATA, result);
}
}
@ -171,7 +178,8 @@ public class ReportResource {
File localesF = new File(request.getServletContext().getRealPath(LOCALES_JSON));
JsonObject localeJ = null;
if (localesF.exists()) {
JsonObject localesJ = JsonParser.parseString(new String(Files.readAllBytes(localesF.toPath())))
JsonObject localesJ = JsonParser
.parseString(new String(Files.readAllBytes(localesF.toPath())))
.getAsJsonObject();
if (localesJ.has(cert.getLocale().toLanguageTag()))
localeJ = localesJ.get(cert.getLocale().toLanguageTag()).getAsJsonObject();
@ -189,37 +197,13 @@ public class ReportResource {
report.getReportPolicy().setI18nData(localeJ);
// get filter criteria
Stream<StrolchRootElement> criteria = report.generateFilterCriteria(type);
if (query != null && !query.isEmpty()) {
String[] parts = query.split(" ");
criteria = criteria.filter(f -> ObjectHelper.contains(f.getName(), parts, true));
}
int maxFacetValues;
int reportMaxFacetValues = report.getReportResource().getInteger(PARAM_MAX_FACET_VALUES);
if (reportMaxFacetValues != 0 && reportMaxFacetValues != limit) {
logger.warn("Report " + report.getReportResource().getId() + " has " + PARAM_MAX_FACET_VALUES +
" defined as " + reportMaxFacetValues + ". Ignoring requested limit " + limit);
maxFacetValues = reportMaxFacetValues;
} else {
maxFacetValues = limit;
}
criteria = criteria.sorted(comparing(StrolchElement::getName));
if (maxFacetValues != 0)
criteria = criteria.limit(maxFacetValues);
// add the data finally
JsonArray array = criteria.map(f -> {
JsonObject o = new JsonObject();
o.addProperty(Tags.Json.ID, f.getId());
o.addProperty(Tags.Json.NAME, f.getName());
return o;
}).collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
JsonArray array = report
.generateFilterCriteria(type, limit, query)
.sorted(comparing(e -> e.get(Tags.Json.NAME).getAsString()))
.collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
String duration = formatNanoDuration(System.nanoTime() - start);
logger.info("Facet Generation for " + id + "." + type + " took: " + duration);
logger.info("Facet Generation for {}.{} took: {}", id, type, duration);
return ResponseUtil.toResponse(DATA, array);
}
}
@ -273,7 +257,8 @@ public class ReportResource {
File localesF = new File(request.getServletContext().getRealPath(LOCALES_JSON));
JsonObject localeJ = null;
if (localesF.exists()) {
JsonObject localesJ = JsonParser.parseString(new String(Files.readAllBytes(localesF.toPath())))
JsonObject localesJ = JsonParser
.parseString(new String(Files.readAllBytes(localesF.toPath())))
.getAsJsonObject();
if (localesJ.has(cert.getLocale().toLanguageTag()))
localeJ = localesJ.get(cert.getLocale().toLanguageTag()).getAsJsonObject();
@ -349,7 +334,7 @@ public class ReportResource {
finalResult.addProperty(PARAM_DURATION, duration);
finalResult.addProperty(PARAM_PARALLEL, report.isParallel());
logger.info(id + " Report took: " + duration);
logger.info("{} Report took: {}", id, duration);
return ResponseUtil.toResponse(DATA, finalResult);
}
}
@ -400,7 +385,8 @@ public class ReportResource {
File localesF = new File(request.getServletContext().getRealPath(LOCALES_JSON));
JsonObject localeJ = null;
if (localesF.exists()) {
JsonObject localesJ = JsonParser.parseString(new String(Files.readAllBytes(localesF.toPath())))
JsonObject localesJ = JsonParser
.parseString(new String(Files.readAllBytes(localesF.toPath())))
.getAsJsonObject();
if (localesJ.has(cert.getLocale().toLanguageTag()))
localeJ = localesJ.get(cert.getLocale().toLanguageTag()).getAsJsonObject();
@ -411,8 +397,10 @@ public class ReportResource {
// send
String fileName = id + "_" + System.currentTimeMillis() + ".csv";
return Response.ok(out, TEXT_CSV_TYPE)
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"").build();
return Response
.ok(out, TEXT_CSV_TYPE)
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
.build();
}
private StreamingOutput getOut(Certificate cert, String realm, String reportId, JsonObject localeJ,
@ -485,12 +473,14 @@ public class ReportResource {
// go through all filters and add to the map of sets
for (JsonElement elem : filters.getAsJsonArray()) {
if (!elem.isJsonObject()) {
logger.warn("There are wrong formatted filters:\n" + elem);
logger.warn("There are wrong formatted filters:\n{}", elem);
continue;
}
JsonObject filter = elem.getAsJsonObject();
filter.get(PARAM_FACET_FILTERS).getAsJsonArray()
filter
.get(PARAM_FACET_FILTERS)
.getAsJsonArray()
.forEach(f -> result.addElement(filter.get(PARAM_FACET_TYPE).getAsString(), f.getAsString()));
}

View File

@ -16,11 +16,13 @@
package li.strolch.rest.filters;
import jakarta.annotation.Priority;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.*;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import li.strolch.exception.StrolchAccessDeniedException;
import li.strolch.exception.StrolchNotAuthenticatedException;
@ -59,9 +61,6 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationRequestFilter.class);
@Context
private HttpServletRequest request;
private Set<String> unsecuredPaths;
protected RestfulStrolchComponent getRestful() {
@ -112,9 +111,8 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
String remoteIp = getRemoteIp(this.request);
logger.info("Remote IP: " + remoteIp + ": " + requestContext.getMethod() + " " +
requestContext.getUriInfo().getRequestUri());
String remoteIp = (String) requestContext.getProperty(STROLCH_REMOTE_IP);
try {
@ -126,19 +124,25 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
} catch (StrolchNotAuthenticatedException e) {
logger.error(e.getMessage());
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authenticated!").build());
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authenticated!")
.build());
} catch (StrolchAccessDeniedException e) {
logger.error(e.getMessage());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authorized!").build());
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authorized!")
.build());
} catch (Exception e) {
logger.error(e.getMessage());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User cannot access the resource.").build());
requestContext.abortWith(Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User cannot access the resource.")
.build());
}
}
@ -209,9 +213,11 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
if (isEmpty(sessionId)) {
logger.error(
"No Authorization header or cookie on request to URL " + requestContext.getUriInfo().getPath());
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Missing Authorization!").build());
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Missing Authorization!")
.build());
return null;
}
@ -223,9 +229,11 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
if (!getRestful().isBasicAuthEnabled()) {
logger.error("Basic Auth is not available for URL " + requestContext.getUriInfo().getPath());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Basic Auth not available").build());
requestContext.abortWith(Response
.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Basic Auth not available")
.build());
return null;
}
@ -233,9 +241,11 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
basicAuth = new String(Base64.getDecoder().decode(basicAuth.getBytes()), StandardCharsets.UTF_8);
String[] parts = basicAuth.split(":");
if (parts.length != 2) {
requestContext.abortWith(
Response.status(Response.Status.BAD_REQUEST).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Invalid Basic Authorization!").build());
requestContext.abortWith(Response
.status(Response.Status.BAD_REQUEST)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Invalid Basic Authorization!")
.build());
return null;
}
@ -256,10 +266,14 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
Certificate certificate = sessionHandler.validate(sessionId, remoteIp);
if (certificate.getUsage() == Usage.SET_PASSWORD) {
if (!requestContext.getUriInfo().getMatchedURIs()
if (!requestContext
.getUriInfo()
.getMatchedURIs()
.contains("strolch/privilege/users/" + certificate.getUsername() + "/password")) {
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN).entity("Can only set password!")
requestContext.abortWith(Response
.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Can only set password!")
.build());
return null;
}
@ -269,27 +283,4 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
requestContext.setProperty(STROLCH_REQUEST_SOURCE, remoteIp);
return certificate;
}
public static String getRemoteIp(HttpServletRequest request) {
if (request == null) {
logger.error("HttpServletRequest NOT AVAILABLE! Probably running in TEST!");
return "(null)";
}
String remoteHost = request.getRemoteHost();
String remoteAddr = request.getRemoteAddr();
StringBuilder sb = new StringBuilder();
if (remoteHost.equals(remoteAddr))
sb.append(remoteAddr);
else {
sb.append(remoteHost).append(": (").append(remoteAddr).append(")");
}
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (isNotEmpty(xForwardedFor))
sb.append(" (fwd)=> ").append(xForwardedFor);
return sb.toString();
}
}

View File

@ -0,0 +1,38 @@
package li.strolch.rest.filters;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.Context;
import li.strolch.rest.helper.RestfulHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_REMOTE_IP;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_REQUEST_URL;
import static li.strolch.rest.helper.ServletRequestHelper.logRequest;
@PreMatching
public class LogRequestFilter implements ContainerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(LogRequestFilter.class);
@Context
private HttpServletRequest request;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String remoteIp = RestfulHelper.getRemoteIp(this.request);
logger.info("Remote IP: {}: {} {}", remoteIp, requestContext.getMethod(),
requestContext.getUriInfo().getRequestUri());
this.request.setAttribute(STROLCH_REMOTE_IP, remoteIp);
this.request.setAttribute(STROLCH_REQUEST_URL,
requestContext.getMethod() + " " + requestContext.getUriInfo().getRequestUri());
logRequest(this.request);
}
}

View File

@ -0,0 +1,73 @@
package li.strolch.rest.helper;
import jakarta.ws.rs.core.Response;
import li.strolch.exception.StrolchAccessDeniedException;
import li.strolch.exception.StrolchNotAuthenticatedException;
import li.strolch.privilege.base.InvalidCredentialsException;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.Usage;
import li.strolch.runtime.sessions.StrolchSessionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
import static li.strolch.utils.helper.ExceptionHelper.getRootCause;
import static li.strolch.utils.helper.ExceptionHelper.getRootCauseExceptionMessage;
import static li.strolch.utils.helper.StringHelper.isEmpty;
public class BasicAuth {
private static final Logger logger = LoggerFactory.getLogger(BasicAuth.class);
private final StrolchSessionHandler sessionHandler;
public BasicAuth(StrolchSessionHandler sessionHandler) {
this.sessionHandler = sessionHandler;
}
public Certificate doBasicAuth(String authorization, String remoteIp) throws BasicAuthFailure {
if (isEmpty(authorization))
throw new BasicAuthFailure(Response.Status.UNAUTHORIZED, "No authentication!");
if (!authorization.startsWith("Basic "))
throw new BasicAuthFailure(Response.Status.BAD_REQUEST, "Invalid basic auth request!");
try {
String auth = new String(Base64.getDecoder().decode(authorization.substring(6)), UTF_8);
int splitIndex = auth.indexOf(':');
String username = auth.substring(0, splitIndex);
String password = auth.substring(splitIndex + 1);
return this.sessionHandler.authenticate(username, password.toCharArray(), remoteIp, Usage.SINGLE, false);
} catch (StrolchNotAuthenticatedException | InvalidCredentialsException e) {
logger.error(e.getMessage());
throw new BasicAuthFailure(Response.Status.UNAUTHORIZED, "Authentication failed", e);
} catch (StrolchAccessDeniedException e) {
logger.error(e.getMessage());
throw new BasicAuthFailure(Response.Status.FORBIDDEN, "User is not authorized!", e);
} catch (Exception e) {
Response.Status status;
String msg;
Throwable rootCause = getRootCause(e);
if (rootCause instanceof StrolchNotAuthenticatedException
|| rootCause instanceof InvalidCredentialsException) {
status = Response.Status.UNAUTHORIZED;
msg = "Authentication failed";
} else if (rootCause instanceof StrolchAccessDeniedException) {
status = Response.Status.FORBIDDEN;
msg = "User is not authorized!";
} else {
status = Response.Status.INTERNAL_SERVER_ERROR;
msg = "Internal error";
}
if (status == Response.Status.INTERNAL_SERVER_ERROR)
logger.error(e.getMessage(), e);
else
logger.error("Basic Auth failed: {}", getRootCauseExceptionMessage(e));
throw new BasicAuthFailure(status, msg, e);
}
}
}

View File

@ -0,0 +1,28 @@
package li.strolch.rest.helper;
import jakarta.ws.rs.core.Response;
public class BasicAuthFailure extends Exception {
private final Response.Status status;
private final String reason;
public BasicAuthFailure(Response.Status status, String reason) {
this.status = status;
this.reason = reason;
}
public BasicAuthFailure(Response.Status status, String reason, Exception cause) {
super(cause);
this.status = status;
this.reason = reason;
}
public Response.Status getStatus() {
return status;
}
public String getReason() {
return reason;
}
}

View File

@ -8,6 +8,7 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import li.strolch.exception.StrolchElementNotFoundException;
import li.strolch.exception.StrolchException;
import li.strolch.exception.StrolchNotAuthenticatedException;
import li.strolch.exception.StrolchUserMessageException;
import li.strolch.model.i18n.I18nMessageToJsonVisitor;
@ -139,34 +140,24 @@ public class ResponseUtil {
if (svcResult.isOk())
return Response.ok().entity(json).type(MediaType.APPLICATION_JSON).build();
Status status;
if (t instanceof AccessDeniedException) {
status = Status.FORBIDDEN;
} else if (t instanceof PrivilegeModelException) {
status = Status.INTERNAL_SERVER_ERROR;
} else if (t instanceof PrivilegeException) {
status = Status.UNAUTHORIZED;
} else if (t instanceof StrolchElementNotFoundException) {
status = Status.NOT_FOUND;
} else {
status = Status.INTERNAL_SERVER_ERROR;
}
Status status = switch (t) {
case AccessDeniedException ignored -> Status.FORBIDDEN;
case PrivilegeException ignored -> Status.UNAUTHORIZED;
case StrolchElementNotFoundException ignored -> Status.NOT_FOUND;
case null, default -> Status.INTERNAL_SERVER_ERROR;
};
return Response.status(status).entity(json).type(MediaType.APPLICATION_JSON).build();
}
public static Response toResponse(Throwable t) {
if (t instanceof StrolchNotAuthenticatedException)
return ResponseUtil.toResponse(Status.UNAUTHORIZED, t);
if (t instanceof AccessDeniedException)
return ResponseUtil.toResponse(Status.FORBIDDEN, t);
if (t instanceof StrolchElementNotFoundException)
return ResponseUtil.toResponse(Status.NOT_FOUND, t);
if (t instanceof PrivilegeModelException)
return ResponseUtil.toResponse(Status.INTERNAL_SERVER_ERROR, t);
if (t instanceof PrivilegeException)
return ResponseUtil.toResponse(Status.FORBIDDEN, t);
return toResponse(Status.INTERNAL_SERVER_ERROR, t);
return switch (t) {
case StrolchNotAuthenticatedException ignored -> toResponse(Status.UNAUTHORIZED, t);
case AccessDeniedException ignored -> toResponse(Status.FORBIDDEN, t);
case StrolchElementNotFoundException ignored -> toResponse(Status.NOT_FOUND, t);
case PrivilegeException ignored -> toResponse(Status.FORBIDDEN, t);
case null, default -> toResponse(Status.INTERNAL_SERVER_ERROR, t);
};
}
public static Response toResponse(Status status, String msg) {
@ -180,13 +171,13 @@ public class ResponseUtil {
public static Response toResponse(Status status, Throwable t) {
JsonObject response = new JsonObject();
if (t instanceof StrolchUserMessageException ex && ((StrolchUserMessageException) t).hasI18n()) {
response.add("i18n", ex.getI18n().accept(new I18nMessageToJsonVisitor()));
} else {
Throwable rootCause = getRootCause(t);
if (rootCause instanceof StrolchUserMessageException ex &&
((StrolchUserMessageException) rootCause).hasI18n()) {
response.add("i18n", ex.getI18n().accept(new I18nMessageToJsonVisitor()));
switch (t) {
case StrolchException ex when ex.hasI18n() ->
response.add("i18n", ex.getI18n().accept(new I18nMessageToJsonVisitor()));
case null, default -> {
Throwable rootCause = getRootCause(t);
if (rootCause instanceof StrolchUserMessageException ex && ex.hasI18n())
response.add("i18n", ex.getI18n().accept(new I18nMessageToJsonVisitor()));
}
}

View File

@ -15,17 +15,10 @@
*/
package li.strolch.rest.helper;
import static java.util.stream.Collectors.toList;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.core.HttpHeaders;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.stream.Stream;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.core.HttpHeaders;
import li.strolch.model.StrolchRootElement;
import li.strolch.model.visitor.StrolchRootElementVisitor;
import li.strolch.privilege.model.Certificate;
@ -36,16 +29,28 @@ import li.strolch.search.SearchResult;
import li.strolch.utils.collections.Paging;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import static li.strolch.utils.helper.StringHelper.isNotEmpty;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class RestfulHelper {
private static final Logger logger = LoggerFactory.getLogger(RestfulHelper.class);
public static Locale getLocale(HttpHeaders headers) {
if (headers == null || StringHelper.isEmpty(headers.getHeaderString(HttpHeaders.ACCEPT_LANGUAGE)))
return null;
return headers.getAcceptableLanguages().get(0);
return headers.getAcceptableLanguages().getFirst();
}
public static Certificate getCert(HttpServletRequest request) {
@ -105,4 +110,27 @@ public class RestfulHelper {
root.add("data", data);
return root;
}
public static String getRemoteIp(HttpServletRequest request) {
if (request == null) {
logger.error("HttpServletRequest NOT AVAILABLE! Probably running in TEST!");
return "(null)";
}
String remoteHost = request.getRemoteHost();
String remoteAddr = request.getRemoteAddr();
StringBuilder sb = new StringBuilder();
if (remoteHost.equals(remoteAddr))
sb.append(remoteAddr);
else {
sb.append(remoteHost).append(": (").append(remoteAddr).append(")");
}
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (isNotEmpty(xForwardedFor))
sb.append(" (fwd)=> ").append(xForwardedFor);
return sb.toString();
}
}

View File

@ -0,0 +1,111 @@
package li.strolch.rest.helper;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import li.strolch.rest.RestfulStrolchComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Enumeration;
import static li.strolch.utils.helper.StringHelper.*;
public class ServletRequestHelper {
private static final Logger logger = LoggerFactory.getLogger(ServletRequestHelper.class);
private static final int PADDING = 30;
private static final int PADDING_SHORT = 27;
public static void logRequest(HttpServletRequest request) {
if (!RestfulStrolchComponent.getInstance().isRestLogging())
return;
try {
StringBuilder sb = new StringBuilder();
sb
.append("\n")
.append(pad("REQUEST URL:"))
.append(request.getMethod())
.append(" ")
.append(request.getRequestURL())
.append("\n");
sb
.append(pad("REQUEST:"))
.append(request.getMethod())
.append(" ")
.append(request.getRequestURI())
.append("\n");
sb.append(pad("QUERY:")).append('?').append(string(request.getQueryString())).append("\n");
String from = request.getRemoteAddr();
sb.append(pad("REMOTE:")).append(from);
if (!from.equals(request.getRemoteHost()))
sb.append("/").append(request.getRemoteHost());
sb.append(":").append(request.getRemotePort()).append("\n");
Enumeration<String> headerNames = request.getHeaderNames();
if (!headerNames.hasMoreElements()) {
sb.append("HEADERS: (none)!\n");
} else {
sb.append("HEADERS: \n");
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
sb.append(" ").append(padShort(headerName)).append(" = ").append(headerValue).append("\n");
}
}
Cookie[] cookies = request.getCookies();
if (cookies == null) {
sb.append("COOKIES: (none)!\n");
} else {
sb.append("COOKIES: \n");
for (Cookie cookie : cookies) {
sb
.append(" ")
.append(padShort(cookie.getName()))
.append(" = ")
.append(cookie.getValue())
.append("\n");
}
}
sb.append(pad("AuthType:")).append(string(request.getAuthType())).append("\n");
sb.append(pad("User-Principal:")).append(string(request.getUserPrincipal())).append("\n");
sb.append(pad("Remote User:")).append(string(request.getRemoteUser())).append("\n");
sb.append(pad("Requested SessionID:")).append(string(request.getRequestedSessionId())).append("\n");
sb.append(pad("Protocol:")).append(string(request.getProtocol())).append("\n");
sb.append(pad("RequestId:")).append(string(request.getRequestId())).append("\n");
sb.append(pad("DispatcherType:")).append(string(request.getDispatcherType())).append("\n");
sb.append(pad("CharacterEncoding:")).append(string(request.getCharacterEncoding())).append("\n");
sb.append(pad("ContentType:")).append(string(request.getContentType())).append("\n");
sb.append(pad("ContentLength:")).append(request.getContentLengthLong()).append("\n");
logger.info(sb.toString());
} catch (Exception e) {
logger.error("Failed to log request", e);
}
}
private static String pad(String string) {
return normalizeLength(string, PADDING, false, ' ');
}
private static String padShort(String string) {
return normalizeLength(string, PADDING_SHORT, false, ' ');
}
private static String string(String string) {
return isEmpty(string) ? "(none)" : string;
}
private static String string(Enum<?> e) {
return e == null ? "(none)" : e.name();
}
private static String string(Object o) {
return o == null ? "(none)" : o.toString();
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -1,6 +1,6 @@
package li.strolch.websocket;
import static li.strolch.rest.filters.AuthenticationRequestFilter.getRemoteIp;
import static li.strolch.rest.helper.RestfulHelper.getRemoteIp;
import jakarta.servlet.annotation.WebFilter;

View File

@ -6,7 +6,7 @@
<parent>
<groupId>li.strolch</groupId>
<artifactId>strolch</artifactId>
<version>2.2.0-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>