Merge remote-tracking branch 'origin/master'

This commit is contained in:
Maxim Gorbunkov 2016-07-13 16:56:33 +04:00
commit 2ec32860de
46 changed files with 484 additions and 205 deletions

View File

@ -144,6 +144,7 @@ create table SYS_SCHEDULED_EXECUTION (
)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^
------------------------------------------------------------------------------------------------------------
@ -711,6 +712,8 @@ create table SYS_CATEGORY_ATTR(
REQUIRED boolean,
LOOKUP boolean,
TARGET_SCREENS varchar(4000),
WIDTH varchar(20),
ROWS_COUNT integer,
--
primary key (ID)
)^

View File

@ -142,6 +142,7 @@ create table SYS_SCHEDULED_EXECUTION (
)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^
create clustered index IDX_SYS_SCHEDULED_EXECUTION_CREATE_TS on SYS_SCHEDULED_EXECUTION (CREATE_TS)^
@ -754,6 +755,8 @@ create table SYS_CATEGORY_ATTR (
REQUIRED tinyint,
LOOKUP tinyint,
TARGET_SCREENS varchar(4000),
WIDTH varchar(20),
ROWS_COUNT integer,
--
primary key nonclustered (ID),
constraint SYS_CATEGORY_ATTR_CATEGORY_ID foreign key (CATEGORY_ID) references SYS_CATEGORY(ID)

View File

@ -141,6 +141,7 @@ create table SYS_SCHEDULED_EXECUTION (
)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^
/**********************************************************************************************/
@ -759,6 +760,8 @@ create table SYS_CATEGORY_ATTR (
REQUIRED boolean,
LOOKUP boolean,
TARGET_SCREENS varchar(4000),
WIDTH varchar(20),
ROWS_COUNT integer,
--
primary key (ID),
constraint SYS_CATEGORY_ATTR_CATEGORY_ID foreign key (CATEGORY_ID) references SYS_CATEGORY(ID)

View File

@ -77,6 +77,8 @@ create table SYS_CATEGORY_ATTR (
REQUIRED char(1),
LOOKUP char(1),
TARGET_SCREENS varchar2(4000),
WIDTH varchar2(20),
ROWS_COUNT integer,
primary key(ID)
)^
create index IDX_SYS_CATEGORY_ATTR_CATEGORY on SYS_CATEGORY_ATTR(CATEGORY_ID)^
@ -233,6 +235,7 @@ create table SYS_SCHEDULED_EXECUTION (
primary key(ID)
)^
create index IDX_SYS_SCH_EXE_TAS_STA_TIM on SYS_SCHEDULED_EXECUTION(TASK_ID, START_TIME)^
create index IDX_SYS_SCH_EXE_TAS_FI_TIM on SYS_SCHEDULED_EXECUTION(TASK_ID, FINISH_TIME)^
create table SYS_SCHEDULED_TASK (
ID varchar2(32) not null,

View File

@ -141,6 +141,7 @@ create table SYS_SCHEDULED_EXECUTION (
)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^
------------------------------------------------------------------------------------------------------------
@ -723,6 +724,8 @@ create table SYS_CATEGORY_ATTR (
REQUIRED boolean,
LOOKUP boolean,
TARGET_SCREENS varchar(4000),
WIDTH varchar(20),
ROWS_COUNT integer,
--
primary key (ID),
constraint SYS_CATEGORY_ATTR_CATEGORY_ID foreign key (CATEGORY_ID) references SYS_CATEGORY(ID)

View File

@ -0,0 +1,2 @@
alter table SYS_CATEGORY_ATTR add WIDTH varchar(20)^
alter table SYS_CATEGORY_ATTR add ROWS_COUNT integer^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -0,0 +1,2 @@
alter table SYS_CATEGORY_ATTR add WIDTH varchar(20)^
alter table SYS_CATEGORY_ATTR add ROWS_COUNT integer^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -0,0 +1,2 @@
alter table SYS_CATEGORY_ATTR add WIDTH varchar(20)^
alter table SYS_CATEGORY_ATTR add ROWS_COUNT integer^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -0,0 +1,2 @@
alter table SYS_CATEGORY_ATTR add WIDTH varchar2(20)^
alter table SYS_CATEGORY_ATTR add ROWS_COUNT integer^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCH_EXE_TAS_FI_TIM on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)

View File

@ -0,0 +1,2 @@
alter table SYS_CATEGORY_ATTR add WIDTH varchar(20)^
alter table SYS_CATEGORY_ATTR add ROWS_COUNT integer^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -55,13 +55,12 @@ public interface PersistenceSecurity extends Security {
/**
* Applies in-memory constraints to the entity
* @param entity -
* @return true, if entity should be filtered from client output
*/
boolean applyConstraints(Entity entity);
void applyConstraints(Entity entity);
/**
* Applies in-memory constraints to the collection of entities and filter the collection
* @param entities -
* Applies in-memory constraints to the entity fields
* @param entities - collection of entities
*/
void applyConstraints(Collection<Entity> entities);
@ -71,6 +70,13 @@ public interface PersistenceSecurity extends Security {
*/
boolean filterByConstraints(Collection<Entity> entities);
/**
* Filter entities in collection by in-memory constraints
* @param entity - collection of entities that will be filtered
* @return true, if entity should be filtered from client output
*/
boolean filterByConstraints(Entity entity);
/**
* Reads security token and restores filtered data
* @param resultEntity -

View File

@ -98,6 +98,7 @@ public class DataManagerBean implements DataManager {
}
E result = null;
boolean needToApplyInMemoryConstraints = needToApplyInMemoryConstraints(context);
try (Transaction tx = persistence.createTransaction()) {
final EntityManager em = persistence.getEntityManager();
@ -115,8 +116,13 @@ public class DataManagerBean implements DataManager {
//noinspection unchecked
List<E> resultList = executeQuery(query, singleResult);
if (!resultList.isEmpty())
if (!resultList.isEmpty()) {
result = resultList.get(0);
}
if (result != null && needToApplyInMemoryConstraints && security.filterByConstraints(result)) {
result = null;
}
if (result instanceof BaseGenericIdEntity && context.isLoadDynamicAttributes()) {
dynamicAttributesManagerAPI.fetchDynamicAttributes(Collections.singletonList((BaseGenericIdEntity) result));
@ -125,12 +131,13 @@ public class DataManagerBean implements DataManager {
tx.commit();
}
if (result != null && needToApplyConstraints(context) && security.applyConstraints(result)) {
return null;
if (result != null) {
if (needToApplyInMemoryConstraints) {
security.applyConstraints(result);
}
attributeSecurity.afterLoad(result);
}
attributeSecurity.afterLoad(result);
return result;
}
@ -183,7 +190,7 @@ public class DataManagerBean implements DataManager {
tx.commit();
}
if (needToApplyConstraints(context)) {
if (needToApplyInMemoryConstraints(context)) {
security.applyConstraints((Collection<Entity>) resultList);
}
@ -208,7 +215,7 @@ public class DataManagerBean implements DataManager {
queryResultsManager.savePreviousQueryResults(context);
if (security.hasMemoryConstraints(metaClass, ConstraintOperationType.READ, ConstraintOperationType.ALL) ) {
if (security.hasMemoryConstraints(metaClass, ConstraintOperationType.READ, ConstraintOperationType.ALL)) {
context = context.copy();
List resultList;
try (Transaction tx = persistence.createTransaction()) {
@ -381,6 +388,10 @@ public class DataManagerBean implements DataManager {
}
}
}
if (isAuthorizationRequired() && userSessionSource.getUserSession().hasConstraints()) {
security.filterByConstraints(res);
}
}
tx.commit();
@ -561,7 +572,7 @@ public class DataManagerBean implements DataManager {
View view = context.getView() != null ? context.getView() :
viewRepository.getView(metadata.getClassNN(context.getMetaClass()), View.LOCAL);
View copy = View.copy(attributeSecurity.createRestrictedView(view));
if (context.isLoadPartialEntities()) {
if (context.isLoadPartialEntities() && !needToApplyInMemoryConstraints(context)) {
copy.setLoadPartialEntities(true);
}
return copy;
@ -570,16 +581,17 @@ public class DataManagerBean implements DataManager {
@SuppressWarnings("unchecked")
protected <E extends Entity> List<E> getResultList(LoadContext<E> context, Query query, boolean ensureDistinct) {
List<E> list = executeQuery(query, false);
boolean filteredByConstraints = false;
int initialSize = list.size();
if (initialSize == 0) {
return list;
}
if (needFilterByConstraints(context)) {
boolean needApplyConstraints = needToApplyInMemoryConstraints(context);
boolean filteredByConstraints = false;
if (needApplyConstraints) {
filteredByConstraints = security.filterByConstraints((Collection<Entity>) list);
}
if (!ensureDistinct) {
return filteredByConstraints ? getResultListIteratively(context, query, list, initialSize) : list;
return filteredByConstraints ? getResultListIteratively(context, query, list, initialSize, true) : list;
}
int requestedFirst = context.getQuery().getFirstResult();
@ -590,13 +602,13 @@ public class DataManagerBean implements DataManager {
}
// In case of not first chunk, even if there where no duplicates, start filling the set from zero
// to ensure correct paging
return getResultListIteratively(context, query, set, initialSize);
return getResultListIteratively(context, query, set, initialSize, needApplyConstraints);
}
@SuppressWarnings("unchecked")
protected <E extends Entity> List<E> getResultListIteratively(LoadContext<E> context, Query query,
Collection<E> filteredCollection,
int initialSize) {
int initialSize, boolean needApplyConstraints) {
int requestedFirst = context.getQuery().getFirstResult();
int requestedMax = context.getQuery().getMaxResults();
@ -614,7 +626,7 @@ public class DataManagerBean implements DataManager {
int maxResults = (requestedFirst + requestedMax) * factor;
int i = 0;
while (filteredCollection.size() < setSize) {
if (i++ > 1000) {
if (i++ > 10000) {
log.warn("In-memory distinct: endless loop detected for " + context);
break;
}
@ -625,7 +637,7 @@ public class DataManagerBean implements DataManager {
if (list.size() == 0) {
break;
}
if (needFilterByConstraints(context)) {
if (needApplyConstraints) {
security.filterByConstraints((Collection<Entity>) list);
}
filteredCollection.addAll(list);
@ -796,33 +808,25 @@ public class DataManagerBean implements DataManager {
return null;
}
protected boolean needToApplyConstraints(LoadContext context) {
protected boolean needToApplyInMemoryConstraints(LoadContext context) {
if (!isAuthorizationRequired() || !userSessionSource.getUserSession().hasConstraints()) {
return false;
}
if (context.getView() == null) {
return false;
MetaClass metaClass = metadata.getSession().getClassNN(context.getMetaClass());
return security.hasMemoryConstraints(metaClass, ConstraintOperationType.READ, ConstraintOperationType.ALL);
}
Session session = metadata.getSession();
for (Class aClass : collectEntityClasses(context.getView(), new HashSet<>())) {
if (security.hasConstraints(session.getClassNN(aClass))) {
if (security.hasMemoryConstraints(session.getClassNN(aClass), ConstraintOperationType.READ, ConstraintOperationType.ALL)) {
return true;
}
}
return false;
}
protected boolean needFilterByConstraints(LoadContext context) {
if (!isAuthorizationRequired() || !userSessionSource.getUserSession().hasConstraints()) {
return false;
}
MetaClass metaClass = metadata.getSession().getClassNN(context.getMetaClass());
return security.hasConstraints(metaClass);
}
protected Set<Class> collectEntityClasses(View view, Set<View> visited) {
if (visited.contains(view)) {
return Collections.emptySet();

View File

@ -19,6 +19,7 @@ package com.haulmont.cuba.core.app.scheduling;
import com.haulmont.cuba.core.entity.ScheduledTask;
import java.util.Date;
import java.util.List;
/**
@ -40,4 +41,6 @@ public interface Coordinator {
void end(Context context);
boolean isLastExecutionFinished(ScheduledTask task, long now);
long getLastFinished(ScheduledTask task);
}

View File

@ -24,8 +24,8 @@ import com.haulmont.cuba.core.Transaction;
import com.haulmont.cuba.core.entity.ScheduledTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.persistence.LockModeType;
import java.util.Date;
@ -35,7 +35,6 @@ import java.util.List;
* Implementation of {@link Coordinator} interface, performing synchronization of singleton schedulers on the main
* database.
* <p>This implementation should not be used if the database is overloaded.</p>
*
*/
@Component(Coordinator.NAME)
public class DbBasedCoordinator implements Coordinator {
@ -108,6 +107,16 @@ public class DbBasedCoordinator implements Coordinator {
}
}
@Override
public long getLastFinished(ScheduledTask task) {
EntityManager em = persistence.getEntityManager();
Query query = em.createQuery(
"select max(e.finishTime) from sys$ScheduledExecution e where e.task.id = ?1")
.setParameter(1, task.getId());
Date date = (Date) query.getFirstResult();
return date == null ? 0 : date.getTime();
}
protected synchronized List<ScheduledTask> getTasks() {
log.trace("Read all active tasks from DB and lock them");
EntityManager em = persistence.getEntityManager();

View File

@ -25,6 +25,7 @@ import com.haulmont.cuba.core.app.ServerInfoAPI;
import com.haulmont.cuba.core.app.scheduled.MethodParameterInfo;
import com.haulmont.cuba.core.entity.ScheduledExecution;
import com.haulmont.cuba.core.entity.ScheduledTask;
import com.haulmont.cuba.core.entity.SchedulingType;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.core.global.Scripting;
@ -104,33 +105,31 @@ public class RunnerBean implements Runner {
// It's better not to pass an entity instance in managed state to another thread
final ScheduledTask taskCopy = metadata.getTools().copy(task);
executorService.submit(new Runnable() {
@Override
public void run() {
log.debug(taskCopy + ": running");
try {
boolean runConcurrent = scheduling.setRunning(taskCopy, true);
if (!runConcurrent) {
executorService.submit(() -> {
log.debug("{}: running", taskCopy);
try {
boolean runConcurrent = scheduling.setRunning(taskCopy, true);
if (!runConcurrent) {
try {
setSecurityContext(taskCopy, userSession);
ScheduledExecution execution = registerExecutionStart(taskCopy, now);
statisticsCounter.incCubaScheduledTasksCount();
try {
setSecurityContext(taskCopy, userSession);
ScheduledExecution execution = registerExecutionStart(taskCopy, now);
statisticsCounter.incCubaScheduledTasksCount();
try {
Object result = executeTask(taskCopy);
registerExecutionFinish(taskCopy, execution, result);
} catch (Throwable throwable) {
registerExecutionFinish(taskCopy, execution, throwable);
throw throwable;
}
} finally {
scheduling.setRunning(taskCopy, false);
Object result = executeTask(taskCopy);
registerExecutionFinish(taskCopy, execution, result);
} catch (Throwable throwable) {
registerExecutionFinish(taskCopy, execution, throwable);
throw throwable;
}
} else {
log.info("Detected concurrent task execution: {}, skip it", taskCopy);
} finally {
scheduling.setRunning(taskCopy, false);
scheduling.setFinished(task);
}
} catch (Throwable throwable) {
log.error("Error running " + taskCopy, throwable);
} else {
log.info("Detected concurrent task execution: {}, skip it", taskCopy);
}
} catch (Throwable throwable) {
log.error("Error running {}", taskCopy, throwable);
}
});
}
@ -148,10 +147,10 @@ public class RunnerBean implements Runner {
}
protected ScheduledExecution registerExecutionStart(ScheduledTask task, long now) {
if (!BooleanUtils.isTrue(task.getLogStart()) && !BooleanUtils.isTrue(task.getSingleton()))
if (!BooleanUtils.isTrue(task.getLogStart()) && !BooleanUtils.isTrue(task.getSingleton()) && task.getSchedulingType() != SchedulingType.FIXED_DELAY)
return null;
log.trace(task + ": registering execution start");
log.trace("{}: registering execution start", task);
Transaction tx = persistence.createTransaction();
try {
@ -172,10 +171,11 @@ public class RunnerBean implements Runner {
}
protected void registerExecutionFinish(ScheduledTask task, ScheduledExecution execution, Object result) {
if ((!BooleanUtils.isTrue(task.getLogFinish()) && !BooleanUtils.isTrue(task.getSingleton())) || execution == null)
if ((!BooleanUtils.isTrue(task.getLogFinish()) && !BooleanUtils.isTrue(task.getSingleton()) && task.getSchedulingType() != SchedulingType.FIXED_DELAY)
|| execution == null)
return;
log.trace(task + ": registering execution finish");
log.trace("{}: registering execution finish", task);
Transaction tx = persistence.createTransaction();
try {
EntityManager em = persistence.getEntityManager();
@ -193,7 +193,7 @@ public class RunnerBean implements Runner {
protected Object executeTask(ScheduledTask task) {
switch (task.getDefinedBy()) {
case BEAN: {
log.trace(task + ": invoking bean");
log.trace("{}: invoking bean", task);
Object bean = AppBeans.get(task.getBeanName());
try {
List<MethodParameterInfo> methodParams = task.getMethodParameters();

View File

@ -33,21 +33,23 @@ import com.haulmont.cuba.security.sys.UserSessionManager;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.perf4j.StopWatch;
import org.perf4j.log4j.Log4JStopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Class that manages {@link ScheduledTask}s in distributed environment.
*
*/
@Component(SchedulingAPI.NAME)
public class Scheduling implements SchedulingAPI {
@ -81,7 +83,9 @@ public class Scheduling implements SchedulingAPI {
protected ConcurrentMap<ScheduledTask, Long> runningTasks = new ConcurrentHashMap<>();
protected Map<ScheduledTask, Long> lastStartCache = new HashMap<>();
protected Map<ScheduledTask, Long> lastStartCache = new ConcurrentHashMap<>();
protected Map<ScheduledTask, Long> lastFinishCache = new ConcurrentHashMap<>();
protected volatile long schedulingStartTime;
@ -136,6 +140,11 @@ public class Scheduling implements SchedulingAPI {
}
}
@Override
public void setFinished(ScheduledTask task) {
lastFinishCache.put(task, timeSource.currentTimeMillis());
}
@Override
public boolean isActive() {
return configuration.getConfig(ServerConfig.class).getSchedulingActive();
@ -168,7 +177,7 @@ public class Scheduling implements SchedulingAPI {
protected void processTask(ScheduledTask task) {
if (isRunning(task)) {
log.trace(task + " is running");
log.trace("{} is running", task);
return;
}
@ -189,12 +198,18 @@ public class Scheduling implements SchedulingAPI {
if (BooleanUtils.isTrue(task.getSingleton())) {
if (task.getStartDate() != null || SchedulingType.CRON == task.getSchedulingType()) {
long currentStart = calculateCurrentStart(task, task.getLastStart(), now, period, frame);
if (needToStartNow(now, frame, task.getLastStart(), currentStart)) {
long currentStart;
if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) {
currentStart = calculateNextDelayDate(task, task.getLastStart(), coordinator.getLastFinished(task), now, frame, period);
} else if (SchedulingType.CRON == task.getSchedulingType()) {
currentStart = calculateNextCronDate(task, task.getLastStart(), now, frame);
} else {
currentStart = calculateNextPeriodDate(task, task.getLastStart(), now, frame, period);
}
if (needToStartInTimeFrame(now, frame, task.getLastStart(), currentStart)) {
runSingletonTask(task, now, me);
} else {
log.trace(task + "\n not in time frame to start");
log.trace("{}\n not in time frame to start", task);
}
} else {
Integer lastServerPriority = task.getLastStartServer() == null ?
@ -208,40 +223,56 @@ public class Scheduling implements SchedulingAPI {
boolean giveChanceToPreviousHost = lastServerWasNotMe(task, me)
&& (lastServerPriority != null && serverPriority.compareTo(lastServerPriority) > 0);
if (log.isTraceEnabled())
log.trace(task + "\n now=" + now + " lastStart=" + task.getLastStart()
+ " lastServer=" + task.getLastStartServer() + " shouldSwitch=" + shouldSwitch
+ " giveChanceToPreviousHost=" + giveChanceToPreviousHost);
log.trace("{}\n now={} lastStart={} lastServer={} shouldSwitch={} giveChanceToPreviousHost={}",
task, now, task.getLastStart(), task.getLastStartServer(), shouldSwitch, giveChanceToPreviousHost);
if (task.getLastStart() == 0
|| shouldSwitch
|| (task.getLastStart() + (giveChanceToPreviousHost ? period + period / 2 : period) <= now)) {
if (task.getLastStart() == 0 || shouldSwitch) {
runSingletonTask(task, now, me);
} else {
log.trace(task + "\n time has not come and we shouldn't switch");
long delay = giveChanceToPreviousHost ? period + period / 2 : period;
if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) {
long lastFinish = coordinator.getLastFinished(task);
if ((task.getLastStart() < lastFinish || !lastFinishCache.containsKey(task)) && lastFinish + delay < now) {
runSingletonTask(task, now, me);
} else {
log.trace("{}\n time has not come and we shouldn't switch", task);
}
} else if (task.getLastStart() + delay <= now) {
runSingletonTask(task, now, me);
} else {
log.trace("{}\n time has not come and we shouldn't switch", task);
}
}
}
} else {
Long lastStart = lastStartCache.get(task);
if (lastStart == null) {
lastStart = 0L;
}
Long lastStart = lastStartCache.getOrDefault(task, 0L);
Long lastFinish = lastFinishCache.getOrDefault(task, 0L);
if (task.getStartDate() != null || SchedulingType.CRON == task.getSchedulingType()) {
long currentStart = calculateCurrentStart(task, lastStart, now, period, frame);
if (needToStartNow(now, frame, lastStart, currentStart)) {
long currentStart;
if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) {
currentStart = calculateNextDelayDate(task, lastStart, lastFinish, now, frame, period);
} else if (SchedulingType.CRON == task.getSchedulingType()) {
currentStart = calculateNextCronDate(task, lastStart, now, frame);
} else {
currentStart = calculateNextPeriodDate(task, lastStart, now, frame, period);
}
if (needToStartInTimeFrame(now, frame, lastStart, currentStart)) {
runTask(task, now);
} else {
log.trace(task + "\n not in time frame to start");
log.trace("{}\n not in time frame to start", task);
}
} else {
if (log.isTraceEnabled())
log.trace(task + "\n now=" + now + " lastStart= " + lastStart);
if (now >= lastStart + period) {
log.trace("{}\n now={} lastStart={} lastFinish={}", task, now, lastStart, lastFinish);
if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) {
if ((lastStart == 0 || lastStart < lastFinish) && now >= lastFinish + period) {
runTask(task, now);
} else {
log.trace("{}\n time has not come", task);
}
} else if (now >= lastStart + period) {
runTask(task, now);
} else {
log.trace(task + "\n time has not come");
log.trace("{}\n time has not come", task);
}
}
}
@ -250,42 +281,48 @@ public class Scheduling implements SchedulingAPI {
}
}
private boolean needToStartNow(long now, long frame, long lastStart, long currentStart) {
protected boolean needToStartInTimeFrame(long now, long frame, long lastStart, long currentStart) {
return currentStart <= now && now < currentStart + frame && lastStart < currentStart;
}
protected long calculateCurrentStart(ScheduledTask task, long lastStart, long now, long period, long frame) {
String cron = task.getCron();
if (SchedulingType.CRON == task.getSchedulingType()) {
StopWatch sw = new Log4JStopWatch("Cron next date calculations");
CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(cron, getCurrentTimeZone());
//if last start = 0 (task never has run) or to far in the past, we use (NOW - FRAME) timestamp for pivot time
//this approach should work fine cause cron works with absolute time
long pivotPreviousTime = Math.max(lastStart, now - frame);
protected long calculateNextCronDate(ScheduledTask task, long date, long currentDate, long frame) {
StopWatch sw = new Log4JStopWatch("Cron next date calculations");
CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(task.getCron(), getCurrentTimeZone());
//if last start = 0 (task never has run) or to far in the past, we use (NOW - FRAME) timestamp for pivot time
//this approach should work fine cause cron works with absolute time
long pivotPreviousTime = Math.max(date, currentDate - frame);
Date currentStart = null;
Date nextDate = cronSequenceGenerator.next(new Date(pivotPreviousTime));
while (nextDate.getTime() < now) {//if next date is in past try to find next date nearest to now
currentStart = nextDate;
nextDate = cronSequenceGenerator.next(nextDate);
}
if (currentStart == null) {
currentStart = nextDate;
}
log.trace(task + "\n now=" + now + " frame=" + frame
+ " currentStart=" + currentStart + " lastStart=" + lastStart + " cron=" + cron);
sw.stop();
return currentStart.getTime();
} else {
long repetitions = (now - task.getStartDate().getTime()) / period;
long currentStart = task.getStartDate().getTime() + repetitions * period;
log.trace(task + "\n now=" + now + " frame=" + frame + " repetitions=" + repetitions +
" currentStart=" + currentStart + " lastStart=" + lastStart);
return currentStart;
Date currentStart = null;
Date nextDate = cronSequenceGenerator.next(new Date(pivotPreviousTime));
while (nextDate.getTime() < currentDate) {//if next date is in past try to find next date nearest to now
currentStart = nextDate;
nextDate = cronSequenceGenerator.next(nextDate);
}
if (currentStart == null) {
currentStart = nextDate;
}
log.trace("{}\n now={} frame={} currentStart={} lastStart={} cron={}",
task, currentDate, frame, currentStart, task.getCron());
sw.stop();
return currentStart.getTime();
}
protected long calculateNextPeriodDate(ScheduledTask task, long date, long currentDate, long frame, long period) {
long repetitions = (currentDate - task.getStartDate().getTime()) / period;
long currentStart = task.getStartDate().getTime() + repetitions * period;
log.trace("{}\n now={} frame={} repetitions={} currentStart={} lastStart={}",
task, currentDate, frame, repetitions, currentStart, date);
return currentStart;
}
protected long calculateNextDelayDate(ScheduledTask task, long lastStart, long lastFinish, long currentDate, long frame, long period) {
long fromDate = lastFinish != 0 ? lastFinish : task.getStartDate().getTime();
long repetitions = (currentDate - fromDate) / period;
long currentStart = fromDate + repetitions * period;
log.trace("{}\n now={} frame={} repetitions={} currentStart={} lastStart={} lastFinish={}",
task, currentDate, frame, repetitions, currentStart, lastStart, lastFinish);
return currentStart;
}
protected TimeZone getCurrentTimeZone() {

View File

@ -55,7 +55,7 @@ public interface SchedulingAPI {
void processScheduledTasks(boolean onlyIfActive);
/**
* Mark the sheduled task as running/not running in the internal list. This method should not be used in the
* Mark the scheduled task as running/not running in the internal list. This method should not be used in the
* application code.
* @param task task instance
* @param running true to mark as running, false to mark as not running
@ -63,6 +63,13 @@ public interface SchedulingAPI {
*/
boolean setRunning(ScheduledTask task, boolean running);
/**
* Mark the scheduled task as finished in the internal list. This method should not be used in the
* application code.
* @param task task instance
*/
void setFinished(ScheduledTask task);
/**
* @return a list of active task instances in detached state
*/

View File

@ -41,7 +41,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import static java.lang.String.format;
@ -131,16 +130,23 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
}
@Override
public void applyConstraints(Collection<Entity> entities) {
List<Entity> supportedEntities = entities.stream().filter(e -> e instanceof HasUuid).collect(Collectors.toList());
internalApplyConstraints(supportedEntities, new HashSet<>());
public boolean filterByConstraints(Entity entity) {
return entity instanceof HasUuid && !isPermittedInMemory(entity);
}
@Override
public boolean applyConstraints(Entity entity) {
if (!(entity instanceof HasUuid))
return false;
return internalApplyConstraints(entity, new HashSet<>());
public void applyConstraints(Collection<Entity> entities) {
Set<UUID> handled = new LinkedHashSet<>();
entities.stream().filter(entity -> entity instanceof HasUuid).forEach(entity -> {
internalApplyConstraints(entity, handled, false);
});
}
@Override
public void applyConstraints(Entity entity) {
if (entity instanceof HasUuid) {
internalApplyConstraints(entity, new HashSet<>(), false);
}
}
@Override
@ -209,13 +215,13 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
}
} catch (JpqlSyntaxException e) {
log.error("Syntax errors found in constraint's JPQL expressions. Entity [{}]. Constraint ID [{}].",
entityName, constraint.getId(), e);
entityName, constraint.getId(), e);
throw new RowLevelSecurityException(
"Syntax errors found in constraint's JPQL expressions. Please see the logs.", entityName);
} catch (Exception e) {
log.error("An error occurred when applying security constraint. Entity [{}]. Constraint ID [{}].",
entityName, constraint.getId(), e);
entityName, constraint.getId(), e);
throw new RowLevelSecurityException(
"An error occurred when applying security constraint. Please see the logs.", entityName);
@ -226,7 +232,7 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
Set<UUID> filtered = new LinkedHashSet<>();
for (Iterator<Entity> iterator = entities.iterator(); iterator.hasNext(); ) {
Entity next = iterator.next();
if (internalApplyConstraints(next, handled)) {
if (internalApplyConstraints(next, handled, true)) {
filtered.add(((HasUuid) next).getUuid());
//we ignore situations when the collection is immutable
iterator.remove();
@ -237,10 +243,10 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
}
@SuppressWarnings("unchecked")
protected boolean internalApplyConstraints(Entity entity, Set<UUID> handled) {
protected boolean internalApplyConstraints(Entity entity, Set<UUID> handled, boolean checkPermitted) {
MetaClass metaClass = entity.getMetaClass();
if (!isPermittedInMemory(entity)) {
if (!isPermittedInMemory(entity) && checkPermitted) {
return true;
}
@ -260,7 +266,7 @@ public class PersistenceSecurityImpl extends SecurityImpl implements Persistence
}
} else if (value instanceof Entity && value instanceof HasUuid) {
Entity valueEntity = (Entity) value;
if (internalApplyConstraints(valueEntity, handled)) {
if (internalApplyConstraints(valueEntity, handled, true)) {
//we ignore the situation when the field is read-only
entity.setValue(property.getName(), null);
if (entity instanceof BaseGenericIdEntity) {

View File

@ -60,42 +60,42 @@ public class SchedulingTest {
scheduledTask.setCron("*/5 * * * * *");
//scheduler has failed couple of runs and now we should run it
long currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 0, 10000l);
long currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 10000l);
assertEquals(date("2013-11-13 15:29:55"), new Date(currentStart));
//last run was year ago, so now-frame should be considered
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2012-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 0, 10000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2012-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 10000l);
assertEquals(date("2013-11-13 15:29:55"), new Date(currentStart));
//last run was very close to now, last start date should be considered
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-13 15:29:59").getTime(), date("2013-11-13 15:30:01").getTime(), 0, 10000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-13 15:29:59").getTime(), date("2013-11-13 15:30:01").getTime(), 10000l);
assertEquals(date("2013-11-13 15:30:00"), new Date(currentStart));
scheduledTask.setCron("0 0 0 * * FRI");
//task should run in next friday
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-13 15:30:00").getTime(), 0, 10000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-13 15:30:00").getTime(), 10000l);
assertEquals(date("2013-11-15 00:00:00"), new Date(currentStart));
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-08 01:01:02").getTime(), 0, 600000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-08 01:01:02").getTime(), 600000l);
assertEquals(date("2013-11-15 00:00:00"), new Date(currentStart));
//task is late but matches frame
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:01:00").getTime(), 0, 600000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:01:00").getTime(), 600000l);
assertEquals(date("2013-11-8 00:00:00"), new Date(currentStart));
//task is late and does not match frame
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:11:00").getTime(), 0, 600000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:11:00").getTime(), 600000l);
assertEquals(date("2013-11-15 00:00:00"), new Date(currentStart));
scheduledTask.setCron("0 59 1 * * *");
//time shift forward
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-10-26 1:59:59").getTime(), date("2013-10-27 00:00:00").getTime(), 0, 600000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-10-26 1:59:59").getTime(), date("2013-10-27 00:00:00").getTime(), 600000l);
assertEquals(date("2013-10-27 01:59:00"), new Date(currentStart));
//time shift backward
currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-03-30 1:59:00").getTime(), date("2013-03-31 00:00:00").getTime(), 0, 600000l);
currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-03-30 1:59:00").getTime(), date("2013-03-31 00:00:00").getTime(), 600000l);
assertEquals(date("2013-03-31 01:59:00"), new Date(currentStart));
}

View File

@ -44,7 +44,6 @@ import java.awt.event.WindowEvent;
import java.util.*;
public class DesktopTabSheet extends DesktopAbstractComponent<JTabbedPane> implements TabSheet, DesktopContainer, AutoExpanding {
protected Map<Component, String> components = new HashMap<>();
protected List<TabImpl> tabs = new ArrayList<>();
@ -61,8 +60,16 @@ public class DesktopTabSheet extends DesktopAbstractComponent<JTabbedPane> imple
protected Set<TabChangeListener> listeners = new HashSet<>();
// CAUTION do not add ChangeListeners directly to impl
protected List<ChangeListener> implTabSheetChangeListeners = new ArrayList<>();
public DesktopTabSheet() {
impl = new JTabbedPaneExt();
impl.addChangeListener(e -> {
for (ChangeListener listener : new ArrayList<>(implTabSheetChangeListeners)) {
listener.stateChanged(e);
}
});
setWidth("100%");
}
@ -239,26 +246,16 @@ public class DesktopTabSheet extends DesktopAbstractComponent<JTabbedPane> imple
lazyTabs.add(new LazyTabInfo(tab, tabContent, descriptor, loader));
if (!initLazyTabListenerAdded) {
impl.addChangeListener(
new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
initLazyTab((JComponent) impl.getSelectedComponent());
}
}
);
implTabSheetChangeListeners.add(new LazyTabChangeListener());
initLazyTabListenerAdded = true;
}
context = loader.getContext();
if (!postInitTaskAdded) {
context.addPostInitTask(new ComponentLoader.PostInitTask() {
@Override
public void execute(ComponentLoader.Context context, Frame window) {
initComponentTabChangeListener();
}
});
context.addPostInitTask((context1, window) ->
initComponentTabChangeListener()
);
postInitTaskAdded = true;
}
return tab;
@ -374,34 +371,45 @@ public class DesktopTabSheet extends DesktopAbstractComponent<JTabbedPane> imple
// init component SelectedTabChangeListener only when needed, making sure it is
// after all lazy tabs listeners
if (!componentTabChangeListenerInitialized) {
impl.addChangeListener(e -> {
if (context != null) {
context.executeInjectTasks();
context.executePostWrapTasks();
}
// Init lazy tab if needed
initLazyTab((JComponent) impl.getSelectedComponent());
// Fire GUI listener
fireTabChanged();
// Execute outstanding post init tasks after GUI listener.
// We suppose that context.executePostInitTasks() executes a task once and then remove it from task list.
if (context != null) {
context.executePostInitTasks();
}
Window window = ComponentsHelper.getWindow(DesktopTabSheet.this);
if (window != null) {
((DsContextImplementation) window.getDsContext()).resumeSuspended();
} else {
log.warn("Please specify Frame for TabSheet");
}
});
implTabSheetChangeListeners.add(new ComponentTabChangeListener());
componentTabChangeListenerInitialized = true;
}
}
protected class ComponentTabChangeListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
if (context != null) {
context.executeInjectTasks();
context.executePostWrapTasks();
context.executeInitTasks();
}
// Fire GUI listener
fireTabChanged();
// Execute outstanding post init tasks after GUI listener.
// We suppose that context.executePostInitTasks() executes a task once and then remove it from task list.
if (context != null) {
context.executePostInitTasks();
}
Window window = ComponentsHelper.getWindow(DesktopTabSheet.this);
if (window != null) {
((DsContextImplementation) window.getDsContext()).resumeSuspended();
} else {
log.warn("Please specify Frame for TabSheet");
}
}
}
protected class LazyTabChangeListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
initLazyTab((JComponent) impl.getSelectedComponent());
}
}
protected void initLazyTab(JComponent tab) {
LazyTabInfo lti = null;
for (LazyTabInfo lazyTabInfo : lazyTabs) {

View File

@ -100,6 +100,12 @@ public class CategoryAttribute extends StandardEntity {
@Column(name = "DEFAULT_DATE_IS_CURRENT")
private Boolean defaultDateIsCurrent;
@Column(name = "WIDTH", length = 20)
private String width;
@Column(name = "ROWS_COUNT")
private Integer rowsCount;
public void setCategory(Category entityType) {
this.category = entityType;
}
@ -268,6 +274,22 @@ public class CategoryAttribute extends StandardEntity {
this.categoryEntityType = categoryEntityType;
}
public String getWidth() {
return width;
}
public void setWidth(String width) {
this.width = width;
}
public Integer getRowsCount() {
return rowsCount;
}
public void setRowsCount(Integer rowsCount) {
this.rowsCount = rowsCount;
}
public Set<String> targetScreensSet() {
if (StringUtils.isNotBlank(targetScreens)) {
return new HashSet<>(Arrays.asList(targetScreens.split(",")));

View File

@ -20,7 +20,8 @@ import com.haulmont.chile.core.datatypes.impl.EnumClass;
public enum SchedulingType implements EnumClass<String> {
CRON("C"),
PERIOD("P");
PERIOD("P"),
FIXED_DELAY("D");
private final String id;

View File

@ -99,6 +99,8 @@ CategoryAttribute.orderNo = Order No
CategoryAttribute.lookup = Select with lookup field
CategoryAttribute.targetScreens = Target screens
CategoryAttribute.defaultDateIsCurrent = Default date is current
CategoryAttribute.width=Width
CategoryAttribute.rowsCount=Rows count
ScheduledTask=Scheduled Task
ScheduledTask.beanName=Bean Name
@ -132,6 +134,7 @@ ScheduledTask.methodParamsXml = XML
SchedulingType.CRON=Cron
SchedulingType.PERIOD=Period
SchedulingType.FIXED_DELAY=Fixed Delay
ScheduledExecution = Scheduled Execution
ScheduledExecution.task = Task

View File

@ -102,6 +102,8 @@ CategoryAttribute.orderNo=Порядковый номер
CategoryAttribute.lookup=Выбирать в выпадающем списке
CategoryAttribute.targetScreens = Экраны
CategoryAttribute.defaultDateIsCurrent=Дата по умолчанию равна текущей
CategoryAttribute.width=Ширина
CategoryAttribute.rowsCount=Количество строк
ScheduledTask=Назначенное задание
ScheduledTask.beanName=Bean Name
@ -135,6 +137,7 @@ ScheduledTask.methodParamsXml = XML
SchedulingType.CRON=Cron
SchedulingType.PERIOD=Period
SchedulingType.FIXED_DELAY=Fixed Delay
ScheduledExecution = Выполнение задания
ScheduledExecution.task = Task

View File

@ -837,6 +837,10 @@ public abstract class WindowManager {
context.executeInjectTasks();
context.setFrame(component);
context.executePostWrapTasks();
// init of frame
context.executeInitTasks();
context.executePostInitTasks();
loadDescriptorWatch.stop();
@ -1020,6 +1024,8 @@ public abstract class WindowManager {
context.executePostWrapTasks();
init(wrappingWindow, params);
context.executeInitTasks();
}
protected void init(Window window, Map<String, Object> params) {

View File

@ -52,16 +52,23 @@ public class AttributeEditor extends AbstractEditor<CategoryAttribute> {
static {
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.BOOLEAN, "defaultBoolean");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.STRING, "defaultString");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.STRING, "width");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.STRING, "rowsCount");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.DOUBLE, "defaultDouble");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.DOUBLE, "width");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.INTEGER, "defaultInt");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.INTEGER, "width");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.DATE, "defaultDate");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.DATE, "defaultDateIsCurrent");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.DATE, "width");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENUMERATION, "enumeration");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENUMERATION, "defaultString");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENUMERATION, "width");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENTITY, "entityClass");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENTITY, "screen");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENTITY, "lookup");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENTITY, "defaultEntityId");
FIELDS_VISIBLE_FOR_DATATYPES.put(PropertyType.ENTITY, "width");
}
protected CategoryAttribute attribute;

View File

@ -44,6 +44,14 @@
<field id="entityClass" custom="true" required="true" width="100%"/>
<field id="screen" custom="true" width="100%"/>
<field id="lookup"/>
<field id="width" width="100%">
<validator class="com.haulmont.cuba.gui.components.validators.PatternValidator"
pattern="^(\d*(\.\d+)?)(%|px)?$" message="msg://widthValidationMsg"/>
</field>
<field id="rowsCount" width="100%">
<validator class="com.haulmont.cuba.gui.components.validators.IntegerValidator"
onlyPositive="true" message="msg://rowsCountValidationMsg"/>
</field>
<field id="defaultEntityId" custom="true" width="100%"/>
<field id="enumeration" required="true" width="100%"/>

View File

@ -63,6 +63,8 @@ entityScreenRequired=Entity screen required
enumRequired=Enumeration required
uniqueName=Attribute with same name already exists
uniqueCode=Attribute with same code already exists
widthValidationMsg=Width is incorrect
rowsCountValidationMsg=Rows count should be a positive number
msgTrue=True
msgFalse=False

View File

@ -58,6 +58,8 @@ nameRequired=Заполните поле "Название"
dataTypeRequired=Заполните поле "Тип атрибута"
entityTypeRequired=Заполните поле "Тип сущности"
entityScreenRequired=Заполните поле "Экран выбора сущности"
widthValidationMsg=Поле "Ширина" указано в некорректном формате
rowsCountValidationMsg=Поле "Количество строк" должно быть положительным числом
enumRequired=Заполните поле "Перечисление"
msgTrue=Да

View File

@ -35,6 +35,7 @@ import com.haulmont.cuba.gui.ComponentsHelper;
import com.haulmont.cuba.gui.data.CollectionDatasource;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.data.DsBuilder;
import com.haulmont.cuba.gui.data.RuntimePropsDatasource;
import com.haulmont.cuba.gui.dynamicattributes.DynamicAttributesGuiTools;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import org.apache.commons.lang.StringUtils;
@ -186,6 +187,20 @@ public abstract class AbstractFieldFactory implements FieldFactory {
textField = textArea;
}
}
if (DynamicAttributesUtils.isDynamicAttribute(property)) {
MetaClass metaClass = datasource instanceof RuntimePropsDatasource ?
((RuntimePropsDatasource) datasource).resolveCategorizedEntityClass() : datasource.getMetaClass();
MetaPropertyPath mpp = DynamicAttributesUtils.getMetaPropertyPath(metaClass, property);
if (mpp != null) {
CategoryAttribute categoryAttribute = DynamicAttributesUtils.getCategoryAttribute(mpp.getMetaProperty());
if (categoryAttribute != null && categoryAttribute.getDataType() == PropertyType.STRING
&& categoryAttribute.getRowsCount() != null && categoryAttribute.getRowsCount() > 1) {
TextArea textArea = componentsFactory.createComponent(TextArea.class);
textArea.setRows(categoryAttribute.getRowsCount());
textField = textArea;
}
}
}
if (textField == null) {
textField = componentsFactory.createComponent(TextField.class);

View File

@ -179,8 +179,17 @@ public class RuntimePropertiesFrame extends AbstractWindow {
for (DynamicAttributesMetaProperty property : metaProperties) {
FieldGroup.FieldConfig field = new FieldGroup.FieldConfig(property.getName());
CategoryAttribute attribute = property.getAttribute();
field.setCaption(attribute != null ? attribute.getName() : property.getName());
field.setWidth(fieldWidth);
if (attribute != null) {
field.setCaption(attribute.getName());
if (StringUtils.isNotBlank(attribute.getWidth())) {
field.setWidth(attribute.getWidth());
} else {
field.setWidth(fieldWidth);
}
} else {
field.setCaption(property.getName());
field.setWidth(fieldWidth);
}
fields.add(field);
Range range = property.getRange();
if (!range.isDatatype()) {

View File

@ -971,6 +971,7 @@
<xs:attributeGroup ref="hasCaption"/>
<xs:attributeGroup ref="hasVisibility"/>
<xs:attributeGroup ref="hasEnableProp"/>
<xs:attributeGroup ref="hasStyle"/>
<xs:attributeGroup ref="isUploadComponent"/>
</xs:complexType>
@ -984,6 +985,7 @@
<xs:attributeGroup ref="hasVisibility"/>
<xs:attributeGroup ref="hasEnableProp"/>
<xs:attributeGroup ref="hasIcon"/>
<xs:attributeGroup ref="hasStyle"/>
<xs:attributeGroup ref="isUploadComponent"/>
</xs:complexType>

View File

@ -44,6 +44,9 @@ public interface ComponentLoader<T extends Component> {
void addInjectTask(InjectTask task);
void executeInjectTasks();
void addInitTask(InitTask task);
void executeInitTasks();
Frame getFrame();
void setFrame(Frame frame);
@ -85,6 +88,19 @@ public interface ComponentLoader<T extends Component> {
void execute(Context context, Frame window);
}
/**
* For internal use only.
*/
interface InitTask {
/**
* This method will be invoked after window components loading before window initialization.
*
* @param context loader context
* @param window top-most window
*/
void execute(Context context, Frame window);
}
/**
* PostInitTasks are used to perform deferred initialization of visual components that requires window controller.
*/

View File

@ -37,6 +37,7 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
protected List<ComponentLoader.PostInitTask> postInitTasks = new ArrayList<>();
protected List<ComponentLoader.InjectTask> injectTasks = new ArrayList<>();
protected List<ComponentLoader.PostWrapTask> postWrapTasks = new ArrayList<>();
protected List<ComponentLoader.InitTask> initTasks = new ArrayList<>();
protected Map<String, Object> parameters;
@ -148,6 +149,18 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
}
}
@Override
public void addInitTask(ComponentLoader.InitTask task) {
initTasks.add(task);
}
@Override
public void executeInitTasks() {
if (!getInitTasks().isEmpty()) {
new InitTaskExecutor(getInitTasks().get(0)).run();
}
}
public List<ComponentLoader.InjectTask> getInjectTasks() {
return injectTasks;
}
@ -160,6 +173,10 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
return postWrapTasks;
}
public List<ComponentLoader.InitTask> getInitTasks() {
return initTasks;
}
protected void removeTask(ComponentLoader.PostInitTask task, ComponentLoaderContext context) {
if (context.getPostInitTasks().remove(task) && context.getParent() != null) {
removeTask(task, (ComponentLoaderContext) context.getParent());
@ -178,11 +195,17 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
}
}
private class TaskExecutor implements Runnable {
protected void removeTask(ComponentLoader.InitTask task, ComponentLoaderContext context) {
if (context.getInitTasks().remove(task) && context.getParent() != null) {
removeTask(task, (ComponentLoaderContext) context.getParent());
}
}
protected class TaskExecutor implements Runnable {
private final ComponentLoader.PostInitTask task;
private TaskExecutor(ComponentLoader.PostInitTask task) {
public TaskExecutor(ComponentLoader.PostInitTask task) {
this.task = task;
}
@ -196,10 +219,10 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
}
}
private class InjectTaskExecutor implements Runnable {
protected class InjectTaskExecutor implements Runnable {
private final ComponentLoader.InjectTask task;
private InjectTaskExecutor(ComponentLoader.InjectTask task) {
public InjectTaskExecutor(ComponentLoader.InjectTask task) {
this.task = task;
}
@ -213,10 +236,10 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
}
}
private class PostWrapTaskExecutor implements Runnable {
protected class PostWrapTaskExecutor implements Runnable {
private final ComponentLoader.PostWrapTask task;
private PostWrapTaskExecutor(ComponentLoader.PostWrapTask task) {
public PostWrapTaskExecutor(ComponentLoader.PostWrapTask task) {
this.task = task;
}
@ -229,4 +252,21 @@ public class ComponentLoaderContext implements ComponentLoader.Context {
}
}
}
protected class InitTaskExecutor implements Runnable {
private final ComponentLoader.InitTask task;
public InitTaskExecutor(ComponentLoader.InitTask task) {
this.task = task;
}
@Override
public void run() {
removeTask(task, ComponentLoaderContext.this);
task.execute(ComponentLoaderContext.this, frame);
if (!getInitTasks().isEmpty()) {
new InitTaskExecutor(getInitTasks().get(0)).run();
}
}
}
}

View File

@ -69,6 +69,7 @@ public class FieldGroupLoader extends AbstractComponentLoader<FieldGroup> {
messages.getMainMessagePack(),
"validation.required.defaultMsg",
attribute.getName()));
loadWidth(field, attribute.getWidth());
fields.add(field);
}
dynamicAttributesGuiTools.listenDynamicAttributesChanges(ds);
@ -197,7 +198,7 @@ public class FieldGroupLoader extends AbstractComponentLoader<FieldGroup> {
field.setFormatter(loadFormatter(element));
loadWidth(field, element);
loadWidth(field, element.attributeValue("width"));
field.setCustom(customField);
@ -215,8 +216,7 @@ public class FieldGroupLoader extends AbstractComponentLoader<FieldGroup> {
return field;
}
protected void loadWidth(FieldGroup.FieldConfig field, Element element) {
final String width = element.attributeValue("width");
protected void loadWidth(FieldGroup.FieldConfig field, String width) {
if ("auto".equalsIgnoreCase(width)) {
field.setWidth(Component.AUTO_SIZE);
} else if (StringUtils.isNotBlank(width)) {

View File

@ -62,6 +62,7 @@ public class FrameLoader<T extends Frame> extends ContainerLoader<T> {
parentContext.addInjectTask(new FrameInjectPostInitTask(wrappingFrame, params));
boolean wrapped = StringUtils.isNotBlank(rootFrameElement.attributeValue("class"));
parentContext.addInitTask(new FrameLoaderInitTask(wrappingFrame, params, wrapped));
parentContext.addPostInitTask(new FrameLoaderPostInitTask(wrappingFrame, params, wrapped));
}
@ -176,6 +177,7 @@ public class FrameLoader<T extends Frame> extends ContainerLoader<T> {
initWrapperFrame(resultComponent, element, parentContext.getParams(), parentContext);
parentContext.getInjectTasks().addAll(innerContext.getInjectTasks());
parentContext.getInitTasks().addAll(innerContext.getInitTasks());
parentContext.getPostInitTasks().addAll(innerContext.getPostInitTasks());
setContext(parentContext);
@ -229,13 +231,12 @@ public class FrameLoader<T extends Frame> extends ContainerLoader<T> {
}
}
protected class FrameLoaderPostInitTask implements PostInitTask {
protected class FrameLoaderInitTask implements InitTask {
protected final Frame frame;
protected final Map<String, Object> params;
protected final boolean wrapped;
public FrameLoaderPostInitTask(Frame frame, Map<String, Object> params, boolean wrapped) {
public FrameLoaderInitTask(Frame frame, Map<String, Object> params, boolean wrapped) {
this.frame = frame;
this.params = params;
this.wrapped = wrapped;
@ -255,6 +256,25 @@ public class FrameLoader<T extends Frame> extends ContainerLoader<T> {
initStopWatch.stop();
}
}
}
}
protected class FrameLoaderPostInitTask implements PostInitTask {
protected final Frame frame;
protected final Map<String, Object> params;
protected final boolean wrapped;
public FrameLoaderPostInitTask(Frame frame, Map<String, Object> params, boolean wrapped) {
this.frame = frame;
this.params = params;
this.wrapped = wrapped;
}
@Override
public void execute(Context context, Frame window) {
if (wrapped) {
String loggingId = ComponentsHelper.getFullFrameId(this.frame);
StopWatch uiPermissionsWatch = new Log4JStopWatch(loggingId + "#" +
UIPerformanceLogger.LifeCycle.UI_PERMISSIONS,

View File

@ -37,7 +37,6 @@ public class CubaResizableTextAreaWrapperWidget extends VCustomComponent {
protected static final int MOUSE_EVENTS = Event.ONMOUSEDOWN | Event.ONMOUSEMOVE | Event.ONMOUSEUP | Event.ONMOUSEOVER;
protected static final int MINIMAL_WIDTH = 17;
protected static final int MINIMAL_HEIGHT = 17;
public boolean isResizable() {
return resizeElement != null;
@ -132,8 +131,10 @@ public class CubaResizableTextAreaWrapperWidget extends VCustomComponent {
int absoluteLeft = getAbsoluteLeft();
int absoluteTop = getAbsoluteTop();
ComputedStyle cs = new ComputedStyle(getElement().getFirstChildElement());
//do not allow mirror-functionality
if (mouseY > absoluteTop + MINIMAL_HEIGHT && mouseX > absoluteLeft + MINIMAL_WIDTH) {
if (mouseY > absoluteTop + cs.getDoubleProperty("min-height") && mouseX > absoluteLeft + MINIMAL_WIDTH) {
int width = mouseX - absoluteLeft + 2;
int height = mouseY - absoluteTop + 2;

View File

@ -404,6 +404,7 @@ public class WebAccordion extends WebAbstractComponent<CubaAccordion> implements
if (context != null) {
context.executeInjectTasks();
context.executePostWrapTasks();
context.executeInitTasks();
}
// Fire GUI listener
fireTabChanged();

View File

@ -310,7 +310,9 @@ public class WebTabSheet extends WebAbstractComponent<CubaTabSheet> implements T
context = loader.getContext();
if (!postInitTaskAdded) {
context.addPostInitTask((context1, window) -> initComponentTabChangeListener());
context.addPostInitTask((context1, window) ->
initComponentTabChangeListener()
);
postInitTaskAdded = true;
}
@ -435,6 +437,7 @@ public class WebTabSheet extends WebAbstractComponent<CubaTabSheet> implements T
if (context != null) {
context.executeInjectTasks();
context.executePostWrapTasks();
context.executeInitTasks();
}
// Fire GUI listener
fireTabChanged();

View File

@ -21,6 +21,10 @@
.cuba-resizabletextarea-wrapper {
position: relative;
overflow: hidden;
.v-textarea {
min-height: $v-unit-size;
}
}
.#{$primary-stylename}-resize-corner {

View File

@ -18,6 +18,10 @@
.cuba-resizabletextarea-wrapper {
position: relative;
overflow: hidden;
.v-textarea {
min-height: 15px;
}
}
.#{$primary-stylename}-resize-corner {