My question is similar to "Displaying Jira issues using Issue Navigator" But my concern is that sometimes my list of issues is quite long and providing the user with a link to the Issue Navigator only works if my link is shorter than the max length of URLs.
Is there another way? Cookies? POST data? Perhaps programmatically creating and sharing a filter on the fly, and returning a link to the Issue Navigator that uses this filter? (But at some point I'd want to delete these filters so I don't have so many lying around.)
I do have the JQL query for getting the same list of issues, but it takes a very (very) long time to run and my code has already done the work of finding out what the result is -- I don't want the user to wait twice for the same thing (once while I'm generating my snazzy graphical servlet view and a second time when they want to see the same results in the Issue Navigator).
The easiest way to implement this is to ensure that the list of issues is cached somewhere within one of your application components. Each separate list of issues should be identified by its own unique ID (the magicKey below) that you define yourself.
You would then write your own JQL function that looks up your pre-calculated list of issues by that magic key, which then converts the list of issues into the format that the Issue Navigator requires.
The buildUriEncodedJqlQuery() method can be used to create such an example JQL query dynamically. For example, if the magic key is 1234, it would yield this JQL query: issue in myJqlFunction(1234).
You'd then feed the user a URL that looks like this:
String url = "/secure/IssueNavigator.jspa?mode=hide&reset=true&jqlQuery=" + MyJqlFunction.buildUriEncodedJqlQuery(magicKey);
The end result is that the user will be placed in the Issue Navigator looking at exactly the list of issues your code has provided.
This code also specifically prevents the user from saving your JQL function as part of a saved filter, since it's assumed that the issue cache in your application will not be permanent. If that's not correct, you will want to empty out the sanitiseOperand part.
In atlassian-plugins.xml:
<jql-function key="myJqlFunction" name="My JQL Function"
class="com.mycompany.MyJqlFunction">
</jql-function>
JQL function:
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.JiraDataType;
import com.atlassian.jira.JiraDataTypes;
import com.atlassian.jira.jql.operand.QueryLiteral;
import com.atlassian.jira.jql.query.QueryCreationContext;
import com.atlassian.jira.plugin.jql.function.ClauseSanitisingJqlFunction;
import com.atlassian.jira.plugin.jql.function.JqlFunction;
import com.atlassian.jira.plugin.jql.function.JqlFunctionModuleDescriptor;
import com.atlassian.jira.util.MessageSet;
import com.atlassian.jira.util.MessageSetImpl;
import com.atlassian.jira.util.NotNull;
import com.atlassian.query.clause.TerminalClause;
import com.atlassian.query.operand.FunctionOperand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MyJqlFunction implements JqlFunction, ClauseSanitisingJqlFunction
{
private static final String JQL_FUNCTION_NAME = "myJqlFunctionName";
private static final int JQL_FUNCTION_MIN_ARG_COUNT = 1;
private static final int JQL_FUNCTION_MAGIC_KEY_INDEX = 0;
public MyJqlFunction()
{
// inject your app's other components here
}
#Override
public void init(#NotNull JqlFunctionModuleDescriptor moduleDescriptor)
{
}
#Override
public JiraDataType getDataType()
{
return JiraDataTypes.ISSUE;
}
#Override
public String getFunctionName()
{
return JQL_FUNCTION_NAME;
}
#Override
public int getMinimumNumberOfExpectedArguments()
{
return JQL_FUNCTION_MIN_ARG_COUNT;
}
#Override
public boolean isList()
{
return true;
}
/**
* This function generates a URL-escaped JQL query that corresponds to the supplied magic key.
*
* #param magicKey
* #return
*/
public static String buildUriEncodedJqlQuery(String magicKey)
{
return "issue%20in%20" + JQL_FUNCTION_NAME + "(%22"
+ magicKey + "%22%)";
}
#Override
public List<QueryLiteral> getValues(#NotNull QueryCreationContext queryCreationContext,
#NotNull FunctionOperand operand,
#NotNull TerminalClause terminalClause)
{
User searchUser = queryCreationContext.getUser();
MessageSet messages = new MessageSetImpl();
List<QueryLiteral> values = internalGetValues(searchUser,
operand,
terminalClause,
messages,
!queryCreationContext.isSecurityOverriden());
return values;
}
private List<QueryLiteral> internalGetValues(#NotNull User searchUser,
#NotNull FunctionOperand operand,
#NotNull TerminalClause terminalClause,
#NotNull MessageSet messages,
#NotNull Boolean checkSecurity)
{
List<QueryLiteral> result = new ArrayList<QueryLiteral>();
if (searchUser==null)
{
// handle anon user
}
List<String> args = operand.getArgs();
if (wasSanitised(args))
{
messages.addErrorMessage("this function can't be used as part of a saved filter etc");
return result;
}
if (args.size() < getMinimumNumberOfExpectedArguments())
{
messages.addErrorMessage("too few arguments, etc");
return result;
}
final String magicKey = args.get(JQL_FUNCTION_MAGIC_KEY_INDEX);
// You need to implement this part yourself! This is where you use the supplied
// magicKey to fetch a list of issues from your own internal data source.
List<String> myIssueKeys = myCache.get(magicKey);
for (String id : myIssueKeys)
{
result.add(new QueryLiteral(operand, id));
}
return result;
}
#Override
public MessageSet validate(User searcher, #NotNull FunctionOperand operand, #NotNull TerminalClause terminalClause)
{
MessageSet messages = new MessageSetImpl();
internalGetValues(searcher, operand, terminalClause, messages, true);
return messages;
}
#Override
public FunctionOperand sanitiseOperand(User paramUser, #NotNull FunctionOperand operand)
{
// prevent the user from saving this as a filter, since the results are presumed to be dynamic
return new FunctionOperand(operand.getName(), Arrays.asList(""));
}
private boolean wasSanitised(List<String> args)
{
return (args.size() == 0 || args.get(JQL_FUNCTION_MAGIC_KEY_INDEX).isEmpty());
}
}
Related
I have a scenario where i need to use different mono which could return me errors and set map values to null if error is returned.
Ex:
Mono<A> a=Some api call;
Mono<A> b=Some api giving error;
Mono<A> c=Some api call;
Now i want to set the resulting response to map
Map<String,A> m=new HashMap<>();
m.put("a",a);
m.put("b",null);
m.put("c",c);
Can anyone help on how to do all this in reactive non blocking way.
I tried zip but it will not execute if any of the api return error or if i use onErrorReturn(null).
Thanks in advance
To solve your problems, you will have to use some tricks. The problem is that :
Giving an empty mono or mono that ends in error cancel zip operation (source: Mono#zip javadoc)
Reactive streams do not allow null values (source: Reactive stream spec, table 2: Subscribers, bullet 13)
Also, note that putting a null value in a hash map is the same as cancelling any previous value associated with the key (it's important in case you're updating an existing map).
Now, to bypass your problem, you can add an abstraction layer, and wrap your values in domain objects.
You can have an object that represents a query, another a valid result, and the last one will mirror an error.
With that, you can design publishers that will always succeed with non null values.
That's a technic used a lot in functional programming : common errors are part of the (one possible) result value.
Now, let's see the example that create a new Map from multiple Monos:
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Map;
public class BypassMonoError {
/**
* An object identified by a key. It serves to track which key to associate to computed values
* #param <K> Type of the key
*/
static class Identified<K> {
protected final K id;
Identified(K id) {
this.id = id;
}
public K getId() {
return id;
}
}
/**
* Describe the result value of an operation, along with the key associated to it.
*
* #param <K> Type of the identifier of the result
* #param <V> Value type
*/
static abstract class Result<K, V> extends Identified<K> {
Result(K id) {
super(id);
}
/**
*
* #return Computed value on success, or null if the operation has failed. Note that here, we cannot tell from
* a success returning a null value or an error
*/
abstract V getOrNull();
}
static final class Success<K, V> extends Result<K, V> {
private final V value;
Success(K id, V value) {
super(id);
this.value = value;
}
#Override
V getOrNull() {
return value;
}
}
static final class Error<K, V> extends Result<K, V> {
private final Exception error;
Error(K id, Exception error) {
super(id);
this.error = error;
}
#Override
V getOrNull() {
return null;
}
public Exception getError() {
return error;
}
}
/**
* A request that can asynchronously generate a result for the associated identifier.
*/
static class Query<K, V> extends Identified<K> {
private final Mono<V> worker;
Query(K id, Mono<V> worker) {
super(id);
this.worker = worker;
}
/**
* #return The operator that computes the result value. Note that any error is silently wrapped in an
* {#link Error empty result with error metadata}.
*/
public Mono<Result<K, V>> runCatching() {
return worker.<Result<K, V>>map(success -> new Success<>(id, success))
.onErrorResume(Exception.class, error -> Mono.just(new Error<K, V>(id, error)));
}
}
public static void main(String[] args) {
final Flux<Query<String, String>> queries = Flux.just(
new Query("a", Mono.just("A")),
new Query("b", Mono.error(new Exception("B"))),
new Query("c", Mono.delay(Duration.ofSeconds(1)).map(v -> "C"))
);
final Flux<Result<String, String>> results = queries.flatMap(query -> query.runCatching());
final Map<String, String> myMap = results.collectMap(Result::getId, Result::getOrNull)
.block();
for (Map.Entry<String, String> entry : myMap.entrySet()) {
System.out.printf("%s -> %s%n", entry.getKey(), entry.getValue());
}
}
}
Note : In the above example, we silently ignore any occurred error. However, when using the flux, you can test if a result is an error, and if it is, you are free to design your own error management (log, fail-first, send in another flux, etc.).
This outputs:
a -> A
b -> null
c -> C
I have not found how to save a general list of primitive types, e.g. ints, or strings, in a property of an entity. I might have missed something obvious...
https://github.com/JetBrains/xodus/wiki/Entity-Stores described that "only Java primitive types, Strings, and ComparableSet values can be used by default".
It seems not hard to convert an Iterable into a ComparableSet. However it is a Set.
I will take a look into PersistentEntityStore.registerCustomPropertyType() to see if that helps. I just feel wrong to do that to just save a list of integers.
Links seemed to be able to serve as a way of saving a list of Entitys. But it seems there is no addProperty() counterpart to addLink().
Appreciated if some one can share a way or a workaround for this, or maybe why this is not supported.
Thanks
As mentioned in the comments, one workaround I came up with was to create a ComparableList, by adopting code from ComparableSet.
The idea was to make a list that is able to convert to and from an ArrayByteIterable, and register it with .registerCustomPropertyType(). To do that, 2 classes are needed, ComparableList and ComparableListBinding. I'm sharing one iteration I used at the bottom. By the way, I made them immutable, comparing to the mutable ComparableSet. The newly implemented types should be registered once in a transaction of the store before using them.
That should allow you to store and retrieve a list. However the items in ComparableList would not get indexed as they would when saved in a ComparableSet -- there are some special treatment for ComparableSet in the entity store implementation. So without modifying the library, indexing would work only with hacks like creating another property to just index the values.
I was considering to implement a different entity store that could better support lists, on top of the xodus key-value store, bypassing the xodus entity store entirely. That might be a better solution to the list issue we are talking about here.
ComparableList:
#SuppressWarnings("unchecked")
public class ComparableList<T extends Comparable<T>> implements Comparable<ComparableList<T>>,
Iterable<T> {
#Nonnull
private final ImmutableList<T> list;
public ComparableList(#Nonnull final Iterable<T> iterable) {
list = ImmutableList.copyOf(iterable);
}
#Override
public int compareTo(#Nonnull final ComparableList<T> other) {
final Iterator<T> thisIt = list.iterator();
final Iterator<T> otherIt = other.list.iterator();
while (thisIt.hasNext() && otherIt.hasNext()) {
final int cmp = thisIt.next().compareTo(otherIt.next());
if (cmp != 0) {
return cmp;
}
}
if (thisIt.hasNext()) {
return 1;
}
if (otherIt.hasNext()) {
return -1;
}
return 0;
}
#NotNull
#Override
public Iterator<T> iterator() {
return list.iterator();
}
#Nullable
public Class<T> getItemClass() {
final Iterator<T> it = list.iterator();
return it.hasNext() ? (Class<T>) it.next().getClass() : null;
}
#Override
public String toString() {
return list.toString();
}
}
ComparableListBinding:
#SuppressWarnings({"unchecked", "rawtypes"})
public class ComparableListBinding extends ComparableBinding {
public static final ComparableListBinding INSTANCE = new ComparableListBinding();
private ComparableListBinding() {}
#Override
public ComparableList readObject(#NotNull final ByteArrayInputStream stream) {
final int valueTypeId = stream.read() ^ 0x80;
final ComparableBinding itemBinding = ComparableValueType.getPredefinedBinding(valueTypeId);
final ImmutableList.Builder<Comparable> builder = ImmutableList.builder();
while (stream.available() > 0) {
builder.add(itemBinding.readObject(stream));
}
return new ComparableList(builder.build());
}
#Override
public void writeObject(#NotNull final LightOutputStream output,
#NotNull final Comparable object) {
final ComparableList<? extends Comparable> list = (ComparableList) object;
final Class itemClass = list.getItemClass();
if (itemClass == null) {
throw new ExodusException("Attempt to write empty ComparableList");
}
final ComparableValueType type = ComparableValueType.getPredefinedType(itemClass);
output.writeByte(type.getTypeId());
final ComparableBinding itemBinding = type.getBinding();
list.forEach(o -> itemBinding.writeObject(output, o));
}
/**
* De-serializes {#linkplain ByteIterable} entry to a {#code ComparableList} value.
*
* #param entry {#linkplain ByteIterable} instance
* #return de-serialized value
*/
public static ComparableList entryToComparableList(#NotNull final ByteIterable entry) {
return (ComparableList) INSTANCE.entryToObject(entry);
}
/**
* Serializes {#code ComparableList} value to the {#linkplain ArrayByteIterable} entry.
*
* #param object value to serialize
* #return {#linkplain ArrayByteIterable} entry
*/
public static ArrayByteIterable comparableSetToEntry(#NotNull final ComparableList object) {
return INSTANCE.objectToEntry(object);
}
}
Let's consider we have the following informations :
As you see an article can be stored in many stores, and vice versa : a store can store many articles : that's the class model (UML )
some code :
FXML Part :
#FXML
private TableView<Article> tblArticles;
#FXML
private TableColumn<Article, String> colStore;
#FXML
private TableColumn<Article, Integer> colQuantity;
getters and setter :
colStore.setCellValueFactory(new PropertyValueFactory<>("store"));
colStore.setCellValueFactory(new PropertyValueFactory<>("quantity"));
I recieve the result seen in the first table but I am not able to do what is in the second table .
And what I want exactly should give the following informations :
So my question is it possible to do this in a TableView ?
Here is a sample app. It follows an MVVM style, which is appropriate for this kind of work. The app was built using Java 13 and will not work in earlier Java versions such as Java 8. It's a relatively long answer, but, ah well, sometimes that is what it takes.
The overall approach is not to create a tableview row for each store that an article is stored in. Instead, we just create a row for each article and we have a custom cell renderer which produces a single formatted cell for all the stores and quantities that that item is stored at.
Now, you could do an alternative implementation based upon a custom rowFactory. However, I do not recommend that approach for this particular task, as I believe it would be unnecessarily complicated to implement and maintain, without providing sufficient value.
Another way to do this is to use nested columns. This approach, when appropriate care is taken, does allow you to create a tableview row for each store that an article is stored in. If you do this, you need some way of populating different data depending on whether a row is either the first row in the group or not. You don't allow the user to reorder and sort data in the table, as that would be quite difficult to cater for because the notion of what is the "first row in the group" would be forever changing. To allow for appropriate rendering with nested columns, you end up with a slightly different view model (the FlatLineItem class below and the accompanying method in the LineItemService that retrieves them).
The image below demonstrates the output of a TableView with a custom cell renderer on the left and a TableView using nested columns on the right. Note how the selection works differently in each case. On the left when a row is selected, it includes all the stores that attached to that row. On the right when the nested columns are used, the row selection is only selecting a row for a given store.
Main application class
This sets up a couple of TableViews.
For the first table view, all it does is create a TableView with a column for each of the elements to be displayed. All the data is extracted from a LineItem view model class using a standard PropertyValueFactory. The slightly different thing is a custom cell renderer for a StoredQuantity field via the StoredQuantityTableCell, this is explained later.
The second view uses nested columns and works based upon the FlatLineItem view model class, also using a standard PropertyValueFactory and uses no custom cell renderer.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.List;
public class AggregateViewApp extends Application {
#Override
public void start(Stage stage) throws Exception {
LineItemService lineItemService = new LineItemService();
TableView<LineItem> tableView = createArticleTableView();
tableView.getItems().setAll(lineItemService.fetchAllLineItems());
TableView<FlatLineItem> nestedTableView = createNestedArticleTableView();
nestedTableView.getItems().setAll(lineItemService.fetchAllFlatLineItems());
HBox layout = new HBox(
40,
tableView,
nestedTableView
);
stage.setScene(new Scene(layout));
stage.show();
}
#SuppressWarnings("unchecked")
private TableView<LineItem> createArticleTableView() {
TableView tableView = new TableView();
TableColumn<LineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
TableColumn<LineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
TableColumn<LineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.setCellValueFactory(new PropertyValueFactory<>("storedQuantities"));
storedArticleCol.setCellFactory(lineItemStringTableColumn -> new StoredQuantityTableCell());
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
tableView.getColumns().addAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 150);
return tableView;
}
#SuppressWarnings("unchecked")
private TableView<FlatLineItem> createNestedArticleTableView() {
TableView tableView = new TableView();
TableColumn<FlatLineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
articleIdCol.setSortable(false);
TableColumn<FlatLineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
nameCol.setSortable(false);
TableColumn<FlatLineItem, String> storeCol = new TableColumn<>("Store");
storeCol.setCellValueFactory(new PropertyValueFactory<>("storeName"));
storeCol.setSortable(false);
TableColumn<FlatLineItem, String> storeQuantityCol = new TableColumn<>("Quantity");
storeQuantityCol.setCellValueFactory(new PropertyValueFactory<>("storeQuantity"));
storeQuantityCol.setSortable(false);
TableColumn<FlatLineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.getColumns().setAll(
storeCol,
storeQuantityCol
);
storedArticleCol.setSortable(false);
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
totalCol.setSortable(false);
tableView.getColumns().setAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 200);
return tableView;
}
public static void main(String[] args) {
launch(AggregateViewApp.class);
}
}
StoredQuantityTableCell.java
This takes a list of StoredQuantities which is a tuple of a store name and a quantity of things stored at that store and then renders that list into a single cell, formatting the display internally in a GridView. You could use whatever internal node layout or formatting you wish and add CSS styling to spice things up if necessary.
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.layout.GridPane;
import java.util.List;
class StoredQuantityTableCell extends TableCell<LineItem, List<StoredQuantity>> {
private GridPane storedQuantityPane;
public StoredQuantityTableCell() {
storedQuantityPane = new GridPane();
storedQuantityPane.setHgap(10);
storedQuantityPane.setVgap(5);
}
#Override
protected void updateItem(List<StoredQuantity> storedQuantities, boolean empty) {
super.updateItem(storedQuantities, empty);
if (storedQuantities == null) {
setGraphic(null);
return;
}
storedQuantityPane.getChildren().removeAll(storedQuantityPane.getChildren());
int row = 0;
for (StoredQuantity storedQuantity: storedQuantities) {
storedQuantityPane.addRow(
row,
new Label(storedQuantity.getStoreName()),
new Label("" + storedQuantity.getQuantity())
);
row++;
}
setGraphic(storedQuantityPane);
}
}
LineItem.java
A view model class representing a row in the table.
import java.util.Collections;
import java.util.List;
public class LineItem {
private long articleId;
private String articleName;
private List<StoredQuantity> storedQuantities;
public LineItem(long articleId, String articleName, List<StoredQuantity> storedQuantities) {
this.articleId = articleId;
this.articleName = articleName;
this.storedQuantities = storedQuantities;
}
public long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public List<StoredQuantity> getStoredQuantities() {
return Collections.unmodifiableList(storedQuantities);
}
public int getTotal() {
return storedQuantities.stream()
.mapToInt(StoredQuantity::getQuantity)
.sum();
}
}
StoredQuantity.java
A view model class representing a store name and quantity of things in the store. This is used by the StoredQuantityTableCell to render the stored quantities for a line item.
public class StoredQuantity implements Comparable<StoredQuantity> {
private String storeName;
private int quantity;
StoredQuantity(String storeName, int quantity) {
this.storeName = storeName;
this.quantity = quantity;
}
public String getStoreName() {
return storeName;
}
public int getQuantity() {
return quantity;
}
#Override
public int compareTo(StoredQuantity o) {
return storeName.compareTo(o.storeName);
}
}
FlatLineItem.java
A view model class supporting a table view with nested columns. A flat line item which can be created for each store that an article is stored in.
public class FlatLineItem {
private Long articleId;
private String articleName;
private final String storeName;
private final Integer storeQuantity;
private final Integer total;
private final boolean firstInGroup;
public FlatLineItem(Long articleId, String articleName, String storeName, Integer storeQuantity, Integer total, boolean firstInGroup) {
this.articleId = articleId;
this.articleName = articleName;
this.storeName = storeName;
this.storeQuantity = storeQuantity;
this.total = total;
this.firstInGroup = firstInGroup;
}
public Long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public String getStoreName() {
return storeName;
}
public Integer getStoreQuantity() {
return storeQuantity;
}
public Integer getTotal() {
return total;
}
public boolean isFirstInGroup() {
return firstInGroup;
}
}
LineItemService.java
This translates values from the database into view model objects (LineItems or FlatLineItems) which can be rendered by the views. Note how the getFlatLineItemsForLineItem which constructs the FlatLineItems for the nested column table view has a notion of what it the first row in a group of line items and propagates the the FlatLineItem appropriately based on that, leaving some values null if they are just repeated from the first item in the group, which results in a clean display.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LineItemService {
private final DB db = DB.instance();
public List<LineItem> fetchAllLineItems() {
return db.findAllArticles()
.stream()
.map(article -> createLineItemForArticle(article.getArticleId()))
.collect(Collectors.toList());
}
public List<FlatLineItem> fetchAllFlatLineItems() {
return fetchAllLineItems().stream()
.flatMap(lineItem -> getFlatLineItemsForLineItem(lineItem).stream())
.collect(Collectors.toList());
}
private List<FlatLineItem> getFlatLineItemsForLineItem(LineItem lineItem) {
ArrayList<FlatLineItem> flatLineItems = new ArrayList<>();
boolean firstStore = true;
for (StoredQuantity storedQuantity: lineItem.getStoredQuantities()) {
FlatLineItem newFlatLineItem;
if (firstStore) {
newFlatLineItem = new FlatLineItem(
lineItem.getArticleId(),
lineItem.getArticleName(),
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
lineItem.getTotal(),
true
);
firstStore = false;
} else {
newFlatLineItem = new FlatLineItem(
null,
null,
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
null,
false
);
}
flatLineItems.add(newFlatLineItem);
}
return flatLineItems;
}
private LineItem createLineItemForArticle(long articleId) {
DB.Article article =
db.findArticleById(
articleId
).orElse(
new DB.Article(articleId, "N/A")
);
List<DB.StoredArticle> storedArticles =
db.findAllStoredArticlesForArticleId(articleId);
return new LineItem(
article.getArticleId(),
article.getName(),
getStoredQuantitesForStoredArticles(storedArticles)
);
}
private List<StoredQuantity> getStoredQuantitesForStoredArticles(List<DB.StoredArticle> storedArticles) {
return storedArticles.stream()
.map(storedArticle ->
new StoredQuantity(
db.findStoreById(storedArticle.getStoreId())
.map(DB.Store::getName)
.orElse("No Store"),
storedArticle.getQuantity()
)
)
.sorted()
.collect(
Collectors.toList()
);
}
}
Mock database class
Just a simple in-memory representation of the database class. In a real app, you would probably use something like SpringData with hibernate to provide the data access repositories using a JPA based object to relational mapping.
The database classes aren't related to the view at all but are just presented here so that a running app can be created within a MVVM style framework.
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
class DB {
private static final DB instance = new DB();
public static DB instance() {
return instance;
}
private List<Article> articles = List.of(
new Article(1, "Hp101"),
new Article(3, "Lenovo303"),
new Article(4, "Asus404")
);
private List<Store> stores = List.of(
new Store(1, "S1"),
new Store(2, "S2")
);
private List<StoredArticle> storedArticles = List.of(
new StoredArticle(1, 1, 30),
new StoredArticle(1, 2, 70),
new StoredArticle(3, 1, 50),
new StoredArticle(4, 2, 70)
);
public Optional<Article> findArticleById(long articleId) {
return articles.stream()
.filter(article -> article.getArticleId() == articleId)
.findFirst();
}
public Optional<Store> findStoreById(long storeId) {
return stores.stream()
.filter(store -> store.getStoreId() == storeId)
.findFirst();
}
public List<StoredArticle> findAllStoredArticlesForArticleId(long articleId) {
return storedArticles.stream()
.filter(storedArticle -> storedArticle.articleId == articleId)
.collect(Collectors.toList());
}
public List<Article> findAllArticles() {
return Collections.unmodifiableList(articles);
}
static class Article {
private long articleId;
private String name;
public Article(long articleId, String name) {
this.articleId = articleId;
this.name = name;
}
public long getArticleId() {
return articleId;
}
public String getName() {
return name;
}
}
static class Store {
private long storeId;
private String name;
public Store(long storeId, String name) {
this.storeId = storeId;
this.name = name;
}
public long getStoreId() {
return storeId;
}
public String getName() {
return name;
}
}
static class StoredArticle {
private long articleId;
private long storeId;
private int quantity;
public StoredArticle(long articleId, long storeId, int quantity) {
this.articleId = articleId;
this.storeId = storeId;
this.quantity = quantity;
}
public long getArticleId() {
return articleId;
}
public long getStoreId() {
return storeId;
}
public int getQuantity() {
return quantity;
}
}
}
Answers to some follow-up questions
Which Approach is the best for updating data ?
All of the approaches I have shown use read only data models and views. To make it read-write would be a bit more work (and out of scope for what I would be prepared to add to this already long answer). Probably, of the two approaches outlined above, the approach which uses a separate row for each store containing an item would be the easiest to adapt to making the data updatable.
Which approach in general I should use to update data ( data are stored for sure in db) ?
Defining a general approach to updating data in a database is out of scope for what I would answer here (it is a purely opinion based answer, as there are many different ways to accomplish this, and as such is off topic for StackOverflow). If it were me, I'd set up a SpringBoot based rest service that connected to the database and have my client app communicate with that. If the app does not need to communicate over the internet, but only communicate with a local DB over a LAN, then adding direct database access by making the app a SpringBoot app and using Spring Data repositories with the embedded H2 database is what I would use.
Is when modifying in a specific row modify in db or wait until user modify in the whole tableview and click on a save button ?
Either way would work, I don't have any strong opinion on one versus the other. I'd probably lean towards the immediate update scenario rather than a delayed save scenario, but it would depend on the app and desired user experience.
Please can you provide me with some code for either to draw a line under every cell or to make it just like usual tableView ( one row gray and one not etc ...)
You can ask that as a separate question. But, in general, use CSS styling. If you use the second approach outlined above which has a row per store, then everything is already a "usual tableView" in terms of styling, with one row gray and one row not, etc., so I don't know that any additional styling is really required in such a case.
While in a distributed processing environment it is common to use "part" file names such as "part-000", is it possible to write an extension of some sort to rename the individual output file names (such as a per window file name) of Apache Beam?
To do this, one might have to be able to assign a name for a window or infer a file name based on the window's content. I would like to know if such an approach is possible.
As to whether the solution should be streaming or batch, a streaming mode example is preferable
Yes as suggested by jkff you can achieve this using TextIO.write.to(FilenamePolicy).
Examples are below:
If you want to write output to particular local file you can use:
lines.apply(TextIO.write().to("/path/to/file.txt"));
Below is the simple way to write the output using the prefix, link. This example is for google storage, instead of this you can use local/s3 paths.
public class MinimalWordCountJava8 {
public static void main(String[] args) {
PipelineOptions options = PipelineOptionsFactory.create();
// In order to run your pipeline, you need to make following runner specific changes:
//
// CHANGE 1/3: Select a Beam runner, such as BlockingDataflowRunner
// or FlinkRunner.
// CHANGE 2/3: Specify runner-required options.
// For BlockingDataflowRunner, set project and temp location as follows:
// DataflowPipelineOptions dataflowOptions = options.as(DataflowPipelineOptions.class);
// dataflowOptions.setRunner(BlockingDataflowRunner.class);
// dataflowOptions.setProject("SET_YOUR_PROJECT_ID_HERE");
// dataflowOptions.setTempLocation("gs://SET_YOUR_BUCKET_NAME_HERE/AND_TEMP_DIRECTORY");
// For FlinkRunner, set the runner as follows. See {#code FlinkPipelineOptions}
// for more details.
// options.as(FlinkPipelineOptions.class)
// .setRunner(FlinkRunner.class);
Pipeline p = Pipeline.create(options);
p.apply(TextIO.read().from("gs://apache-beam-samples/shakespeare/*"))
.apply(FlatMapElements
.into(TypeDescriptors.strings())
.via((String word) -> Arrays.asList(word.split("[^\\p{L}]+"))))
.apply(Filter.by((String word) -> !word.isEmpty()))
.apply(Count.<String>perElement())
.apply(MapElements
.into(TypeDescriptors.strings())
.via((KV<String, Long> wordCount) -> wordCount.getKey() + ": " + wordCount.getValue()))
// CHANGE 3/3: The Google Cloud Storage path is required for outputting the results to.
.apply(TextIO.write().to("gs://YOUR_OUTPUT_BUCKET/AND_OUTPUT_PREFIX"));
p.run().waitUntilFinish();
}
}
This example code will give you more control on writing the output:
/**
* A {#link FilenamePolicy} produces a base file name for a write based on metadata about the data
* being written. This always includes the shard number and the total number of shards. For
* windowed writes, it also includes the window and pane index (a sequence number assigned to each
* trigger firing).
*/
protected static class PerWindowFiles extends FilenamePolicy {
private final ResourceId prefix;
public PerWindowFiles(ResourceId prefix) {
this.prefix = prefix;
}
public String filenamePrefixForWindow(IntervalWindow window) {
String filePrefix = prefix.isDirectory() ? "" : prefix.getFilename();
return String.format(
"%s-%s-%s", filePrefix, formatter.print(window.start()), formatter.print(window.end()));
}
#Override
public ResourceId windowedFilename(int shardNumber,
int numShards,
BoundedWindow window,
PaneInfo paneInfo,
OutputFileHints outputFileHints) {
IntervalWindow intervalWindow = (IntervalWindow) window;
String filename =
String.format(
"%s-%s-of-%s%s",
filenamePrefixForWindow(intervalWindow),
shardNumber,
numShards,
outputFileHints.getSuggestedFilenameSuffix());
return prefix.getCurrentDirectory().resolve(filename, StandardResolveOptions.RESOLVE_FILE);
}
#Override
public ResourceId unwindowedFilename(
int shardNumber, int numShards, OutputFileHints outputFileHints) {
throw new UnsupportedOperationException("Unsupported.");
}
}
#Override
public PDone expand(PCollection<InputT> teamAndScore) {
if (windowed) {
teamAndScore
.apply("ConvertToRow", ParDo.of(new BuildRowFn()))
.apply(new WriteToText.WriteOneFilePerWindow(filenamePrefix));
} else {
teamAndScore
.apply("ConvertToRow", ParDo.of(new BuildRowFn()))
.apply(TextIO.write().to(filenamePrefix));
}
return PDone.in(teamAndScore.getPipeline());
}
Yes. Per documentation of TextIO:
If you want better control over how filenames are generated than the default policy allows, a custom FilenamePolicy can also be set using TextIO.Write.to(FilenamePolicy)
This is perfectly valid example with beam 2.1.0. You can call on your data (PCollection e.g)
import org.apache.beam.sdk.io.FileBasedSink.FilenamePolicy;
import org.apache.beam.sdk.io.TextIO;
import org.apache.beam.sdk.io.fs.ResolveOptions.StandardResolveOptions;
import org.apache.beam.sdk.io.fs.ResourceId;
import org.apache.beam.sdk.transforms.display.DisplayData;
#SuppressWarnings("serial")
public class FilePolicyExample {
public static void main(String[] args) {
FilenamePolicy policy = new WindowedFilenamePolicy("somePrefix");
//data
data.apply(TextIO.write().to("your_DIRECTORY")
.withFilenamePolicy(policy)
.withWindowedWrites()
.withNumShards(4));
}
private static class WindowedFilenamePolicy extends FilenamePolicy {
final String outputFilePrefix;
WindowedFilenamePolicy(String outputFilePrefix) {
this.outputFilePrefix = outputFilePrefix;
}
#Override
public ResourceId windowedFilename(
ResourceId outputDirectory, WindowedContext input, String extension) {
String filename = String.format(
"%s-%s-%s-of-%s-pane-%s%s%s",
outputFilePrefix,
input.getWindow(),
input.getShardNumber(),
input.getNumShards() - 1,
input.getPaneInfo().getIndex(),
input.getPaneInfo().isLast() ? "-final" : "",
extension);
return outputDirectory.resolve(filename, StandardResolveOptions.RESOLVE_FILE);
}
#Override
public ResourceId unwindowedFilename(
ResourceId outputDirectory, Context input, String extension) {
throw new UnsupportedOperationException("Expecting windowed outputs only");
}
#Override
public void populateDisplayData(DisplayData.Builder builder) {
builder.add(DisplayData.item("fileNamePrefix", outputFilePrefix)
.withLabel("File Name Prefix"));
}
}
}
You can check https://beam.apache.org/releases/javadoc/2.3.0/org/apache/beam/sdk/io/FileIO.html for more information, you should search "File naming" in "Writing files".
.apply(
FileIO.<RootElement>write()
.via(XmlIO
.sink(RootElement.class)
.withRootElement(ROOT_XML_ELEMENT)
.withCharset(StandardCharsets.UTF_8))
.to(FILE_PATH)
.withNaming((window, pane, numShards, shardIndex, compression) -> NEW_FILE_NAME)
I am trying to build a simple component to understand how and why JSF 2.X works the way it does. I have been using the newer annotations and have been trying to piece together a clear example.
So I have built my component and deployed it in a xhtml file as follows:
<kal:day value="K.Day" title="Kalendar" model="#{kalendarDay}"/>
The within the UIComponent I do the following:
ValueExpression ve = getValueExpression("model");
if (ve != null)
{
System.out.println("model expression "+ve.getExpressionString());
model = (KalendarDay) ve.getValue(getFacesContext().getELContext());
System.out.println("model "+model);
}
The expression "#{kalendarDay}" is correctly displayed indicating that the value has been successfully transmitted between the page and the component. However the evaluation of the expression results in "null".
This seems to indicate that the backing bean is unavailable at this point, although the page correctly validates and deploys. So I am 95% certain that the bean is there at run time.
So perhaps this is a phase thing? Should I be evaluating this in the decode of the renderer and setting the value in the attributes map? I am still a little confused about the combination of actual values and value expressions.
So my question is where should I fetch and evaluate the valueExpression for model and should I store the result of the evaluation in the UIComponent or should I simply evaluate it every time?
SSCCE files below I think these are the only required files to demonstrate the problem
Bean Interface -----
/**
*
*/
package com.istana.kalendar.fixture;
import java.util.Date;
/**
* #author User
*
*/
public interface KalendarDay
{
public Date getDate();
}
Bean Implementation ---
/**
*
*/
package com.istana.kalendar.session.wui;
import java.util.Calendar;
import java.util.Date;
import javax.ejb.Stateful;
import javax.inject.Named;
import com.istana.kalendar.fixture.KalendarDay;
/**
* #author User
*
*/
#Named ("kalendarDay")
#Stateful
public class KalKalendarDay
implements KalendarDay
{
private Calendar m_date = Calendar.getInstance();
/* (non-Javadoc)
* #see com.istana.kalendar.fixture.KalendarDay#getDate()
*/
#Override
public Date getDate()
{
return m_date.getTime();
}
}
UIComponent ---
/**
*
*/
package com.istana.kalendar.fixture.jsf;
import javax.el.ValueExpression;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;
import com.istana.kalendar.fixture.KalendarDay;
/**
* #author User
*
*/
#FacesComponent (value=UIDay.COMPONENT_TYPE)
public class UIDay extends UIOutput
{
static final
public String COMPONENT_TYPE = "com.istana.kalendar.fixture.jsf.Day";
static final
public String COMPONENT_FAMILY = "com.istana.kalendar.fixture.jsf.Day";
private KalendarDay m_model;
private String m_title;
#Override
public String getRendererType()
{
return UIDayRenderer.RENDERER_TYPE;
}
#Override
public String getFamily()
{
return COMPONENT_FAMILY;
}
public KalendarDay getModel()
{
KalendarDay model = (KalendarDay) getStateHelper().eval("model");
System.out.println("model "+model);
return model;
}
public void setModel(KalendarDay model)
{
getStateHelper().put("model",model);
}
public String getTitle()
{
return (String) getStateHelper().eval("title");
}
public void setTitle(String title)
{
getStateHelper().put("title",title);
}
}
UIComponentRenderer ---
/**
*
*/
package com.istana.kalendar.fixture.jsf;
import java.io.IOException;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import com.istana.kalendar.fixture.KalendarDay;
/**
* #author User
*
*/
#FacesRenderer (componentFamily = UIDay.COMPONENT_FAMILY
,rendererType = UIDayRenderer.RENDERER_TYPE
)
public class UIDayRenderer extends Renderer
{
static final
public String RENDERER_TYPE = "com.istana.kalendar.fixture.jsf.DayRenderer";
#Override
public void encodeBegin (FacesContext context,UIComponent component)
throws IOException
{
UIDay uic = (UIDay) component;
ResponseWriter writer = context.getResponseWriter();
writer.startElement("p", uic);
/*
* This is the call that triggers the println
*/
writer.write("Day - "+uic.getModel().getDate());
}
#Override
public void encodeEnd (FacesContext context,UIComponent component)
throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.endElement("p");
writer.flush();
}
}
kalendar.taglib.xml ---
<facelet-taglib
id="kalendar"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
version="2.0"
>
<namespace>http://istana.com/kalendar</namespace>
<tag>
<tag-name>day</tag-name>
<component>
<component-type>com.istana.kalendar.fixture.jsf.Day</component-type>
</component>
</tag>
</facelet-taglib>
I'm not sure why it's null, but the symptoms indicate that the #{kalendarDay} is been specified during view render time while you're trying to evaluate it during the view build time.
So perhaps this is a phase thing? Should I be evaluating this in the decode of the renderer and setting the value in the attributes map? I am still a little confused about the combination of actual values and value expressions.
You should use the encodeXxx() methods of the component or the associated renderer (if any) to generate HTML based on the component's attributes/properties.
You should use the decode() method of the component or the associated renderer (if any) to set component's attributes/properties based on HTTP request parameters which are been sent along with a HTML form submit.
So my question is where should I fetch and evaluate the valueExpression for model and should I store the result of the evaluation in the UIComponent or should I simply evaluate it every time?
Since JSF 2.x it's recommended to explicitly specify a getter and setter for component attributes which in turn delegates to UIComponent#getStateHelper().
public String getValue() {
return (String) getStateHelper().eval("value");
}
public void setValue(String value) {
getStateHelper().put("value", value);
}
public String getTitle() {
return (String) getStateHelper().eval("title");
}
public void setTitle(String title) {
getStateHelper().put("title", title);
}
public Object getModel() {
return getStateHelper().eval("model");
}
public void setModel(Object model) {
getStateHelper().put("model", model);
}
That's all you basically need (note that the getter and setter must exactly match the attribute name as per Javabeans specification). Then in the encodeXxx() method(s) just call getModel() to get (and evaluate) the value of the model attribute.