Moving qqq-backend-core into its own subdir

This commit is contained in:
2022-07-28 12:02:54 -05:00
parent 1c3fafbf73
commit adfe472b70
217 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ActionHelper
{
/*******************************************************************************
**
*******************************************************************************/
public static void validateSession(AbstractActionInput request) throws QException
{
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
if(!authenticationModule.isSessionValid(request.getSession()))
{
throw new QAuthenticationException("Invalid session in request");
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.async;
/*******************************************************************************
** Interface to be implemented (as lambdas), for working with AsyncJobManager.
*******************************************************************************/
@FunctionalInterface
public interface AsyncJob<T>
{
/*******************************************************************************
** Run the job, taking a callback object (where you can communicate your status
** back), returning a result when you're done..
*******************************************************************************/
T run(AsyncJobCallback callback) throws Exception;
}

View File

@@ -0,0 +1,110 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.async;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
/*******************************************************************************
** Argument passed to an AsyncJob when it runs, which can be used to communicate
** data back out of the job.
**
** TODO - future - allow cancellation to be indicated here?
*******************************************************************************/
public class AsyncJobCallback
{
private UUID jobUUID;
private AsyncJobStatus asyncJobStatus;
/*******************************************************************************
**
*******************************************************************************/
public AsyncJobCallback(UUID jobUUID, AsyncJobStatus asyncJobStatus)
{
this.jobUUID = jobUUID;
this.asyncJobStatus = asyncJobStatus;
}
/*******************************************************************************
** Update the message
*******************************************************************************/
public void updateStatus(String message)
{
this.asyncJobStatus.setMessage(message);
storeUpdatedStatus();
}
/*******************************************************************************
** Update all 3 status fields
*******************************************************************************/
public void updateStatus(String message, int current, int total)
{
this.asyncJobStatus.setMessage(message);
this.asyncJobStatus.setCurrent(current);
this.asyncJobStatus.setTotal(total);
storeUpdatedStatus();
}
/*******************************************************************************
** Update the current and total fields (e.g., 1 of 2, 2 of 2, 3 of 2)
*******************************************************************************/
public void updateStatus(int current, int total)
{
this.asyncJobStatus.setCurrent(current);
this.asyncJobStatus.setTotal(total);
storeUpdatedStatus();
}
/*******************************************************************************
** Remove the values from the current & total fields
*******************************************************************************/
public void clearCurrentAndTotal()
{
this.asyncJobStatus.setCurrent(null);
this.asyncJobStatus.setTotal(null);
storeUpdatedStatus();
}
/*******************************************************************************
**
*******************************************************************************/
protected void storeUpdatedStatus()
{
AsyncJobManager.getStateProvider().put(new UUIDAndTypeStateKey(jobUUID, StateType.ASYNC_JOB_STATUS), asyncJobStatus);
}
}

View File

@@ -0,0 +1,186 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.async;
import java.io.Serializable;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Class to manage running asynchronous actions, and working with their statuses.
*******************************************************************************/
public class AsyncJobManager
{
private static final Logger LOG = LogManager.getLogger(AsyncJobManager.class);
/*******************************************************************************
** Start a job - if it finishes within the specified timeout, get its results,
** else, get back an exception with the job id.
*******************************************************************************/
public <T extends Serializable> T startJob(long timeout, TimeUnit timeUnit, AsyncJob<T> asyncJob) throws JobGoingAsyncException, QException
{
return (startJob("Anonymous", timeout, timeUnit, asyncJob));
}
/*******************************************************************************
** Start a job - if it finishes within the specified timeout, get its results,
** else, get back an exception with the job id.
*******************************************************************************/
public <T extends Serializable> T startJob(String jobName, long timeout, TimeUnit timeUnit, AsyncJob<T> asyncJob) throws JobGoingAsyncException, QException
{
UUIDAndTypeStateKey uuidAndTypeStateKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.ASYNC_JOB_STATUS);
AsyncJobStatus asyncJobStatus = new AsyncJobStatus();
asyncJobStatus.setState(AsyncJobState.RUNNING);
getStateProvider().put(uuidAndTypeStateKey, asyncJobStatus);
try
{
CompletableFuture<T> future = CompletableFuture.supplyAsync(() ->
{
return (runAsyncJob(jobName, asyncJob, uuidAndTypeStateKey, asyncJobStatus));
});
if(timeout == 0)
{
throw (new JobGoingAsyncException(uuidAndTypeStateKey.getUuid().toString()));
}
T result = future.get(timeout, timeUnit);
return (result);
}
catch(InterruptedException | ExecutionException e)
{
throw (new QException("Error running job", e));
}
catch(TimeoutException e)
{
LOG.info("Job going async " + uuidAndTypeStateKey.getUuid());
throw (new JobGoingAsyncException(uuidAndTypeStateKey.getUuid().toString()));
}
}
/*******************************************************************************
** Start a job, and always, just get back the job UUID.
*******************************************************************************/
public <T extends Serializable> String startJob(AsyncJob<T> asyncJob) throws QException
{
return (startJob("Anonymous", asyncJob));
}
/*******************************************************************************
** Start a job, and always, just get back the job UUID.
*******************************************************************************/
public <T extends Serializable> String startJob(String jobName, AsyncJob<T> asyncJob) throws QException
{
try
{
startJob(jobName, 0, TimeUnit.MILLISECONDS, asyncJob);
throw (new QException("Job was expected to go asynchronous, but did not"));
}
catch(JobGoingAsyncException jgae)
{
return (jgae.getJobUUID());
}
}
/*******************************************************************************
** run the job.
*******************************************************************************/
private <T extends Serializable> T runAsyncJob(String jobName, AsyncJob<T> asyncJob, UUIDAndTypeStateKey uuidAndTypeStateKey, AsyncJobStatus asyncJobStatus)
{
String originalThreadName = Thread.currentThread().getName();
Thread.currentThread().setName("Job:" + jobName + ":" + uuidAndTypeStateKey.getUuid().toString().substring(0, 8));
try
{
LOG.info("Starting job " + uuidAndTypeStateKey.getUuid());
T result = asyncJob.run(new AsyncJobCallback(uuidAndTypeStateKey.getUuid(), asyncJobStatus));
asyncJobStatus.setState(AsyncJobState.COMPLETE);
getStateProvider().put(uuidAndTypeStateKey, asyncJobStatus);
LOG.info("Completed job " + uuidAndTypeStateKey.getUuid());
return (result);
}
catch(Exception e)
{
asyncJobStatus.setState(AsyncJobState.ERROR);
asyncJobStatus.setCaughtException(e);
getStateProvider().put(uuidAndTypeStateKey, asyncJobStatus);
LOG.warn("Job " + uuidAndTypeStateKey.getUuid() + " ended with an exception: ", e);
throw (new CompletionException(e));
}
finally
{
Thread.currentThread().setName(originalThreadName);
}
}
/*******************************************************************************
** Get the status of the job identified by the given UUID.
**
*******************************************************************************/
public Optional<AsyncJobStatus> getJobStatus(String uuid)
{
UUIDAndTypeStateKey uuidAndTypeStateKey = new UUIDAndTypeStateKey(UUID.fromString(uuid), StateType.ASYNC_JOB_STATUS);
return (getStateProvider().get(AsyncJobStatus.class, uuidAndTypeStateKey));
}
/*******************************************************************************
** Load an instance of the appropriate state provider
**
*******************************************************************************/
static StateProviderInterface getStateProvider()
{
// TODO - read this from somewhere in meta data eh?
return InMemoryStateProvider.getInstance();
// todo - by using JSON serialization internally, this makes stupidly large payloads and crashes things.
// return TempFileStateProvider.getInstance();
}
}

View File

@@ -0,0 +1,33 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.async;
/*******************************************************************************
** Possible states for an async job's "running"-ness.
*******************************************************************************/
public enum AsyncJobState
{
RUNNING,
COMPLETE,
ERROR
}

View File

@@ -0,0 +1,166 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.async;
import java.io.Serializable;
/*******************************************************************************
** Object to track current status of an async job - e.g., its state, and some
** messages from the backend like "x of y"
*******************************************************************************/
public class AsyncJobStatus implements Serializable
{
private AsyncJobState state;
private String message;
private Integer current;
private Integer total;
private Exception caughtException;
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return "AsyncJobStatus{"
+ "state=" + state
+ ", message='" + message + '\''
+ ", current=" + current
+ ", total=" + total
+ ", caughtException=" + caughtException
+ '}';
}
/*******************************************************************************
** Getter for state
**
*******************************************************************************/
public AsyncJobState getState()
{
return state;
}
/*******************************************************************************
** Setter for state
**
*******************************************************************************/
public void setState(AsyncJobState state)
{
this.state = state;
}
/*******************************************************************************
** Getter for message
**
*******************************************************************************/
public String getMessage()
{
return message;
}
/*******************************************************************************
** Setter for message
**
*******************************************************************************/
public void setMessage(String message)
{
this.message = message;
}
/*******************************************************************************
** Getter for current
**
*******************************************************************************/
public Integer getCurrent()
{
return current;
}
/*******************************************************************************
** Setter for current
**
*******************************************************************************/
public void setCurrent(Integer current)
{
this.current = current;
}
/*******************************************************************************
** Getter for total
**
*******************************************************************************/
public Integer getTotal()
{
return total;
}
/*******************************************************************************
** Setter for total
**
*******************************************************************************/
public void setTotal(Integer total)
{
this.total = total;
}
/*******************************************************************************
** Getter for caughtException
**
*******************************************************************************/
public Exception getCaughtException()
{
return caughtException;
}
/*******************************************************************************
** Setter for caughtException
**
*******************************************************************************/
public void setCaughtException(Exception caughtException)
{
this.caughtException = caughtException;
}
}

View File

@@ -0,0 +1,56 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.async;
/*******************************************************************************
** Exception thrown by AsyncJobManager, not to indicate an error, per se, but
** rather to indicate that a job has taken too long, as is now "going async".
**
** So, this exception contains the jobUUID.
*******************************************************************************/
public class JobGoingAsyncException extends Exception
{
private String jobUUID;
/*******************************************************************************
**
*******************************************************************************/
public JobGoingAsyncException(String jobUUID)
{
this.jobUUID = jobUUID;
}
/*******************************************************************************
** Getter for jobUUID
**
*******************************************************************************/
public String getJobUUID()
{
return jobUUID;
}
}

View File

@@ -0,0 +1,40 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
/*******************************************************************************
** Interface for the Count action.
**
*******************************************************************************/
public interface CountInterface
{
/*******************************************************************************
**
*******************************************************************************/
CountOutput execute(CountInput countInput) throws QException;
}

View File

@@ -0,0 +1,53 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
/*******************************************************************************
** Interface for the Delete action.
**
*******************************************************************************/
public interface DeleteInterface
{
/*******************************************************************************
**
*******************************************************************************/
DeleteOutput execute(DeleteInput deleteInput) throws QException;
/*******************************************************************************
** Specify whether this particular module's delete action natively supports
** receiving a queryFilter as input (e.g., SQL does). If the module doesn't
** support a query filter, then the qqq framework (DeleteAction) will, if it
** receives a queryFilter in its input, it will execute the query, and pass
** the list of primary keys down into the module's delete implementation.
*******************************************************************************/
default boolean supportsQueryFilterInput()
{
return (false);
}
}

View File

@@ -0,0 +1,40 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
/*******************************************************************************
** Interface for the Insert action.
**
*******************************************************************************/
public interface InsertInterface
{
/*******************************************************************************
**
*******************************************************************************/
InsertOutput execute(InsertInput insertInput) throws QException;
}

View File

@@ -0,0 +1,40 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
/*******************************************************************************
** Interface for the Query action.
**
*******************************************************************************/
public interface QueryInterface
{
/*******************************************************************************
**
*******************************************************************************/
QueryOutput execute(QueryInput queryInput) throws QException;
}

View File

@@ -0,0 +1,40 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.interfaces;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
/*******************************************************************************
** Interface for the update action.
**
*******************************************************************************/
public interface UpdateInterface
{
/*******************************************************************************
**
*******************************************************************************/
UpdateOutput execute(UpdateInput updateInput) throws QException;
}

View File

@@ -0,0 +1,71 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.metadata;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Action to fetch top-level meta-data in a qqq instance.
**
*******************************************************************************/
public class MetaDataAction
{
/*******************************************************************************
**
*******************************************************************************/
public MetaDataOutput execute(MetaDataInput metaDataInput) throws QException
{
ActionHelper.validateSession(metaDataInput);
// todo pre-customization - just get to modify the request?
MetaDataOutput metaDataOutput = new MetaDataOutput();
Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>();
for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet())
{
tables.put(entry.getKey(), new QFrontendTableMetaData(entry.getValue(), false));
}
metaDataOutput.setTables(tables);
Map<String, QFrontendProcessMetaData> processes = new LinkedHashMap<>();
for(Map.Entry<String, QProcessMetaData> entry : metaDataInput.getInstance().getProcesses().entrySet())
{
processes.put(entry.getKey(), new QFrontendProcessMetaData(entry.getValue(), false));
}
metaDataOutput.setProcesses(processes);
// todo post-customization - can do whatever w/ the result if you want
return metaDataOutput;
}
}

View File

@@ -0,0 +1,61 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.metadata;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
/*******************************************************************************
** Action to fetch meta-data for a process.
**
*******************************************************************************/
public class ProcessMetaDataAction
{
/*******************************************************************************
**
*******************************************************************************/
public ProcessMetaDataOutput execute(ProcessMetaDataInput processMetaDataInput) throws QException
{
ActionHelper.validateSession(processMetaDataInput);
// todo pre-customization - just get to modify the request?
ProcessMetaDataOutput processMetaDataOutput = new ProcessMetaDataOutput();
QProcessMetaData process = processMetaDataInput.getInstance().getProcess(processMetaDataInput.getProcessName());
if(process == null)
{
throw (new QNotFoundException("Process [" + processMetaDataInput.getProcessName() + "] was not found."));
}
processMetaDataOutput.setProcess(new QFrontendProcessMetaData(process, true));
// todo post-customization - can do whatever w/ the result if you want
return processMetaDataOutput;
}
}

View File

@@ -0,0 +1,61 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.metadata;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Action to fetch meta-data for a table.
**
*******************************************************************************/
public class TableMetaDataAction
{
/*******************************************************************************
**
*******************************************************************************/
public TableMetaDataOutput execute(TableMetaDataInput tableMetaDataInput) throws QException
{
ActionHelper.validateSession(tableMetaDataInput);
// todo pre-customization - just get to modify the request?
TableMetaDataOutput tableMetaDataOutput = new TableMetaDataOutput();
QTableMetaData table = tableMetaDataInput.getInstance().getTable(tableMetaDataInput.getTableName());
if(table == null)
{
throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found."));
}
tableMetaDataOutput.setTable(new QFrontendTableMetaData(table, true));
// todo post-customization - can do whatever w/ the result if you want
return tableMetaDataOutput;
}
}

View File

@@ -0,0 +1,44 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.processes;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
/*******************************************************************************
** Simple interface that Backend Steps (e.g., code within processes) must implement
**
*******************************************************************************/
public interface BackendStep
{
/*******************************************************************************
** Execute the backend step - using the request as input, and the result as output.
**
** TODO - think about - why take the Result object as a param, instead of return it?
** Is this way easier for inter-language operability maybe?
* Also - there's way too much "process-specific gunk" in the Request object - can we simplify it?
*******************************************************************************/
void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException;
}

View File

@@ -0,0 +1,48 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.processes;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
** When a process/function can't run because it's missing data, this interface
** defines how the core framework goes back to a middleware (and possibly to a
** frontend) to get the data.
*******************************************************************************/
public interface QProcessCallback
{
/*******************************************************************************
** Get the filter query for this callback.
*******************************************************************************/
QQueryFilter getQueryFilter();
/*******************************************************************************
** Get the field values for this callback.
*******************************************************************************/
Map<String, Serializable> getFieldValues(List<QFieldMetaData> fields);
}

View File

@@ -0,0 +1,232 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.processes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Action handler for running backend steps as part of processes.
*
*******************************************************************************/
public class RunBackendStepAction
{
private static final Logger LOG = LogManager.getLogger(RunBackendStepAction.class);
/*******************************************************************************
**
*******************************************************************************/
public RunBackendStepOutput execute(RunBackendStepInput runBackendStepInput) throws QException
{
ActionHelper.validateSession(runBackendStepInput);
QProcessMetaData process = runBackendStepInput.getInstance().getProcess(runBackendStepInput.getProcessName());
if(process == null)
{
throw new QException("Process [" + runBackendStepInput.getProcessName() + "] is not defined in this instance.");
}
QStepMetaData stepMetaData = process.getStep(runBackendStepInput.getStepName());
if(stepMetaData == null)
{
throw new QException("Step [" + runBackendStepInput.getStepName() + "] is not defined in the process [" + process.getName() + "]");
}
if(!(stepMetaData instanceof QBackendStepMetaData backendStepMetaData))
{
throw new QException("Step [" + runBackendStepInput.getStepName() + "] is not a backend step.");
}
//////////////////////////////////////////////////////////////////////////////////////
// ensure input data is set as needed - use callback object to get anything missing //
//////////////////////////////////////////////////////////////////////////////////////
ensureRecordsAreInRequest(runBackendStepInput, backendStepMetaData);
ensureInputFieldsAreInRequest(runBackendStepInput, backendStepMetaData);
////////////////////////////////////////////////////////////////////
// load and run the user-defined code that actually does the work //
////////////////////////////////////////////////////////////////////
return (runStepCode(backendStepMetaData.getCode(), runBackendStepInput));
}
/*******************************************************************************
** check if this step needs any input fields - and if so, if we need to get one
** via the callback
**
*******************************************************************************/
private void ensureInputFieldsAreInRequest(RunBackendStepInput runBackendStepInput, QBackendStepMetaData step) throws QException
{
QFunctionInputMetaData inputMetaData = step.getInputMetaData();
if(inputMetaData == null)
{
return;
}
List<QFieldMetaData> fieldsToGet = new ArrayList<>();
List<QFieldMetaData> requiredFieldsMissing = new ArrayList<>();
for(QFieldMetaData field : inputMetaData.getFieldList())
{
Serializable value = runBackendStepInput.getValue(field.getName());
if(value == null)
{
if(field.getDefaultValue() != null)
{
runBackendStepInput.addValue(field.getName(), field.getDefaultValue());
}
else
{
fieldsToGet.add(field);
if(field.getIsRequired())
{
requiredFieldsMissing.add(field);
}
}
}
}
if(!fieldsToGet.isEmpty())
{
QProcessCallback callback = runBackendStepInput.getCallback();
if(callback == null)
{
if(requiredFieldsMissing.isEmpty())
{
///////////////////////////////////////////////////////////////
// if no required fields are missing, just return gracefully //
///////////////////////////////////////////////////////////////
return;
}
///////////////////////////////////////////////////////////////////
// but if any required fields ARE missing, then that's an error. //
///////////////////////////////////////////////////////////////////
LOG.info("Missing value for required fields: " + requiredFieldsMissing.stream().map(QFieldMetaData::getName).collect(Collectors.joining(", ")));
throw (new QUserFacingException("Missing values for one or more fields",
new QException("Function is missing values for fields, but no callback was present to request fields from a user")));
}
Map<String, Serializable> fieldValues = callback.getFieldValues(fieldsToGet);
if(fieldValues != null)
{
for(Map.Entry<String, Serializable> entry : fieldValues.entrySet())
{
runBackendStepInput.addValue(entry.getKey(), entry.getValue());
// todo - check to make sure got values back?
}
}
}
}
/*******************************************************************************
** check if this step uses a record list - and if so, if we need to get one
** via the callback
*******************************************************************************/
private void ensureRecordsAreInRequest(RunBackendStepInput runBackendStepInput, QBackendStepMetaData step) throws QException
{
QFunctionInputMetaData inputMetaData = step.getInputMetaData();
if(inputMetaData != null && inputMetaData.getRecordListMetaData() != null)
{
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
{
QueryInput queryInput = new QueryInput(runBackendStepInput.getInstance());
queryInput.setSession(runBackendStepInput.getSession());
queryInput.setTableName(inputMetaData.getRecordListMetaData().getTableName());
// todo - handle this being async (e.g., http)
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
// then this step can re-run, hopefully with the needed data.
QProcessCallback callback = runBackendStepInput.getCallback();
if(callback == null)
{
throw (new QUserFacingException("Missing input records.",
new QException("Function is missing input records, but no callback was present to request fields from a user")));
}
queryInput.setFilter(callback.getQueryFilter());
QueryOutput queryOutput = new QueryAction().execute(queryInput);
runBackendStepInput.setRecords(queryOutput.getRecords());
// todo - handle 0 results found?
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private RunBackendStepOutput runStepCode(QCodeReference code, RunBackendStepInput runBackendStepInput)
{
RunBackendStepOutput runBackendStepOutput = new RunBackendStepOutput();
try
{
runBackendStepOutput.seedFromRequest(runBackendStepInput);
Class<?> codeClass = Class.forName(code.getName());
Object codeObject = codeClass.getConstructor().newInstance();
if(!(codeObject instanceof BackendStep backendStepCodeObject))
{
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of FunctionBody"));
}
backendStepCodeObject.run(runBackendStepInput, runBackendStepOutput);
}
catch(Exception e)
{
runBackendStepOutput = new RunBackendStepOutput();
runBackendStepOutput.setException(e);
LOG.info("Error running backend step code", e);
}
return (runBackendStepOutput);
}
}

View File

@@ -0,0 +1,332 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.processes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Action handler for running q-processes (which are a sequence of q-functions).
*
*******************************************************************************/
public class RunProcessAction
{
private static final Logger LOG = LogManager.getLogger(RunProcessAction.class);
/*******************************************************************************
**
*******************************************************************************/
public RunProcessOutput execute(RunProcessInput runProcessInput) throws QException
{
ActionHelper.validateSession(runProcessInput);
QProcessMetaData process = runProcessInput.getInstance().getProcess(runProcessInput.getProcessName());
if(process == null)
{
throw new QException("Process [" + runProcessInput.getProcessName() + "] is not defined in this instance.");
}
RunProcessOutput runProcessOutput = new RunProcessOutput();
//////////////////////////////////////////////////////////
// generate a UUID for the process, if one wasn't given //
//////////////////////////////////////////////////////////
if(runProcessInput.getProcessUUID() == null)
{
runProcessInput.setProcessUUID(UUID.randomUUID().toString());
}
runProcessOutput.setProcessUUID(runProcessInput.getProcessUUID());
UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.fromString(runProcessInput.getProcessUUID()), StateType.PROCESS_STATUS);
ProcessState processState = primeProcessState(runProcessInput, stateKey);
// todo - custom routing
List<QStepMetaData> stepList = getAvailableStepList(process, runProcessInput);
try
{
STEP_LOOP:
for(QStepMetaData step : stepList)
{
if(step instanceof QFrontendStepMetaData)
{
////////////////////////////////////////////////////////////////
// Handle what to do with frontend steps, per request setting //
////////////////////////////////////////////////////////////////
switch(runProcessInput.getFrontendStepBehavior())
{
case BREAK ->
{
LOG.info("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processState.setNextStepName(step.getName());
break STEP_LOOP;
}
case SKIP ->
{
LOG.info("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
//////////////////////////////////////////////////////////////////////
// much less error prone in case this code changes in the future... //
//////////////////////////////////////////////////////////////////////
// noinspection UnnecessaryContinue
continue;
}
case FAIL ->
{
LOG.info("Throwing error for frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
throw (new QException("Failing process at step " + step.getName() + " (as requested, to fail on frontend steps)"));
}
default -> throw new IllegalStateException("Unexpected value: " + runProcessInput.getFrontendStepBehavior());
}
}
else if(step instanceof QBackendStepMetaData backendStepMetaData)
{
///////////////////////
// Run backend steps //
///////////////////////
runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState);
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
}
}
catch(QException qe)
{
////////////////////////////////////////////////////////////
// upon exception (e.g., one thrown by a step), throw it. //
////////////////////////////////////////////////////////////
throw (qe);
}
catch(Exception e)
{
////////////////////////////////////////////////////////////
// upon exception (e.g., one thrown by a step), throw it. //
////////////////////////////////////////////////////////////
throw (new QException("Error running process", e));
}
finally
{
//////////////////////////////////////////////////////
// always put the final state in the process result //
//////////////////////////////////////////////////////
runProcessOutput.setProcessState(processState);
}
return (runProcessOutput);
}
/*******************************************************************************
** When we start running a process (or resuming it), get data in the RunProcessRequest
** either from the state provider (if they're found, for a resume).
*******************************************************************************/
ProcessState primeProcessState(RunProcessInput runProcessInput, UUIDAndTypeStateKey stateKey) throws QException
{
Optional<ProcessState> optionalProcessState = loadState(stateKey);
if(optionalProcessState.isEmpty())
{
if(runProcessInput.getStartAfterStep() == null)
{
///////////////////////////////////////////////////////////////////////////////////
// this is fine - it means its our first time running in the backend. //
// Go ahead and store the state that we have (e.g., w/ initial records & values) //
///////////////////////////////////////////////////////////////////////////////////
storeState(stateKey, runProcessInput.getProcessState());
optionalProcessState = Optional.of(runProcessInput.getProcessState());
}
else
{
////////////////////////////////////////////////////////////////////////////////////////
// if this isn't the first step, but there's no state, then that's a problem, so fail //
////////////////////////////////////////////////////////////////////////////////////////
throw (new QException("Could not find state for process [" + runProcessInput.getProcessName() + "] [" + stateKey.getUuid() + "] in state provider."));
}
}
else
{
//////////////////////////////////////////////////////////////////////////////////////////////////////
// capture any values that the caller may have supplied in the request, before restoring from state //
//////////////////////////////////////////////////////////////////////////////////////////////////////
Map<String, Serializable> valuesFromCaller = runProcessInput.getValues();
///////////////////////////////////////////////////
// if there is a previously stored state, use it //
///////////////////////////////////////////////////
runProcessInput.seedFromProcessState(optionalProcessState.get());
///////////////////////////////////////////////////////////////////////////
// if there were values from the caller, put those (back) in the request //
///////////////////////////////////////////////////////////////////////////
if(valuesFromCaller != null)
{
for(Map.Entry<String, Serializable> entry : valuesFromCaller.entrySet())
{
runProcessInput.addValue(entry.getKey(), entry.getValue());
}
}
}
ProcessState processState = optionalProcessState.get();
processState.clearNextStepName();
return processState;
}
/*******************************************************************************
** Run a single backend step.
*******************************************************************************/
private void runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
{
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(runProcessInput.getInstance(), processState);
runBackendStepInput.setProcessName(process.getName());
runBackendStepInput.setStepName(backendStep.getName());
runBackendStepInput.setTableName(process.getTableName());
runBackendStepInput.setSession(runProcessInput.getSession());
runBackendStepInput.setCallback(runProcessInput.getCallback());
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());
RunBackendStepOutput lastFunctionResult = new RunBackendStepAction().execute(runBackendStepInput);
storeState(stateKey, lastFunctionResult.getProcessState());
if(lastFunctionResult.getException() != null)
{
runProcessOutput.setException(lastFunctionResult.getException());
throw (lastFunctionResult.getException());
}
}
/*******************************************************************************
** Get the list of steps which are eligible to run.
*******************************************************************************/
private List<QStepMetaData> getAvailableStepList(QProcessMetaData process, RunProcessInput runProcessInput)
{
if(runProcessInput.getStartAfterStep() == null)
{
/////////////////////////////////////////////////////////////////////////////
// if the caller did not supply a 'startAfterStep', then use the full list //
/////////////////////////////////////////////////////////////////////////////
return (process.getStepList());
}
else
{
////////////////////////////////////////////////////////////////////////////////
// else, loop until the startAfterStep is found, and return the ones after it //
////////////////////////////////////////////////////////////////////////////////
boolean foundStartAfterStep = false;
List<QStepMetaData> rs = new ArrayList<>();
for(QStepMetaData step : process.getStepList())
{
if(foundStartAfterStep)
{
rs.add(step);
}
if(step.getName().equals(runProcessInput.getStartAfterStep()))
{
foundStartAfterStep = true;
}
}
return (rs);
}
}
/*******************************************************************************
** Load an instance of the appropriate state provider
**
*******************************************************************************/
public static StateProviderInterface getStateProvider()
{
// TODO - read this from somewhere in meta data eh?
return InMemoryStateProvider.getInstance();
// todo - by using JSON serialization internally, this makes stupidly large payloads and crashes things.
// return TempFileStateProvider.getInstance();
}
/*******************************************************************************
** public method to get a process state just by UUID.
*******************************************************************************/
public static Optional<ProcessState> getState(String processUUID)
{
return (getStateProvider().get(ProcessState.class, new UUIDAndTypeStateKey(UUID.fromString(processUUID), StateType.PROCESS_STATUS)));
}
/*******************************************************************************
** Store the process state from a function result to the state provider
**
*******************************************************************************/
private void storeState(UUIDAndTypeStateKey stateKey, ProcessState processState)
{
getStateProvider().put(stateKey, processState);
}
/*******************************************************************************
** Load the process state.
**
*******************************************************************************/
private Optional<ProcessState> loadState(UUIDAndTypeStateKey stateKey)
{
return (getStateProvider().get(ProcessState.class, stateKey));
}
}

View File

@@ -0,0 +1,143 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.kingsrook.qqq.backend.core.adapters.QRecordToCsvAdapter;
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** CSV report format implementation
*******************************************************************************/
public class CsvReportStreamer implements ReportStreamerInterface
{
private static final Logger LOG = LogManager.getLogger(CsvReportStreamer.class);
private final QRecordToCsvAdapter qRecordToCsvAdapter;
private ReportInput reportInput;
private QTableMetaData table;
private List<QFieldMetaData> fields;
private OutputStream outputStream;
/*******************************************************************************
**
*******************************************************************************/
public CsvReportStreamer()
{
qRecordToCsvAdapter = new QRecordToCsvAdapter();
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void start(ReportInput reportInput, List<QFieldMetaData> fields) throws QReportingException
{
this.reportInput = reportInput;
this.fields = fields;
table = reportInput.getTable();
outputStream = this.reportInput.getReportOutputStream();
writeReportHeaderRow();
}
/*******************************************************************************
**
*******************************************************************************/
private void writeReportHeaderRow() throws QReportingException
{
try
{
int col = 0;
for(QFieldMetaData column : fields)
{
if(col++ > 0)
{
outputStream.write(',');
}
outputStream.write(('"' + column.getLabel() + '"').getBytes(StandardCharsets.UTF_8));
}
outputStream.write('\n');
outputStream.flush();
}
catch(Exception e)
{
throw (new QReportingException("Error starting CSV report"));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException
{
List<QRecord> qRecords = recordPipe.consumeAvailableRecords();
LOG.info("Consuming [" + qRecords.size() + "] records from the pipe");
try
{
for(QRecord qRecord : qRecords)
{
String csv = qRecordToCsvAdapter.recordToCsv(table, qRecord, fields);
outputStream.write(csv.getBytes(StandardCharsets.UTF_8));
outputStream.flush(); // todo - less often?
}
return (qRecords.size());
}
catch(Exception e)
{
throw (new QReportingException("Error writing CSV report", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void finish()
{
}
}

View File

@@ -0,0 +1,214 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.OutputStream;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dhatim.fastexcel.Workbook;
import org.dhatim.fastexcel.Worksheet;
/*******************************************************************************
** Excel report format implementation
*******************************************************************************/
public class ExcelReportStreamer implements ReportStreamerInterface
{
private static final Logger LOG = LogManager.getLogger(ExcelReportStreamer.class);
private ReportInput reportInput;
private QTableMetaData table;
private List<QFieldMetaData> fields;
private OutputStream outputStream;
private Workbook workbook;
private Worksheet worksheet;
private int row = 1;
/*******************************************************************************
**
*******************************************************************************/
public ExcelReportStreamer()
{
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void start(ReportInput reportInput, List<QFieldMetaData> fields) throws QReportingException
{
this.reportInput = reportInput;
this.fields = fields;
table = reportInput.getTable();
outputStream = this.reportInput.getReportOutputStream();
workbook = new Workbook(outputStream, "QQQ", null);
worksheet = workbook.newWorksheet("Sheet 1");
writeReportHeaderRow();
}
/*******************************************************************************
**
*******************************************************************************/
private void writeReportHeaderRow() throws QReportingException
{
try
{
int col = 0;
for(QFieldMetaData column : fields)
{
worksheet.value(0, col, column.getLabel());
col++;
}
worksheet.flush();
}
catch(Exception e)
{
throw (new QReportingException("Error starting Excel report"));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException
{
List<QRecord> qRecords = recordPipe.consumeAvailableRecords();
LOG.info("Consuming [" + qRecords.size() + "] records from the pipe");
try
{
for(QRecord qRecord : qRecords)
{
int col = 0;
for(QFieldMetaData column : fields)
{
Serializable value = qRecord.getValue(column.getName());
if(value != null)
{
if(value instanceof String s)
{
worksheet.value(row, col, s);
}
else if(value instanceof Number n)
{
worksheet.value(row, col, n);
}
else if(value instanceof Boolean b)
{
worksheet.value(row, col, b);
}
else if(value instanceof Date d)
{
worksheet.value(row, col, d);
worksheet.style(row, col).format("yyyy-MM-dd").set();
}
else if(value instanceof LocalDate d)
{
worksheet.value(row, col, d);
worksheet.style(row, col).format("yyyy-MM-dd").set();
}
else if(value instanceof LocalDateTime d)
{
worksheet.value(row, col, d);
worksheet.style(row, col).format("yyyy-MM-dd H:mm:ss").set();
}
else if(value instanceof ZonedDateTime d)
{
worksheet.value(row, col, d);
worksheet.style(row, col).format("yyyy-MM-dd H:mm:ss").set();
}
else
{
worksheet.value(row, col, ValueUtils.getValueAsString(value));
}
}
col++;
}
row++;
worksheet.flush(); // todo? not at all? or just sometimes?
}
}
catch(Exception e)
{
try
{
workbook.finish();
outputStream.close();
}
finally
{
throw (new QReportingException("Error generating Excel report", e));
}
}
return (qRecords.size());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void finish() throws QReportingException
{
try
{
if(workbook != null)
{
workbook.finish();
}
}
catch(Exception e)
{
throw (new QReportingException("Error finishing Excel report", e));
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Object to connect a producer of records with a consumer.
** Best for those to be on different threads, to avoid deadlock.
*******************************************************************************/
public class RecordPipe
{
private ArrayBlockingQueue<QRecord> queue = new ArrayBlockingQueue<>(10_000);
/*******************************************************************************
** Add a record to the pipe
** Returns true iff the record fit in the pipe; false if the pipe is currently full.
*******************************************************************************/
public boolean addRecord(QRecord record)
{
return (queue.offer(record));
}
/*******************************************************************************
** Add a list of records to the pipe
*******************************************************************************/
public void addRecords(List<QRecord> records)
{
queue.addAll(records);
}
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> consumeAvailableRecords()
{
List<QRecord> rs = new ArrayList<>();
while(true)
{
QRecord record = queue.poll();
if(record == null)
{
break;
}
rs.add(record);
}
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/
public int countAvailableRecords()
{
return (queue.size());
}
}

View File

@@ -0,0 +1,324 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobState;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Action to generate a report.
**
** At this time (future may change?), this action starts a new thread to run
** the query in the backend module. As records are produced by the query,
** they are put into a RecordPipe, which the ReportStreamer pulls from, to write
** to the report output stream. This action will block until the query job
** is complete, and the final records have been consumed from the pipe, at which
** time the report outputStream can be closed.
**
*******************************************************************************/
public class ReportAction
{
private static final Logger LOG = LogManager.getLogger(ReportAction.class);
private boolean preExecuteRan = false;
private Integer countFromPreExecute = null;
private static final int TIMEOUT_AFTER_NO_RECORDS_MS = 10 * 60 * 1000;
private static final int MAX_SLEEP_MS = 1000;
private static final int INIT_SLEEP_MS = 10;
/*******************************************************************************
** Validation logic, that will run before the action is executed -- ideally, if
** a caller is going to run the execution in a thread, they'd call this method
** first, in their thread, to catch any validation errors before they start
** the thread (which they may abandon).
*******************************************************************************/
public void preExecute(ReportInput reportInput) throws QException
{
ActionHelper.validateSession(reportInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface backendModule = qBackendModuleDispatcher.getQBackendModule(reportInput.getBackend());
///////////////////////////////////
// verify field names (if given) //
///////////////////////////////////
if(CollectionUtils.nullSafeHasContents(reportInput.getFieldNames()))
{
QTableMetaData table = reportInput.getTable();
List<String> badFieldNames = new ArrayList<>();
for(String fieldName : reportInput.getFieldNames())
{
try
{
table.getField(fieldName);
}
catch(IllegalArgumentException iae)
{
badFieldNames.add(fieldName);
}
}
if(!badFieldNames.isEmpty())
{
throw (new QUserFacingException(badFieldNames.size() == 1
? ("Field name " + badFieldNames.get(0) + " was not found on the " + table.getLabel() + " table.")
: ("Fields names " + StringUtils.joinWithCommasAndAnd(badFieldNames) + " were not found on the " + table.getLabel() + " table.")));
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// check if this report format has a max-rows limit -- if so, do a count to verify we're under the limit //
///////////////////////////////////////////////////////////////////////////////////////////////////////////
ReportFormat reportFormat = reportInput.getReportFormat();
verifyCountUnderMax(reportInput, backendModule, reportFormat);
preExecuteRan = true;
}
/*******************************************************************************
** Run the report.
*******************************************************************************/
public ReportOutput execute(ReportInput reportInput) throws QException
{
if(!preExecuteRan)
{
/////////////////////////////////////
// ensure that pre-execute has ran //
/////////////////////////////////////
preExecute(reportInput);
}
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface backendModule = qBackendModuleDispatcher.getQBackendModule(reportInput.getBackend());
//////////////////////////
// set up a query input //
//////////////////////////
QueryInterface queryInterface = backendModule.getQueryInterface();
QueryInput queryInput = new QueryInput(reportInput.getInstance());
queryInput.setSession(reportInput.getSession());
queryInput.setTableName(reportInput.getTableName());
queryInput.setFilter(reportInput.getQueryFilter());
queryInput.setLimit(reportInput.getLimit());
/////////////////////////////////////////////////////////////////
// tell this query that it needs to put its output into a pipe //
/////////////////////////////////////////////////////////////////
RecordPipe recordPipe = new RecordPipe();
queryInput.setRecordPipe(recordPipe);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// set up a report streamer, which will read rows from the pipe, and write formatted report rows to the output stream //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ReportFormat reportFormat = reportInput.getReportFormat();
ReportStreamerInterface reportStreamer = reportFormat.newReportStreamer();
reportStreamer.start(reportInput, getFields(reportInput));
//////////////////////////////////////////
// run the query action as an async job //
//////////////////////////////////////////
AsyncJobManager asyncJobManager = new AsyncJobManager();
String queryJobUUID = asyncJobManager.startJob("ReportAction>QueryAction", (status) -> (queryInterface.execute(queryInput)));
LOG.info("Started query job [" + queryJobUUID + "] for report");
AsyncJobState queryJobState = AsyncJobState.RUNNING;
AsyncJobStatus asyncJobStatus = null;
long recordCount = 0;
int nextSleepMillis = INIT_SLEEP_MS;
long lastReceivedRecordsAt = System.currentTimeMillis();
long reportStartTime = System.currentTimeMillis();
while(queryJobState.equals(AsyncJobState.RUNNING))
{
if(recordPipe.countAvailableRecords() == 0)
{
///////////////////////////////////////////////////////////
// if the pipe is empty, sleep to let the producer work. //
// todo - smarter sleep? like get notified vs. sleep? //
///////////////////////////////////////////////////////////
LOG.info("No records are available in the pipe. Sleeping [" + nextSleepMillis + "] ms to give producer a chance to work");
SleepUtils.sleep(nextSleepMillis, TimeUnit.MILLISECONDS);
nextSleepMillis = Math.min(nextSleepMillis * 2, MAX_SLEEP_MS);
long timeSinceLastReceivedRecord = System.currentTimeMillis() - lastReceivedRecordsAt;
if(timeSinceLastReceivedRecord > TIMEOUT_AFTER_NO_RECORDS_MS)
{
throw (new QReportingException("Query action appears to have stopped producing records (last record received " + timeSinceLastReceivedRecord + " ms ago)."));
}
}
else
{
////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the pipe has records, consume them. reset the sleep timer so if we sleep again it'll be short. //
////////////////////////////////////////////////////////////////////////////////////////////////////////
lastReceivedRecordsAt = System.currentTimeMillis();
nextSleepMillis = INIT_SLEEP_MS;
int recordsConsumed = reportStreamer.takeRecordsFromPipe(recordPipe);
recordCount += recordsConsumed;
LOG.info(countFromPreExecute != null
? String.format("Processed %,d of %,d records so far", recordCount, countFromPreExecute)
: String.format("Processed %,d records so far", recordCount));
}
////////////////////////////////////
// refresh the query job's status //
////////////////////////////////////
Optional<AsyncJobStatus> optionalAsyncJobStatus = asyncJobManager.getJobStatus(queryJobUUID);
if(optionalAsyncJobStatus.isEmpty())
{
/////////////////////////////////////////////////
// todo - ... maybe some version of try-again? //
/////////////////////////////////////////////////
throw (new QException("Could not get status of report query job [" + queryJobUUID + "]"));
}
asyncJobStatus = optionalAsyncJobStatus.get();
queryJobState = asyncJobStatus.getState();
}
LOG.info("Query job [" + queryJobUUID + "] for report completed with status: " + asyncJobStatus);
///////////////////////////////////////////////////
// send the final records to the report streamer //
///////////////////////////////////////////////////
int recordsConsumed = reportStreamer.takeRecordsFromPipe(recordPipe);
recordCount += recordsConsumed;
long reportEndTime = System.currentTimeMillis();
LOG.info((countFromPreExecute != null
? String.format("Processed %,d of %,d records", recordCount, countFromPreExecute)
: String.format("Processed %,d records", recordCount))
+ String.format(" at end of report in %,d ms (%.2f records/second).", (reportEndTime - reportStartTime), 1000d * (recordCount / (.001d + (reportEndTime - reportStartTime)))));
//////////////////////////////////////////////////////////////////
// Critical: we must close the stream here as our final action //
//////////////////////////////////////////////////////////////////
reportStreamer.finish();
try
{
reportInput.getReportOutputStream().close();
}
catch(Exception e)
{
throw (new QReportingException("Error completing report", e));
}
ReportOutput reportOutput = new ReportOutput();
reportOutput.setRecordCount(recordCount);
return (reportOutput);
}
/*******************************************************************************
**
*******************************************************************************/
private List<QFieldMetaData> getFields(ReportInput reportInput)
{
QTableMetaData table = reportInput.getTable();
if(reportInput.getFieldNames() != null)
{
return (reportInput.getFieldNames().stream().map(table::getField).toList());
}
else
{
return (new ArrayList<>(table.getFields().values()));
}
}
/*******************************************************************************
**
*******************************************************************************/
private void verifyCountUnderMax(ReportInput reportInput, QBackendModuleInterface backendModule, ReportFormat reportFormat) throws QException
{
if(reportFormat.getMaxCols() != null)
{
List<QFieldMetaData> fields = getFields(reportInput);
if (fields.size() > reportFormat.getMaxCols())
{
throw (new QUserFacingException("The requested report would include more columns ("
+ String.format("%,d", fields.size()) + ") than the maximum allowed ("
+ String.format("%,d", reportFormat.getMaxCols()) + ") for the selected file format (" + reportFormat + ")."));
}
}
if(reportFormat.getMaxRows() != null)
{
if(reportInput.getLimit() == null || reportInput.getLimit() > reportFormat.getMaxRows())
{
CountInterface countInterface = backendModule.getCountInterface();
CountInput countInput = new CountInput(reportInput.getInstance());
countInput.setSession(reportInput.getSession());
countInput.setTableName(reportInput.getTableName());
countInput.setFilter(reportInput.getQueryFilter());
CountOutput countOutput = countInterface.execute(countInput);
countFromPreExecute = countOutput.getCount();
if(countFromPreExecute > reportFormat.getMaxRows())
{
throw (new QUserFacingException("The requested report would include more rows ("
+ String.format("%,d", countFromPreExecute) + ") than the maximum allowed ("
+ String.format("%,d", reportFormat.getMaxRows()) + ") for the selected file format (" + reportFormat + ")."));
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
** Interface for various report formats to implement.
*******************************************************************************/
public interface ReportStreamerInterface
{
/*******************************************************************************
** Called once, before any rows are available. Meant to write a header, for example.
*******************************************************************************/
void start(ReportInput reportInput, List<QFieldMetaData> fields) throws QReportingException;
/*******************************************************************************
** Called as records flow into the pipe.
******************************************************************************/
int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException;
/*******************************************************************************
** Called once, after all rows are available. Meant to write a footer, or close resources, for example.
*******************************************************************************/
void finish() throws QReportingException;
}

View File

@@ -0,0 +1,53 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
/*******************************************************************************
** Action to run a count against a table.
**
*******************************************************************************/
public class CountAction
{
/*******************************************************************************
**
*******************************************************************************/
public CountOutput execute(CountInput countInput) throws QException
{
ActionHelper.validateSession(countInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(countInput.getBackend());
// todo pre-customization - just get to modify the request?
CountOutput countOutput = qModule.getCountInterface().execute(countInput);
// todo post-customization - can do whatever w/ the result if you want
return countOutput;
}
}

View File

@@ -0,0 +1,121 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Action to delete 1 or more records.
**
*******************************************************************************/
public class DeleteAction
{
private static final Logger LOG = LogManager.getLogger(DeleteAction.class);
/*******************************************************************************
**
*******************************************************************************/
public DeleteOutput execute(DeleteInput deleteInput) throws QException
{
ActionHelper.validateSession(deleteInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
// todo pre-customization - just get to modify the request?
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
{
throw (new QException("A delete request may not contain both a list of primary keys and a query filter."));
}
DeleteInterface deleteInterface = qModule.getDeleteInterface();
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
{
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
List<Serializable> primaryKeyList = getPrimaryKeysFromQueryFilter(deleteInput);
deleteInput.setPrimaryKeys(primaryKeyList);
if(primaryKeyList.isEmpty())
{
LOG.info("0 primaryKeys found. Returning with no-op");
DeleteOutput deleteOutput = new DeleteOutput();
deleteOutput.setRecordsWithErrors(new ArrayList<>());
deleteOutput.setDeletedRecordCount(0);
return (deleteOutput);
}
}
DeleteOutput deleteResult = deleteInterface.execute(deleteInput);
// todo post-customization - can do whatever w/ the result if you want
return deleteResult;
}
/*******************************************************************************
** For an implementation that doesn't support a queryFilter as its input,
** but a scenario where a query filter was passed in - run the query, to
** get a list of primary keys.
*******************************************************************************/
public static List<Serializable> getPrimaryKeysFromQueryFilter(DeleteInput deleteInput) throws QException
{
try
{
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
QueryInput queryInput = new QueryInput(deleteInput.getInstance(), deleteInput.getSession());
queryInput.setTableName(deleteInput.getTableName());
queryInput.setFilter(deleteInput.getQueryFilter());
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
return (queryOutput.getRecords().stream()
.map(r -> r.getValue(deleteInput.getTable().getPrimaryKeyField()))
.toList());
}
catch(Exception e)
{
LOG.warn("Error getting primary keys from query filter before bulk-delete", e);
throw (new QException("Error getting keys from filter prior to delete.", e));
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Action to insert one or more records.
**
*******************************************************************************/
public class InsertAction
{
private static final Logger LOG = LogManager.getLogger(InsertAction.class);
/*******************************************************************************
**
*******************************************************************************/
public InsertOutput execute(InsertInput insertInput) throws QException
{
ActionHelper.validateSession(insertInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(insertInput.getBackend());
// todo pre-customization - just get to modify the request?
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
// todo post-customization - can do whatever w/ the result if you want
return insertOutput;
}
}

View File

@@ -0,0 +1,53 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
/*******************************************************************************
** Action to run a query against a table.
**
*******************************************************************************/
public class QueryAction
{
/*******************************************************************************
**
*******************************************************************************/
public QueryOutput execute(QueryInput queryInput) throws QException
{
ActionHelper.validateSession(queryInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
// todo pre-customization - just get to modify the request?
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
// todo post-customization - can do whatever w/ the result if you want
return queryOutput;
}
}

View File

@@ -0,0 +1,53 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
/*******************************************************************************
** Action to update one or more records.
**
*******************************************************************************/
public class UpdateAction
{
/*******************************************************************************
**
*******************************************************************************/
public UpdateOutput execute(UpdateInput updateInput) throws QException
{
ActionHelper.validateSession(updateInput);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
// todo pre-customization - just get to modify the request?
UpdateOutput updateResult = qModule.getUpdateInterface().execute(updateInput);
// todo post-customization - can do whatever w/ the result if you want
return updateResult;
}
}

View File

@@ -0,0 +1,186 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.adapters;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
/*******************************************************************************
** Adapter class to convert a CSV string into a list of QRecords.
**
*******************************************************************************/
public class CsvToQRecordAdapter
{
/*******************************************************************************
** convert a CSV String into a List of QRecords, for a given table, optionally
** using a given mapping.
**
** todo - meta-data validation, type handling
*******************************************************************************/
public List<QRecord> buildRecordsFromCsv(String csv, QTableMetaData table, AbstractQFieldMapping<?> mapping)
{
if(!StringUtils.hasContent(csv))
{
throw (new IllegalArgumentException("Empty csv value was provided."));
}
List<QRecord> rs = new ArrayList<>();
try
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there's no mapping (e.g., table-standard field names), or key-based mapping, then first row is headers //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(mapping == null || AbstractQFieldMapping.SourceType.KEY.equals(mapping.getSourceType()))
{
CSVParser csvParser = new CSVParser(new StringReader(csv),
CSVFormat.DEFAULT
.withFirstRecordAsHeader()
.withIgnoreHeaderCase()
.withTrim());
List<String> headers = csvParser.getHeaderNames();
headers = makeHeadersUnique(headers);
List<CSVRecord> csvRecords = csvParser.getRecords();
for(CSVRecord csvRecord : csvRecords)
{
//////////////////////////////////////////////////////////////////
// put values from the CSV record into a map of header -> value //
//////////////////////////////////////////////////////////////////
Map<String, String> csvValues = new HashMap<>();
for(int i=0; i<headers.size(); i++)
{
csvValues.put(headers.get(i), csvRecord.get(i));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// now move values into the QRecord, using the mapping to get the 'header' corresponding to each QField //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
QRecord qRecord = new QRecord();
rs.add(qRecord);
for(QFieldMetaData field : table.getFields().values())
{
String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
qRecord.setValue(field.getName(), csvValues.get(fieldSource));
}
}
}
else if(AbstractQFieldMapping.SourceType.INDEX.equals(mapping.getSourceType()))
{
///////////////////////////////
// else, index-based mapping //
///////////////////////////////
CSVParser csvParser = new CSVParser(new StringReader(csv),
CSVFormat.DEFAULT
.withTrim());
List<CSVRecord> csvRecords = csvParser.getRecords();
for(CSVRecord csvRecord : csvRecords)
{
/////////////////////////////////////////////////////////////////
// put values from the CSV record into a map of index -> value //
/////////////////////////////////////////////////////////////////
Map<Integer, String> csvValues = new HashMap<>();
int index = 1;
for(String value : csvRecord)
{
csvValues.put(index++, value);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// now move values into the QRecord, using the mapping to get the 'header' corresponding to each QField //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
QRecord qRecord = new QRecord();
rs.add(qRecord);
for(QFieldMetaData field : table.getFields().values())
{
Integer fieldIndex = (Integer) mapping.getFieldSource(field.getName());
qRecord.setValue(field.getName(), csvValues.get(fieldIndex));
}
}
}
else
{
throw (new IllegalArgumentException("Unrecognized mapping source type: " + mapping.getSourceType()));
}
}
catch(IOException e)
{
throw (new IllegalArgumentException("Error parsing CSV: " + e.getMessage(), e));
}
return (rs);
}
/*******************************************************************************
** For a list of headers, if any duplicates are found, add a numeric suffix
** to the duplicates.
**
** So this header row: A,B,C,C,C
** Would become: A,B,C,C 2,C 3
**
** See unit test for more scenarios - some of which we do not handle well yet,
** such as "C 2, C, C 3"
*******************************************************************************/
protected List<String> makeHeadersUnique(List<String> headers)
{
Map<String, Integer> countsByHeader = new HashMap<>();
List<String> rs = new ArrayList<>();
for(String header : headers)
{
String headerToUse = header;
String headerWithoutSuffix = header.replaceFirst(" \\d+$", "");
if(countsByHeader.containsKey(headerWithoutSuffix))
{
int suffix = countsByHeader.get(headerWithoutSuffix) + 1;
countsByHeader.put(headerWithoutSuffix, suffix);
headerToUse = headerWithoutSuffix + " " + suffix;
}
else
{
countsByHeader.put(headerWithoutSuffix, 1);
}
rs.add(headerToUse);
}
return (rs);
}
}

View File

@@ -0,0 +1,152 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.adapters;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QIndexBasedFieldMapping;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;
/*******************************************************************************
** Adapter class to convert a JSON string into a QFieldMapping object
**
*******************************************************************************/
public class JsonToQFieldMappingAdapter
{
/*******************************************************************************
** adapts a json string into an AbstractQFieldMapping.
**
** The mapping will be a QKeyBasedFieldMapping if the keys in the json object are
** Strings. It will be a QIndexBasedFieldMapping if the keys are integers.
**
*******************************************************************************/
public AbstractQFieldMapping<?> buildMappingFromJson(String json)
{
if(!StringUtils.hasContent(json))
{
throw (new IllegalArgumentException("Empty json value was provided."));
}
try
{
JSONObject jsonObject = JsonUtils.toJSONObject(json);
jsonObject = promoteInnerMappingIfAppropriate(jsonObject);
//////////////////////////////////////////////////////////////////////////////////////////////
// look at the keys in the mapping - if they're strings, then we're doing key-based mapping //
// if they're numbers, then we're doing index based -- and if they're a mix, that's illegal //
//////////////////////////////////////////////////////////////////////////////////////////////
AbstractQFieldMapping.SourceType sourceType = determineSourceType(jsonObject);
@SuppressWarnings("rawtypes")
AbstractQFieldMapping mapping = null;
switch(sourceType)
{
case KEY:
{
mapping = new QKeyBasedFieldMapping();
for(String fieldName : jsonObject.keySet())
{
((QKeyBasedFieldMapping) mapping).addMapping(fieldName, jsonObject.getString(fieldName));
}
break;
}
case INDEX:
{
mapping = new QIndexBasedFieldMapping();
for(String fieldName : jsonObject.keySet())
{
((QIndexBasedFieldMapping) mapping).addMapping(fieldName, jsonObject.getInt(fieldName));
}
break;
}
default:
{
throw (new IllegalArgumentException("Unsupported sourceType: " + sourceType));
}
}
return (mapping);
}
catch(JSONException je)
{
throw (new IllegalArgumentException("Malformed JSON value: " + je.getMessage(), je));
}
}
/*******************************************************************************
** So - this class was first written assuming that the JSON it would take would
** just be a mapping - e.g., {a:b, c:d} or {a:0, b:1}.
**
** But - it turns out, callers may expect that they can create an instance of
** AbstractQFieldMapping, then serialize it, then de-serialize it, and that seems sane.
**
** So - this method tries to determine if the JSON Object we took in looks like
** a serialized from of a AbstractQFieldMapping - and if so, then it "promotes"
** the "mapping" object from within that outer json object, since the rest of
** this class knows how to (and expects to) handle that object.
*******************************************************************************/
private JSONObject promoteInnerMappingIfAppropriate(JSONObject jsonObject)
{
if(jsonObject.has("mapping") && jsonObject.has("sourceType") && jsonObject.keySet().size() == 2)
{
return (jsonObject.getJSONObject("mapping"));
}
return (jsonObject);
}
/*******************************************************************************
**
*******************************************************************************/
private AbstractQFieldMapping.SourceType determineSourceType(JSONObject jsonObject)
{
for(String fieldName : jsonObject.keySet())
{
Object sourceObject = jsonObject.get(fieldName);
if(sourceObject instanceof String)
{
return (AbstractQFieldMapping.SourceType.KEY);
}
else if(sourceObject instanceof Integer)
{
return (AbstractQFieldMapping.SourceType.INDEX);
}
else
{
throw new IllegalArgumentException("Source object is unsupported type: " + sourceObject.getClass().getSimpleName());
}
}
throw new IllegalArgumentException("No fields were found in the mapping.");
}
}

View File

@@ -0,0 +1,119 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.adapters;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/*******************************************************************************
** Adapter class to convert a JSON string into a list of QRecords.
**
*******************************************************************************/
public class JsonToQRecordAdapter
{
/*******************************************************************************
** convert a JSON String into a List of QRecords, for a given table, optionally
** using a given mapping.
**
** todo - meta-data validation, use the mapping, type handling
*******************************************************************************/
public List<QRecord> buildRecordsFromJson(String json, QTableMetaData table, AbstractQFieldMapping<?> mapping)
{
if(!StringUtils.hasContent(json))
{
throw (new IllegalArgumentException("Empty json value was provided."));
}
List<QRecord> rs = new ArrayList<>();
try
{
if(JsonUtils.looksLikeObject(json))
{
JSONObject jsonObject = JsonUtils.toJSONObject(json);
rs.add(buildRecordFromJsonObject(jsonObject, table, mapping));
}
else if(JsonUtils.looksLikeArray(json))
{
JSONArray jsonArray = JsonUtils.toJSONArray(json);
for(Object object : jsonArray)
{
if(object instanceof JSONObject jsonObject)
{
rs.add(buildRecordFromJsonObject(jsonObject, table, mapping));
}
else
{
throw (new IllegalArgumentException("Element at index " + rs.size() + " in json array was not a json object."));
}
}
}
else
{
throw (new IllegalArgumentException("Malformed JSON value - did not start with '{' or '['."));
}
}
catch(JSONException je)
{
throw (new IllegalArgumentException("Malformed JSON value: " + je.getMessage(), je));
}
return (rs);
}
/*******************************************************************************
** private method to build one QRecord from one jsonObject
**
** todo - meta-data validation, type handling
*******************************************************************************/
private QRecord buildRecordFromJsonObject(JSONObject jsonObject, QTableMetaData table, AbstractQFieldMapping<?> mapping)
{
QRecord record = new QRecord();
for(QFieldMetaData field : table.getFields().values())
{
String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
// todo - so if the mapping didn't say how to map this field, does that mean we should use the default name for the field?
if(jsonObject.has(fieldSource))
{
record.setValue(field.getName(), (Serializable) jsonObject.get(fieldSource));
}
}
return (record);
}
}

View File

@@ -0,0 +1,97 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.adapters;
import java.io.IOException;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import org.json.JSONObject;
/*******************************************************************************
** Methods for adapting qInstances to serialized (string) formats (e.g., json),
** and vice versa.
*******************************************************************************/
public class QInstanceAdapter
{
/*******************************************************************************
** Convert a qInstance to JSON.
**
*******************************************************************************/
public String qInstanceToJson(QInstance qInstance)
{
return (JsonUtils.toJson(qInstance));
}
/*******************************************************************************
** Convert a qInstance to JSON.
**
*******************************************************************************/
public String qInstanceToJsonIncludingBackend(QInstance qInstance)
{
String jsonString = JsonUtils.toJson(qInstance);
JSONObject jsonObject = JsonUtils.toJSONObject(jsonString);
String backendsJsonString = JsonUtils.toJson(qInstance.getBackends());
JSONObject backendsJsonObject = JsonUtils.toJSONObject(backendsJsonString);
jsonObject.put("backends", backendsJsonObject);
return (jsonObject.toString());
}
/*******************************************************************************
** Build a qInstance from JSON.
**
*******************************************************************************/
public QInstance jsonToQInstance(String json) throws IOException
{
return (JsonUtils.toObject(json, QInstance.class));
}
/*******************************************************************************
** Build a qInstance from JSON.
**
*******************************************************************************/
public QInstance jsonToQInstanceIncludingBackends(String json) throws IOException
{
QInstance qInstance = JsonUtils.toObject(json, QInstance.class);
JSONObject jsonObject = JsonUtils.toJSONObject(json);
JSONObject backendsJsonObject = jsonObject.getJSONObject("backends");
Map<String, QBackendMetaData> backends = JsonUtils.toObject(backendsJsonObject.toString(), new TypeReference<>()
{
});
qInstance.setBackends(backends);
return qInstance;
}
}

View File

@@ -0,0 +1,87 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.adapters;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** Class to convert QRecords to CSV Strings.
*******************************************************************************/
public class QRecordToCsvAdapter
{
/*******************************************************************************
**
*******************************************************************************/
public String recordToCsv(QTableMetaData table, QRecord record)
{
return (recordToCsv(table, record, new ArrayList<>(table.getFields().values())));
}
/*******************************************************************************
**
*******************************************************************************/
public String recordToCsv(QTableMetaData table, QRecord record, List<QFieldMetaData> fields)
{
StringBuilder rs = new StringBuilder();
int fieldNo = 0;
for(QFieldMetaData field : fields)
{
if(fieldNo++ > 0)
{
rs.append(',');
}
rs.append('"');
Serializable value = record.getValue(field.getName());
String valueAsString = ValueUtils.getValueAsString(value);
if(StringUtils.hasContent(valueAsString))
{
rs.append(sanitize(valueAsString));
}
rs.append('"');
}
rs.append('\n');
return (rs.toString());
}
/*******************************************************************************
** todo - kinda weak... can we find this in a CSV lib??
*******************************************************************************/
private String sanitize(String value)
{
return (value.replaceAll("\"", "\"\"").replaceAll("\n", " "));
}
}

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
* Exception thrown doing authentication
*
*******************************************************************************/
public class QAuthenticationException extends QException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QAuthenticationException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QAuthenticationException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
* Base class for checked exceptions thrown in qqq.
*
*******************************************************************************/
public class QException extends Exception
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,109 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
import java.util.Arrays;
import java.util.List;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Exception thrown during qqq-starup, if a QInstance is found to have validation
** issues. Contains a list of reasons (to avoid spoon-feeding as much as possible).
**
*******************************************************************************/
public class QInstanceValidationException extends QException
{
private List<String> reasons;
/*******************************************************************************
** Constructor of message - does not populate reasons!
**
*******************************************************************************/
public QInstanceValidationException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of a list of reasons. They feed into the core exception message.
**
*******************************************************************************/
public QInstanceValidationException(List<String> reasons)
{
super(
(reasons != null && reasons.size() > 0)
? "Instance validation failed for the following reasons: " + StringUtils.joinWithCommasAndAnd(reasons)
: "Validation failed, but no reasons were provided");
if(reasons != null && reasons.size() > 0)
{
this.reasons = reasons;
}
}
/*******************************************************************************
** Constructor of an array/varargs of reasons. They feed into the core exception message.
**
*******************************************************************************/
public QInstanceValidationException(String... reasons)
{
super(
(reasons != null && reasons.length > 0)
? "Instance validation failed for the following reasons: " + StringUtils.joinWithCommasAndAnd(Arrays.stream(reasons).toList())
: "Validation failed, but no reasons were provided");
if(reasons != null && reasons.length > 0)
{
this.reasons = Arrays.stream(reasons).toList();
}
}
/*******************************************************************************
** Constructor of message & cause - does not populate reasons!
**
*******************************************************************************/
public QInstanceValidationException(String message, Throwable cause)
{
super(message, cause);
}
/*******************************************************************************
** Getter for reasons
**
*******************************************************************************/
public List<String> getReasons()
{
return reasons;
}
}

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
* Exception thrown while doing module-dispatch
*
*******************************************************************************/
public class QModuleDispatchException extends QException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QModuleDispatchException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QModuleDispatchException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,54 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
** User-facing exception for when something wasn't found (e.g., a named table or
** record-by-id).
**
*******************************************************************************/
public class QNotFoundException extends QUserFacingException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QNotFoundException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QNotFoundException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
* Exception thrown while generating reports
*
*******************************************************************************/
public class QReportingException extends QException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QReportingException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QReportingException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,53 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
** Exception with a good-quality message meant to be shown to an end-user.
**
*******************************************************************************/
public class QUserFacingException extends QException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QUserFacingException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QUserFacingException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,54 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.exceptions;
/*******************************************************************************
** Exception for when there's a problem with a value (like a string that you need
** to be an integer, but it isn't).
**
*******************************************************************************/
public class QValueException extends RuntimeException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QValueException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QValueException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -0,0 +1,43 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*******************************************************************************
** Class-level annotation to declare what fields should run through the variable
** interpreter - e.g., to be replaced with env-var values at run-time.
*******************************************************************************/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InterpretableFields
{
/*******************************************************************************
** list of field names in the class that are interpretable.
*******************************************************************************/
String[] fieldNames() default {};
}

View File

@@ -0,0 +1,406 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteStoreStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditReceiveValuesStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditStoreRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** As part of helping a QInstance be created and/or validated, apply some default
** transformations to it, such as populating missing labels based on names.
**
*******************************************************************************/
public class QInstanceEnricher
{
/*******************************************************************************
**
*******************************************************************************/
public void enrich(QInstance qInstance)
{
if(qInstance.getTables() != null)
{
qInstance.getTables().values().forEach(this::enrich);
defineTableBulkProcesses(qInstance);
}
if(qInstance.getProcesses() != null)
{
qInstance.getProcesses().values().forEach(this::enrich);
}
if(qInstance.getBackends() != null)
{
qInstance.getBackends().values().forEach(this::enrich);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void enrich(QBackendMetaData qBackendMetaData)
{
qBackendMetaData.enrich();
}
/*******************************************************************************
**
*******************************************************************************/
private void enrich(QTableMetaData table)
{
if(!StringUtils.hasContent(table.getLabel()))
{
table.setLabel(nameToLabel(table.getName()));
}
if(table.getFields() != null)
{
table.getFields().values().forEach(this::enrich);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void enrich(QProcessMetaData process)
{
if(!StringUtils.hasContent(process.getLabel()))
{
process.setLabel(nameToLabel(process.getName()));
}
if(process.getStepList() != null)
{
process.getStepList().forEach(this::enrich);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void enrich(QStepMetaData step)
{
if(!StringUtils.hasContent(step.getLabel()))
{
step.setLabel(nameToLabel(step.getName()));
}
step.getInputFields().forEach(this::enrich);
step.getOutputFields().forEach(this::enrich);
if(step instanceof QFrontendStepMetaData frontendStepMetaData)
{
if(frontendStepMetaData.getFormFields() != null)
{
frontendStepMetaData.getFormFields().forEach(this::enrich);
}
if(frontendStepMetaData.getViewFields() != null)
{
frontendStepMetaData.getViewFields().forEach(this::enrich);
}
if(frontendStepMetaData.getRecordListFields() != null)
{
frontendStepMetaData.getRecordListFields().forEach(this::enrich);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private void enrich(QFieldMetaData field)
{
if(!StringUtils.hasContent(field.getLabel()))
{
field.setLabel(nameToLabel(field.getName()));
}
}
/*******************************************************************************
**
*******************************************************************************/
private String nameToLabel(String name)
{
if(name == null)
{
return (null);
}
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
}
/*******************************************************************************
** Add bulk insert/edit/delete processes to all tables (unless the meta data
** already had these processes defined (e.g., the user defined custom ones)
*******************************************************************************/
private void defineTableBulkProcesses(QInstance qInstance)
{
for(QTableMetaData table : qInstance.getTables().values())
{
if(table.getFields() == null)
{
/////////////////////////////////////////////////////////////////
// these processes can't be defined if there aren't any fields //
/////////////////////////////////////////////////////////////////
continue;
}
// todo - add idea of 'supportsBulkX'
String bulkInsertProcessName = table.getName() + ".bulkInsert";
if(qInstance.getProcess(bulkInsertProcessName) == null)
{
defineTableBulkInsert(qInstance, table, bulkInsertProcessName);
}
String bulkEditProcessName = table.getName() + ".bulkEdit";
if(qInstance.getProcess(bulkEditProcessName) == null)
{
defineTableBulkEdit(qInstance, table, bulkEditProcessName);
}
String bulkDeleteProcessName = table.getName() + ".bulkDelete";
if(qInstance.getProcess(bulkDeleteProcessName) == null)
{
defineTableBulkDelete(qInstance, table, bulkDeleteProcessName);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private void defineTableBulkInsert(QInstance qInstance, QTableMetaData table, String processName)
{
List<QFieldMetaData> editableFields = table.getFields().values().stream()
.filter(QFieldMetaData::getIsEditable)
.toList();
String fieldsForHelpText = editableFields.stream()
.map(QFieldMetaData::getLabel)
.collect(Collectors.joining(", "));
QFrontendStepMetaData uploadScreen = new QFrontendStepMetaData()
.withName("upload")
.withLabel("Upload File")
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB).withIsRequired(true))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
// .withValue("text", "Upload a CSV or XLSX file with the following columns: " + fieldsForHelpText));
.withValue("text", "Upload a CSV file with the following columns: " + fieldsForHelpText));
QBackendStepMetaData receiveFileStep = new QBackendStepMetaData()
.withName("receiveFile")
.withCode(new QCodeReference(BulkInsertReceiveFileStep.class))
.withOutputMetaData(new QFunctionOutputMetaData()
.withFieldList(List.of(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER))));
QFrontendStepMetaData reviewScreen = new QFrontendStepMetaData()
.withName("review")
.withRecordListFields(editableFields)
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below were parsed from your file, and will be inserted if you click Submit."))
.withViewField(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER).withLabel("# of file rows"));
QBackendStepMetaData storeStep = new QBackendStepMetaData()
.withName("storeRecords")
.withCode(new QCodeReference(BulkInsertStoreRecordsStep.class))
.withOutputMetaData(new QFunctionOutputMetaData()
.withFieldList(List.of(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER))));
QFrontendStepMetaData resultsScreen = new QFrontendStepMetaData()
.withName("results")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below have been inserted."))
.withViewField(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER).withLabel("# of file rows"));
qInstance.addProcess(
new QProcessMetaData()
.withName(processName)
.withLabel(table.getLabel() + " Bulk Insert")
.withTableName(table.getName())
.withIsHidden(true)
.withStepList(List.of(
uploadScreen,
receiveFileStep,
reviewScreen,
storeStep,
resultsScreen
)));
}
/*******************************************************************************
**
*******************************************************************************/
private void defineTableBulkEdit(QInstance qInstance, QTableMetaData table, String processName)
{
List<QFieldMetaData> editableFields = table.getFields().values().stream()
.filter(QFieldMetaData::getIsEditable)
.toList();
QFrontendStepMetaData editScreen = new QFrontendStepMetaData()
.withName("edit")
.withLabel("Edit Values")
.withFormFields(editableFields)
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", """
Flip the switches next to the fields that you want to edit.
The values you supply here will be updated in all of the records you are bulk editing.
You can clear out the value in a field by flipping the switch on for that field and leaving the input field blank.
Fields whose switches are off will not be updated."""))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.BULK_EDIT_FORM)
);
QBackendStepMetaData receiveValuesStep = new QBackendStepMetaData()
.withName("receiveValues")
.withCode(new QCodeReference(BulkEditReceiveValuesStep.class))
.withInputData(new QFunctionInputMetaData()
.withRecordListMetaData(new QRecordListMetaData().withTableName(table.getName()))
.withField(new QFieldMetaData(BulkEditReceiveValuesStep.FIELD_ENABLED_FIELDS, QFieldType.STRING))
.withFields(editableFields));
QFrontendStepMetaData reviewScreen = new QFrontendStepMetaData()
.withName("review")
.withRecordListFields(editableFields)
.withViewField(new QFieldMetaData(BulkEditReceiveValuesStep.FIELD_VALUES_BEING_UPDATED, QFieldType.STRING))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below will be updated if you click Submit."));
QBackendStepMetaData storeStep = new QBackendStepMetaData()
.withName("storeRecords")
.withCode(new QCodeReference(BulkEditStoreRecordsStep.class))
.withOutputMetaData(new QFunctionOutputMetaData()
.withFieldList(List.of(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER))));
QFrontendStepMetaData resultsScreen = new QFrontendStepMetaData()
.withName("results")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withViewField(new QFieldMetaData(BulkEditReceiveValuesStep.FIELD_VALUES_BEING_UPDATED, QFieldType.STRING))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below have been updated."));
qInstance.addProcess(
new QProcessMetaData()
.withName(processName)
.withLabel(table.getLabel() + " Bulk Edit")
.withTableName(table.getName())
.withIsHidden(true)
.withStepList(List.of(
LoadInitialRecordsStep.defineMetaData(table.getName()),
editScreen,
receiveValuesStep,
reviewScreen,
storeStep,
resultsScreen
)));
}
/*******************************************************************************
**
*******************************************************************************/
private void defineTableBulkDelete(QInstance qInstance, QTableMetaData table, String processName)
{
QFrontendStepMetaData reviewScreen = new QFrontendStepMetaData()
.withName("review")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below will be deleted if you click Submit."));
QBackendStepMetaData storeStep = new QBackendStepMetaData()
.withName("delete")
.withCode(new QCodeReference(BulkDeleteStoreStep.class));
QFrontendStepMetaData resultsScreen = new QFrontendStepMetaData()
.withName("results")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below have been deleted."));
qInstance.addProcess(
new QProcessMetaData()
.withName(processName)
.withLabel(table.getLabel() + " Bulk Delete")
.withTableName(table.getName())
.withIsHidden(true)
.withStepList(List.of(
LoadInitialRecordsStep.defineMetaData(table.getName()),
reviewScreen,
storeStep,
resultsScreen
)));
}
}

View File

@@ -0,0 +1,41 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
/*******************************************************************************
** Object used to mark a QInstance has having been validated.
**
*******************************************************************************/
public final class QInstanceValidationKey
{
/*******************************************************************************
** package-private constructor, so that only this package can create an instance
** of this class, but an instance of this class is required to mark an instance
** as validated, so no one can cheat.
**
*******************************************************************************/
QInstanceValidationKey()
{
}
}

View File

@@ -0,0 +1,162 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Class that knows how to take a look at the data in a QInstance, and report
** if it is all valid - e.g., non-null things are set; references line-up (e.g.,
** a table's backend must be a defined backend).
**
** Prior to doing validation, the the QInstanceEnricher is ran over the QInstance,
** e.g., to fill in things that can be defaulted or assumed. TODO let the instance
** customize or opt-out of Enrichment.
**
*******************************************************************************/
public class QInstanceValidator
{
/*******************************************************************************
**
*******************************************************************************/
public void validate(QInstance qInstance) throws QInstanceValidationException
{
if(qInstance.getHasBeenValidated())
{
//////////////////////////////////////////
// don't re-validate if previously done //
//////////////////////////////////////////
return;
}
try
{
/////////////////////////////////////////////////////////////////////////////////////////////////
// before validation, enrich the object (e.g., to fill in values that the user doesn't have to //
/////////////////////////////////////////////////////////////////////////////////////////////////
// TODO - possible point of customization (use a different enricher, or none, or pass it options).
new QInstanceEnricher().enrich(qInstance);
}
catch(Exception e)
{
throw (new QInstanceValidationException("Error enriching qInstance prior to validation.", e));
}
//////////////////////////////////////////////////////////////////////////
// do the validation checks - a good qInstance has all conditions TRUE! //
//////////////////////////////////////////////////////////////////////////
List<String> errors = new ArrayList<>();
try
{
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getBackends()),
"At least 1 backend must be defined."))
{
qInstance.getBackends().forEach((backendName, backend) ->
{
assertCondition(errors, Objects.equals(backendName, backend.getName()),
"Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
});
}
/////////////////////////
// validate the tables //
/////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getTables()),
"At least 1 table must be defined."))
{
qInstance.getTables().forEach((tableName, table) ->
{
assertCondition(errors, Objects.equals(tableName, table.getName()),
"Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
////////////////////////////////////////
// validate the backend for the table //
////////////////////////////////////////
if(assertCondition(errors, StringUtils.hasContent(table.getBackendName()),
"Missing backend name for table " + tableName + "."))
{
if(CollectionUtils.nullSafeHasContents(qInstance.getBackends()))
{
assertCondition(errors, qInstance.getBackendForTable(tableName) != null,
"Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
}
}
//////////////////////////////////
// validate fields in the table //
//////////////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(table.getFields()),
"At least 1 field must be defined in table " + tableName + "."))
{
table.getFields().forEach((fieldName, field) ->
{
assertCondition(errors, Objects.equals(fieldName, field.getName()),
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
if(field.getPossibleValueSourceName() != null)
{
assertCondition(errors, qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
});
}
});
}
}
catch(Exception e)
{
throw (new QInstanceValidationException("Error performing qInstance validation.", e));
}
if(!errors.isEmpty())
{
throw (new QInstanceValidationException(errors));
}
qInstance.setHasBeenValidated(new QInstanceValidationKey());
}
/*******************************************************************************
**
*******************************************************************************/
private boolean assertCondition(List<String> errors, boolean condition, String message)
{
if(!condition)
{
errors.add(message);
}
return (condition);
}
}

View File

@@ -0,0 +1,175 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** To avoid having secrets (passwords, access keys, etc) committed into meta data
** files, as well as to just let some meta data not be hard-coded, this class is
** used by the Enricher to "promote" values, such as ${env.ACCESS_KEY}
** to be read from the environment (or other secret providers (to be implemented)).
**
** Supported syntax / value sources are:
** ${env.VAR} = system environment variables, e.g., export VAR=val
** ${prop.VAR} = properties, e.g., -DVAR=val
** ${literal.VAR} = get back a literal "VAR" (in case VAR matches some of the other supported syntax in here)
*******************************************************************************/
public class QMetaDataVariableInterpreter
{
private static final Logger LOG = LogManager.getLogger(QMetaDataVariableInterpreter.class);
private Map<String, String> customEnvironment;
/*******************************************************************************
**
*******************************************************************************/
public void interpretObject(Object o) throws QException
{
///////////////////////////////////////////////////////////////////////////////////////////////////
// get the InterpretableFields from the object's class - exiting if the annotation isn't present //
///////////////////////////////////////////////////////////////////////////////////////////////////
InterpretableFields interpretableFields = o.getClass().getAnnotation(InterpretableFields.class);
if(interpretableFields == null)
{
return;
}
//////////////////////////////////////////////////////////
// iterate over interpretable fields, interpreting each //
//////////////////////////////////////////////////////////
for(String fieldName : interpretableFields.fieldNames())
{
try
{
///////////////////////////////////////////////////////////////////////////////////////
// get the getter & setter methods for the field (getMethod will throw if not found) //
// enforce Strings-only at this time. //
///////////////////////////////////////////////////////////////////////////////////////
String fieldNameUcFirst = fieldName.substring(0, 1).toUpperCase(Locale.ROOT) + fieldName.substring(1);
Method getter = o.getClass().getMethod("get" + fieldNameUcFirst);
Class<?> fieldType = getter.getReturnType();
if(!fieldType.equals(String.class))
{
throw new QException("Interpretable field: " + fieldName + " on class " + o.getClass() + " is not a String (which is required at this time)");
}
Method setter = o.getClass().getMethod("set" + fieldNameUcFirst, fieldType);
//////////////////////////////////////////////////////////////////////////////////////////////
// get the value - if it's null, move on, else, interpret it, and put it back in the object //
//////////////////////////////////////////////////////////////////////////////////////////////
Object value = getter.invoke(o);
if(value == null)
{
continue;
}
String interpreted = interpret((String) value);
setter.invoke(o, interpreted);
}
catch(Exception e)
{
throw (new QException("Error interpreting variables in object " + o, e));
}
}
}
/*******************************************************************************
** Interpret a value string, which may be a variable, into its run-time value.
**
** If input is null, output is null.
** If input looks like ${env.X}, then the return value is the value of the env variable 'X'
** If input looks like ${prop.X}, then the return value is the value of the system property 'X'
** If input looks like ${literal.X}, then the return value is the literal 'X'
** - used if you really want to get back the literal value, ${env.X}, for example.
** Else the output is the input.
*******************************************************************************/
public String interpret(String value)
{
if(value == null)
{
return (null);
}
String envPrefix = "${env.";
if(value.startsWith(envPrefix) && value.endsWith("}"))
{
String envVarName = value.substring(envPrefix.length()).replaceFirst("}$", "");
String envValue = getEnvironment().get(envVarName);
return (envValue);
}
String propPrefix = "${prop.";
if(value.startsWith(propPrefix) && value.endsWith("}"))
{
String propertyName = value.substring(propPrefix.length()).replaceFirst("}$", "");
String propertyValue = System.getProperty(propertyName);
return (propertyValue);
}
String literalPrefix = "${literal.";
if(value.startsWith(literalPrefix) && value.endsWith("}"))
{
String literalValue = value.substring(literalPrefix.length()).replaceFirst("}$", "");
return (literalValue);
}
return (value);
}
/*******************************************************************************
** Setter for customEnvironment - protected - meant to be called (at least at this
** time), only in unit test
**
*******************************************************************************/
protected void setCustomEnvironment(Map<String, String> customEnvironment)
{
this.customEnvironment = customEnvironment;
}
/*******************************************************************************
**
*******************************************************************************/
private Map<String, String> getEnvironment()
{
if(this.customEnvironment != null)
{
return (this.customEnvironment);
}
return System.getenv();
}
}

View File

@@ -0,0 +1,178 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Base input class for all Q actions.
**
*******************************************************************************/
public abstract class AbstractActionInput
{
private static final Logger LOG = LogManager.getLogger(AbstractActionInput.class);
protected QInstance instance;
protected QSession session;
private AsyncJobCallback asyncJobCallback;
/*******************************************************************************
**
*******************************************************************************/
public AbstractActionInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public AbstractActionInput(QInstance instance)
{
this.instance = instance;
validateInstance(instance);
}
/*******************************************************************************
** performance instance validation (if not previously done).
*******************************************************************************/
private void validateInstance(QInstance instance)
{
////////////////////////////////////////////////////////////
// if this instance hasn't been validated yet, do so now //
// noting that this will also enrich any missing metaData //
////////////////////////////////////////////////////////////
if(!instance.getHasBeenValidated())
{
try
{
new QInstanceValidator().validate(instance);
}
catch(QInstanceValidationException e)
{
LOG.warn(e);
throw (new IllegalArgumentException("QInstance failed validation" + e.getMessage()));
}
}
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData getAuthenticationMetaData()
{
return (instance.getAuthentication());
}
/*******************************************************************************
** Getter for instance
**
*******************************************************************************/
public QInstance getInstance()
{
return instance;
}
/*******************************************************************************
** Setter for instance
**
*******************************************************************************/
public void setInstance(QInstance instance)
{
validateInstance(instance);
this.instance = instance;
}
/*******************************************************************************
** Getter for session
**
*******************************************************************************/
public QSession getSession()
{
return session;
}
/*******************************************************************************
** Setter for session
**
*******************************************************************************/
public void setSession(QSession session)
{
this.session = session;
}
/*******************************************************************************
** Getter for asyncJobCallback
**
*******************************************************************************/
public AsyncJobCallback getAsyncJobCallback()
{
if(asyncJobCallback == null)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// don't return null here (too easy to NPE). instead, if someone wants one of these, create one and give it to them. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
asyncJobCallback = new AsyncJobCallback(UUID.randomUUID(), new AsyncJobStatus());
}
return asyncJobCallback;
}
/*******************************************************************************
** Setter for asyncJobCallback
**
*******************************************************************************/
public void setAsyncJobCallback(AsyncJobCallback asyncJobCallback)
{
this.asyncJobCallback = asyncJobCallback;
}
}

View File

@@ -0,0 +1,39 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions;
/*******************************************************************************
** Abstract base class for any qqq-action's output.
**
*******************************************************************************/
public abstract class AbstractActionOutput
{
// todo - status codes?
/*******************************************************************************
**
*******************************************************************************/
public AbstractActionOutput()
{
}
}

View File

@@ -0,0 +1,98 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Base class for input for any qqq action that works against a table.
**
*******************************************************************************/
public abstract class AbstractTableActionInput extends AbstractActionInput
{
private String tableName;
/*******************************************************************************
**
*******************************************************************************/
public QBackendMetaData getBackend()
{
return (instance.getBackendForTable(getTableName()));
}
/*******************************************************************************
**
*******************************************************************************/
public QTableMetaData getTable()
{
return (instance.getTable(getTableName()));
}
/*******************************************************************************
**
*******************************************************************************/
public AbstractTableActionInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public AbstractTableActionInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
}

View File

@@ -0,0 +1,53 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input for the meta-data action
**
*******************************************************************************/
public class MetaDataInput extends AbstractActionInput
{
/*******************************************************************************
**
*******************************************************************************/
public MetaDataInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public MetaDataInput(QInstance instance)
{
super(instance);
}
}

View File

@@ -0,0 +1,83 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
/*******************************************************************************
* Output for a metaData action
*
*******************************************************************************/
public class MetaDataOutput extends AbstractActionOutput
{
private Map<String, QFrontendTableMetaData> tables;
private Map<String, QFrontendProcessMetaData> processes;
/*******************************************************************************
** Getter for tables
**
*******************************************************************************/
public Map<String, QFrontendTableMetaData> getTables()
{
return tables;
}
/*******************************************************************************
** Setter for tables
**
*******************************************************************************/
public void setTables(Map<String, QFrontendTableMetaData> tables)
{
this.tables = tables;
}
/*******************************************************************************
** Getter for processes
**
*******************************************************************************/
public Map<String, QFrontendProcessMetaData> getProcesses()
{
return processes;
}
/*******************************************************************************
** Setter for processes
**
*******************************************************************************/
public void setProcesses(Map<String, QFrontendProcessMetaData> processes)
{
this.processes = processes;
}
}

View File

@@ -0,0 +1,77 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input for meta-data for a process.
**
*******************************************************************************/
public class ProcessMetaDataInput extends AbstractActionInput
{
private String processName;
/*******************************************************************************
**
*******************************************************************************/
public ProcessMetaDataInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public ProcessMetaDataInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for processName
**
*******************************************************************************/
public String getProcessName()
{
return processName;
}
/*******************************************************************************
** Setter for processName
**
*******************************************************************************/
public void setProcessName(String processName)
{
this.processName = processName;
}
}

View File

@@ -0,0 +1,58 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
/*******************************************************************************
* Output for a process-metaData action
*
*******************************************************************************/
public class ProcessMetaDataOutput extends AbstractActionOutput
{
private QFrontendProcessMetaData process;
/*******************************************************************************
** Getter for process
**
*******************************************************************************/
public QFrontendProcessMetaData getProcess()
{
return process;
}
/*******************************************************************************
** Setter for process
**
*******************************************************************************/
public void setProcess(QFrontendProcessMetaData process)
{
this.process = process;
}
}

View File

@@ -0,0 +1,77 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input for meta-data for a table.
**
*******************************************************************************/
public class TableMetaDataInput extends AbstractActionInput
{
private String tableName;
/*******************************************************************************
**
*******************************************************************************/
public TableMetaDataInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public TableMetaDataInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
}

View File

@@ -0,0 +1,58 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
/*******************************************************************************
* Output for a table-metaData action
*
*******************************************************************************/
public class TableMetaDataOutput extends AbstractActionOutput
{
private QFrontendTableMetaData table;
/*******************************************************************************
** Getter for table
**
*******************************************************************************/
public QFrontendTableMetaData getTable()
{
return table;
}
/*******************************************************************************
** Setter for table
**
*******************************************************************************/
public void setTable(QFrontendTableMetaData table)
{
this.table = table;
}
}

View File

@@ -0,0 +1,120 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
**
*******************************************************************************/
public class ProcessState implements Serializable
{
private List<QRecord> records = new ArrayList<>();
private Map<String, Serializable> values = new HashMap<>();
private Optional<String> nextStepName = Optional.empty();
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return records;
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.records = records;
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return values;
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.values = values;
}
/*******************************************************************************
** Getter for nextStepName
**
*******************************************************************************/
public Optional<String> getNextStepName()
{
return nextStepName;
}
/*******************************************************************************
** Setter for nextStepName
**
*******************************************************************************/
public void setNextStepName(String nextStepName)
{
this.nextStepName = Optional.of(nextStepName);
}
/*******************************************************************************
** clear out the value of nextStepName (set the Optional to empty)
**
*******************************************************************************/
public void clearNextStepName()
{
this.nextStepName = Optional.empty();
}
}

View File

@@ -0,0 +1,81 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
/*******************************************************************************
** Model a file that a user uploaded (or otherwise submitted to the qqq backend).
*******************************************************************************/
public class QUploadedFile implements Serializable
{
public static final String DEFAULT_UPLOADED_FILE_FIELD_NAME = "uploadedFileKey";
private String filename;
private byte[] bytes;
/*******************************************************************************
** Getter for filename
**
*******************************************************************************/
public String getFilename()
{
return filename;
}
/*******************************************************************************
** Setter for filename
**
*******************************************************************************/
public void setFilename(String filename)
{
this.filename = filename;
}
/*******************************************************************************
** Getter for bytes
**
*******************************************************************************/
public byte[] getBytes()
{
return bytes;
}
/*******************************************************************************
** Setter for bytes
**
*******************************************************************************/
public void setBytes(byte[] bytes)
{
this.bytes = bytes;
}
}

View File

@@ -0,0 +1,409 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** Input data container for the RunBackendStep action
**
*******************************************************************************/
public class RunBackendStepInput extends AbstractActionInput
{
private ProcessState processState;
private String processName;
private String tableName;
private String stepName;
private QProcessCallback callback;
private AsyncJobCallback asyncJobCallback;
/*******************************************************************************
**
*******************************************************************************/
public RunBackendStepInput()
{
processState = new ProcessState();
}
/*******************************************************************************
**
*******************************************************************************/
public RunBackendStepInput(QInstance instance)
{
super(instance);
processState = new ProcessState();
}
/*******************************************************************************
**
*******************************************************************************/
public RunBackendStepInput(QInstance instance, ProcessState processState)
{
super(instance);
this.processState = processState;
}
/*******************************************************************************
**
*******************************************************************************/
public QStepMetaData getStepMetaData()
{
return (instance.getProcessStep(getProcessName(), getStepName()));
}
/*******************************************************************************
** Getter for processName
**
*******************************************************************************/
public String getProcessName()
{
return processName;
}
/*******************************************************************************
** Setter for processName
**
*******************************************************************************/
public void setProcessName(String processName)
{
this.processName = processName;
}
/*******************************************************************************
** Setter for processName
**
*******************************************************************************/
public RunBackendStepInput withProcessName(String processName)
{
this.processName = processName;
return (this);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public RunBackendStepInput withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QTableMetaData getTable()
{
if(tableName == null)
{
return (null);
}
return (instance.getTable(tableName));
}
/*******************************************************************************
** Getter for functionName
**
*******************************************************************************/
public String getStepName()
{
return stepName;
}
/*******************************************************************************
** Setter for functionName
**
*******************************************************************************/
public void setStepName(String stepName)
{
this.stepName = stepName;
}
/*******************************************************************************
** Setter for functionName
**
*******************************************************************************/
public RunBackendStepInput withFunctionName(String functionName)
{
this.stepName = functionName;
return (this);
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return processState.getRecords();
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.processState.setRecords(records);
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public RunBackendStepInput withRecords(List<QRecord> records)
{
this.processState.setRecords(records);
return (this);
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return processState.getValues();
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunBackendStepInput withValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
return (this);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunBackendStepInput addValue(String fieldName, Serializable value)
{
this.processState.getValues().put(fieldName, value);
return (this);
}
/*******************************************************************************
** Getter for callback
**
*******************************************************************************/
public QProcessCallback getCallback()
{
return callback;
}
/*******************************************************************************
** Setter for callback
**
*******************************************************************************/
public void setCallback(QProcessCallback callback)
{
this.callback = callback;
}
/*******************************************************************************
** Setter for callback
**
*******************************************************************************/
public RunBackendStepInput withCallback(QProcessCallback callback)
{
this.callback = callback;
return (this);
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Serializable getValue(String fieldName)
{
return (processState.getValues().get(fieldName));
}
/*******************************************************************************
** Getter for a single field's date value
**
*******************************************************************************/
public LocalDate getValueLocalDate(String fieldName)
{
return (ValueUtils.getValueAsLocalDate(getValue(fieldName)));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getValueString(String fieldName)
{
return ((String) getValue(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
}
/*******************************************************************************
** Accessor for processState - protected, because we generally want to access
** its members through wrapper methods, we think
**
*******************************************************************************/
protected ProcessState getProcessState()
{
return processState;
}
/*******************************************************************************
**
*******************************************************************************/
public void setAsyncJobCallback(AsyncJobCallback asyncJobCallback)
{
this.asyncJobCallback = asyncJobCallback;
}
/*******************************************************************************
**
*******************************************************************************/
public AsyncJobCallback getAsyncJobCallback()
{
if(asyncJobCallback == null)
{
/////////////////////////////////////////////////////////////////////////
// avoid NPE in case we didn't have one of these! create a new one... //
/////////////////////////////////////////////////////////////////////////
asyncJobCallback = new AsyncJobCallback(UUID.randomUUID(), new AsyncJobStatus());
}
return (asyncJobCallback);
}
}

View File

@@ -0,0 +1,244 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** Output data container for the RunBackendStep action
**
*******************************************************************************/
public class RunBackendStepOutput extends AbstractActionOutput
{
private ProcessState processState;
private Exception exception; // todo - make optional
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return "RunBackendStepOutput{exception?='" + (exception == null ? "null" : exception.getMessage())
+ ",records.size()=" + (processState == null ? null : processState.getRecords().size())
+ ",values=" + (processState == null ? null : processState.getValues())
+ "}";
}
/*******************************************************************************
**
*******************************************************************************/
public RunBackendStepOutput()
{
this.processState = new ProcessState();
}
/*******************************************************************************
** e.g., populate the process state (records, values) in this result object.
**
*******************************************************************************/
public void seedFromRequest(RunBackendStepInput runBackendStepInput)
{
this.processState = runBackendStepInput.getProcessState();
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return processState.getRecords();
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.processState.setRecords(records);
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public RunBackendStepOutput withRecords(List<QRecord> records)
{
this.processState.setRecords(records);
return (this);
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return processState.getValues();
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunBackendStepOutput withValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
return (this);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunBackendStepOutput addValue(String fieldName, Serializable value)
{
this.processState.getValues().put(fieldName, value);
return (this);
}
/*******************************************************************************
** Accessor for processState
**
*******************************************************************************/
public ProcessState getProcessState()
{
return processState;
}
/*******************************************************************************
**
*******************************************************************************/
public void setException(Exception exception)
{
this.exception = exception;
}
/*******************************************************************************
**
*******************************************************************************/
public Exception getException()
{
return exception;
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Serializable getValue(String fieldName)
{
return (processState.getValues().get(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getValueString(String fieldName)
{
return (ValueUtils.getValueAsString(getValue(fieldName)));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Boolean getValueBoolean(String fieldName)
{
return (ValueUtils.getValueAsBoolean(getValue(fieldName)));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public BigDecimal getValueBigDecimal(String fieldName)
{
return (ValueUtils.getValueAsBigDecimal(getValue(fieldName)));
}
}

View File

@@ -0,0 +1,378 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
/*******************************************************************************
** Input data container for the RunProcess action
**
*******************************************************************************/
public class RunProcessInput extends AbstractActionInput
{
private String processName;
private QProcessCallback callback;
private ProcessState processState;
private FrontendStepBehavior frontendStepBehavior = FrontendStepBehavior.BREAK;
private String startAfterStep;
private String processUUID;
private AsyncJobCallback asyncJobCallback;
/*******************************************************************************
**
*******************************************************************************/
public enum FrontendStepBehavior
{
BREAK,
SKIP,
FAIL
}
/*******************************************************************************
**
*******************************************************************************/
public RunProcessInput()
{
processState = new ProcessState();
}
/*******************************************************************************
**
*******************************************************************************/
public RunProcessInput(QInstance instance)
{
super(instance);
processState = new ProcessState();
}
/*******************************************************************************
** e.g., for steps after the first step in a process, seed the data in a run
** function request from a process state.
**
*******************************************************************************/
public void seedFromProcessState(ProcessState processState)
{
this.processState = processState;
}
/*******************************************************************************
**
*******************************************************************************/
public QProcessMetaData getProcessMetaData()
{
return (instance.getProcess(getProcessName()));
}
/*******************************************************************************
** Getter for processName
**
*******************************************************************************/
public String getProcessName()
{
return processName;
}
/*******************************************************************************
** Setter for processName
**
*******************************************************************************/
public void setProcessName(String processName)
{
this.processName = processName;
}
/*******************************************************************************
** Setter for processName
**
*******************************************************************************/
public RunProcessInput withProcessName(String processName)
{
this.processName = processName;
return (this);
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return processState.getRecords();
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.processState.setRecords(records);
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public RunProcessInput withRecords(List<QRecord> records)
{
setRecords(records);
return (this);
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return this.processState.getValues();
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunProcessInput withValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
return (this);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunProcessInput addValue(String fieldName, Serializable value)
{
this.processState.getValues().put(fieldName, value);
return (this);
}
/*******************************************************************************
** Getter for callback
**
*******************************************************************************/
public QProcessCallback getCallback()
{
return callback;
}
/*******************************************************************************
** Setter for callback
**
*******************************************************************************/
public void setCallback(QProcessCallback callback)
{
this.callback = callback;
}
/*******************************************************************************
** Setter for callback
**
*******************************************************************************/
public RunProcessInput withCallback(QProcessCallback callback)
{
this.callback = callback;
return (this);
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Serializable getValue(String fieldName)
{
return (this.processState.getValues().get(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getValueString(String fieldName)
{
return ((String) getValue(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return ((Integer) getValue(fieldName));
}
/*******************************************************************************
** Accessor for processState
**
*******************************************************************************/
public ProcessState getProcessState()
{
return processState;
}
/*******************************************************************************
** Getter for frontendStepBehavior
**
*******************************************************************************/
public FrontendStepBehavior getFrontendStepBehavior()
{
return frontendStepBehavior;
}
/*******************************************************************************
** Setter for frontendStepBehavior
**
*******************************************************************************/
public void setFrontendStepBehavior(FrontendStepBehavior frontendStepBehavior)
{
this.frontendStepBehavior = frontendStepBehavior;
}
/*******************************************************************************
**
*******************************************************************************/
public void setStartAfterStep(String startAfterStep)
{
this.startAfterStep = startAfterStep;
}
/*******************************************************************************
**
*******************************************************************************/
public String getStartAfterStep()
{
return startAfterStep;
}
/*******************************************************************************
**
*******************************************************************************/
public void setProcessUUID(String processUUID)
{
this.processUUID = processUUID;
}
/*******************************************************************************
**
*******************************************************************************/
public String getProcessUUID()
{
return processUUID;
}
/*******************************************************************************
**
*******************************************************************************/
public void setAsyncJobCallback(AsyncJobCallback asyncJobCallback)
{
this.asyncJobCallback = asyncJobCallback;
}
/*******************************************************************************
**
*******************************************************************************/
public AsyncJobCallback getAsyncJobCallback()
{
return asyncJobCallback;
}
}

View File

@@ -0,0 +1,221 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Output data container for the RunProcess action
**
*******************************************************************************/
public class RunProcessOutput extends AbstractActionOutput implements Serializable
{
private ProcessState processState;
private String processUUID;
private Optional<Exception> exception = Optional.empty();
/*******************************************************************************
**
*******************************************************************************/
public RunProcessOutput()
{
processState = new ProcessState();
}
/*******************************************************************************
**
*******************************************************************************/
public RunProcessOutput(ProcessState processState)
{
this.processState = processState;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return "RunProcessOutput{uuid='" + processUUID
+ ",exception?='" + (exception.isPresent() ? exception.get().getMessage() : "null")
+ ",records.size()=" + (processState == null ? null : processState.getRecords().size())
+ ",values=" + (processState == null ? null : processState.getValues())
+ "}";
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return processState.getRecords();
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.processState.setRecords(records);
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public RunProcessOutput withRecords(List<QRecord> records)
{
this.processState.setRecords(records);
return (this);
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return processState.getValues();
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunProcessOutput withValues(Map<String, Serializable> values)
{
this.processState.setValues(values);
return (this);
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public RunProcessOutput addValue(String fieldName, Serializable value)
{
this.processState.getValues().put(fieldName, value);
return (this);
}
/*******************************************************************************
** Getter for processUUID
**
*******************************************************************************/
public String getProcessUUID()
{
return processUUID;
}
/*******************************************************************************
** Setter for processUUID
**
*******************************************************************************/
public void setProcessUUID(String processUUID)
{
this.processUUID = processUUID;
}
/*******************************************************************************
** Getter for processState
**
*******************************************************************************/
public ProcessState getProcessState()
{
return processState;
}
/*******************************************************************************
** Setter for processState
**
*******************************************************************************/
public void setProcessState(ProcessState processState)
{
this.processState = processState;
}
/*******************************************************************************
**
*******************************************************************************/
public void setException(Exception exception)
{
this.exception = Optional.of(exception);
}
/*******************************************************************************
**
*******************************************************************************/
public Optional<Exception> getException()
{
return exception;
}
}

View File

@@ -0,0 +1,126 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.reporting;
import java.util.Locale;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.reporting.CsvReportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.ExcelReportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.ReportStreamerInterface;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.dhatim.fastexcel.Worksheet;
/*******************************************************************************
** QQQ Report/export file formats
*******************************************************************************/
public enum ReportFormat
{
XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelReportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
CSV(null, null, CsvReportStreamer::new, "text/csv");
private final Integer maxRows;
private final Integer maxCols;
private final String mimeType;
private final Supplier<? extends ReportStreamerInterface> streamerConstructor;
/*******************************************************************************
**
*******************************************************************************/
ReportFormat(Integer maxRows, Integer maxCols, Supplier<? extends ReportStreamerInterface> streamerConstructor, String mimeType)
{
this.maxRows = maxRows;
this.maxCols = maxCols;
this.mimeType = mimeType;
this.streamerConstructor = streamerConstructor;
}
/*******************************************************************************
**
*******************************************************************************/
public static ReportFormat fromString(String format) throws QUserFacingException
{
if(!StringUtils.hasContent(format))
{
throw (new QUserFacingException("Report format was not specified."));
}
try
{
return (ReportFormat.valueOf(format.toUpperCase(Locale.ROOT)));
}
catch(IllegalArgumentException iae)
{
throw (new QUserFacingException("Unsupported report format: " + format + "."));
}
}
/*******************************************************************************
** Getter for maxRows
**
*******************************************************************************/
public Integer getMaxRows()
{
return maxRows;
}
/*******************************************************************************
** Getter for maxCols
**
*******************************************************************************/
public Integer getMaxCols()
{
return maxCols;
}
/*******************************************************************************
** Getter for mimeType
**
*******************************************************************************/
public String getMimeType()
{
return mimeType;
}
/*******************************************************************************
**
*******************************************************************************/
public ReportStreamerInterface newReportStreamer()
{
return (streamerConstructor.get());
}
}

View File

@@ -0,0 +1,207 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.reporting;
import java.io.OutputStream;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
** Input for a Report action
*******************************************************************************/
public class ReportInput extends AbstractTableActionInput
{
private QQueryFilter queryFilter;
private Integer limit;
private List<String> fieldNames;
private String filename;
private ReportFormat reportFormat;
private OutputStream reportOutputStream;
/*******************************************************************************
**
*******************************************************************************/
public ReportInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public ReportInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
**
*******************************************************************************/
public ReportInput(QInstance instance, QSession session)
{
super(instance);
setSession(session);
}
/*******************************************************************************
** Getter for queryFilter
**
*******************************************************************************/
public QQueryFilter getQueryFilter()
{
return queryFilter;
}
/*******************************************************************************
** Setter for queryFilter
**
*******************************************************************************/
public void setQueryFilter(QQueryFilter queryFilter)
{
this.queryFilter = queryFilter;
}
/*******************************************************************************
** Getter for limit
**
*******************************************************************************/
public Integer getLimit()
{
return limit;
}
/*******************************************************************************
** Setter for limit
**
*******************************************************************************/
public void setLimit(Integer limit)
{
this.limit = limit;
}
/*******************************************************************************
** Getter for fieldNames
**
*******************************************************************************/
public List<String> getFieldNames()
{
return fieldNames;
}
/*******************************************************************************
** Setter for fieldNames
**
*******************************************************************************/
public void setFieldNames(List<String> fieldNames)
{
this.fieldNames = fieldNames;
}
/*******************************************************************************
** Getter for filename
**
*******************************************************************************/
public String getFilename()
{
return filename;
}
/*******************************************************************************
** Setter for filename
**
*******************************************************************************/
public void setFilename(String filename)
{
this.filename = filename;
}
/*******************************************************************************
** Getter for reportFormat
**
*******************************************************************************/
public ReportFormat getReportFormat()
{
return reportFormat;
}
/*******************************************************************************
** Setter for reportFormat
**
*******************************************************************************/
public void setReportFormat(ReportFormat reportFormat)
{
this.reportFormat = reportFormat;
}
/*******************************************************************************
** Getter for reportOutputStream
**
*******************************************************************************/
public OutputStream getReportOutputStream()
{
return reportOutputStream;
}
/*******************************************************************************
** Setter for reportOutputStream
**
*******************************************************************************/
public void setReportOutputStream(OutputStream reportOutputStream)
{
this.reportOutputStream = reportOutputStream;
}
}

View File

@@ -0,0 +1,56 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.reporting;
import java.io.Serializable;
/*******************************************************************************
** Output for a Report action
*******************************************************************************/
public class ReportOutput implements Serializable
{
public long recordCount;
/*******************************************************************************
** Getter for recordCount
**
*******************************************************************************/
public long getRecordCount()
{
return recordCount;
}
/*******************************************************************************
** Setter for recordCount
**
*******************************************************************************/
public void setRecordCount(long recordCount)
{
this.recordCount = recordCount;
}
}

View File

@@ -0,0 +1,81 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.shared.mapping;
/*******************************************************************************
** For bulk-loads, define where a QField comes from in an input data source.
**
*******************************************************************************/
public abstract class AbstractQFieldMapping<T>
{
/*******************************************************************************
** Enum to define the types of sources a mapping may use
**
*******************************************************************************/
@SuppressWarnings("rawtypes")
public enum SourceType
{
KEY(String.class),
INDEX(Integer.class);
private Class sourceClass;
/*******************************************************************************
** enum constructor
*******************************************************************************/
SourceType(Class sourceClass)
{
this.sourceClass = sourceClass;
}
/*******************************************************************************
** Getter for sourceClass
**
*******************************************************************************/
public Class getSourceClass()
{
return sourceClass;
}
}
/*******************************************************************************
** For a given field, return its source - a key (e.g., from a json object or csv
** with a header row) or an index (for a csv w/o a header)
**
*******************************************************************************/
public abstract T getFieldSource(String fieldName);
/*******************************************************************************
** for a mapping instance, get what its source-type is
**
*******************************************************************************/
public abstract SourceType getSourceType();
}

View File

@@ -0,0 +1,121 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.shared.mapping;
import java.util.LinkedHashMap;
import java.util.Map;
/*******************************************************************************
** Field Mapping implementation that uses Integer keys (e.g., from a CSV file
** WITHOUT a header row).
**
** Note: 1-based index!!
**
*******************************************************************************/
public class QIndexBasedFieldMapping extends AbstractQFieldMapping<Integer>
{
private Map<String, Integer> mapping;
/*******************************************************************************
** Get the field source (e.g., integer index of a CSV column) corresponding to a
** propery qqq table fieldName.
**
*******************************************************************************/
@Override
public Integer getFieldSource(String fieldName)
{
if(mapping == null)
{
return (null);
}
return (mapping.get(fieldName));
}
/*******************************************************************************
** Tell framework what kind of keys this mapping class uses (INDEX)
**
*******************************************************************************/
@Override
public SourceType getSourceType()
{
return (SourceType.INDEX);
}
/*******************************************************************************
** Add a single mapping to this mapping object. fieldName = qqq metaData fieldName,
** key = field index (integer) in the CSV
**
*******************************************************************************/
public void addMapping(String fieldName, Integer key)
{
if(mapping == null)
{
mapping = new LinkedHashMap<>();
}
mapping.put(fieldName, key);
}
/*******************************************************************************
** Fluently add a single mapping to this mapping object. fieldName = qqq metaData
** fieldName, key = field index (integer) in the CSV
**
*******************************************************************************/
public QIndexBasedFieldMapping withMapping(String fieldName, Integer key)
{
addMapping(fieldName, key);
return (this);
}
/*******************************************************************************
** Getter for mapping
**
*******************************************************************************/
public Map<String, Integer> getMapping()
{
return mapping;
}
/*******************************************************************************
** Setter for mapping
**
*******************************************************************************/
public void setMapping(Map<String, Integer> mapping)
{
this.mapping = mapping;
}
}

View File

@@ -0,0 +1,119 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.shared.mapping;
import java.util.LinkedHashMap;
import java.util.Map;
/*******************************************************************************
** Field Mapping implementation that uses string keys (e.g., from a CSV file
** with a header row, or from one JSON object to the proper qqq field names)
**
*******************************************************************************/
public class QKeyBasedFieldMapping extends AbstractQFieldMapping<String>
{
private Map<String, String> mapping;
/*******************************************************************************
** Get the source field (e.g., name that's in the CSV header or the input json
** object) corresponding to a proper qqq table fieldName.
**
*******************************************************************************/
@Override
public String getFieldSource(String fieldName)
{
if(mapping == null)
{
return (null);
}
return (mapping.get(fieldName));
}
/*******************************************************************************
** Tell framework what kind of keys this mapping class uses (KEY)
**
*******************************************************************************/
@Override
public SourceType getSourceType()
{
return (SourceType.KEY);
}
/*******************************************************************************
** Add a single mapping to this mapping object. fieldName = qqq metaData fieldName,
** key = field name in the CSV or source-json, for example.
**
*******************************************************************************/
public void addMapping(String fieldName, String key)
{
if(mapping == null)
{
mapping = new LinkedHashMap<>();
}
mapping.put(fieldName, key);
}
/*******************************************************************************
** Fluently add a single mapping to this mapping object. fieldName = qqq metaData fieldName,
** key = field name in the CSV or source-json, for example.
**
*******************************************************************************/
public QKeyBasedFieldMapping withMapping(String fieldName, String key)
{
addMapping(fieldName, key);
return (this);
}
/*******************************************************************************
** Getter for mapping
**
*******************************************************************************/
public Map<String, String> getMapping()
{
return mapping;
}
/*******************************************************************************
** Setter for mapping
**
*******************************************************************************/
public void setMapping(Map<String, String> mapping)
{
this.mapping = mapping;
}
}

View File

@@ -0,0 +1,81 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.count;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input data for the Count action
**
*******************************************************************************/
public class CountInput extends AbstractTableActionInput
{
private QQueryFilter filter;
/*******************************************************************************
**
*******************************************************************************/
public CountInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public CountInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for filter
**
*******************************************************************************/
public QQueryFilter getFilter()
{
return filter;
}
/*******************************************************************************
** Setter for filter
**
*******************************************************************************/
public void setFilter(QQueryFilter filter)
{
this.filter = filter;
}
}

View File

@@ -0,0 +1,55 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.count;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
/*******************************************************************************
** Output for a count action
**
*******************************************************************************/
public class CountOutput extends AbstractActionOutput
{
private Integer count;
/*******************************************************************************
**
*******************************************************************************/
public Integer getCount()
{
return count;
}
/*******************************************************************************
**
*******************************************************************************/
public void setCount(Integer count)
{
this.count = count;
}
}

View File

@@ -0,0 +1,126 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input for a Delete action.
**
*******************************************************************************/
public class DeleteInput extends AbstractTableActionInput
{
private List<Serializable> primaryKeys;
private QQueryFilter queryFilter;
/*******************************************************************************
**
*******************************************************************************/
public DeleteInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public DeleteInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for ids
**
*******************************************************************************/
public List<Serializable> getPrimaryKeys()
{
return primaryKeys;
}
/*******************************************************************************
** Setter for ids
**
*******************************************************************************/
public void setPrimaryKeys(List<Serializable> primaryKeys)
{
this.primaryKeys = primaryKeys;
}
/*******************************************************************************
** Setter for ids
**
*******************************************************************************/
public DeleteInput withPrimaryKeys(List<Serializable> primaryKeys)
{
this.primaryKeys = primaryKeys;
return (this);
}
/*******************************************************************************
** Getter for queryFilter
**
*******************************************************************************/
public QQueryFilter getQueryFilter()
{
return queryFilter;
}
/*******************************************************************************
** Setter for queryFilter
**
*******************************************************************************/
public void setQueryFilter(QQueryFilter queryFilter)
{
this.queryFilter = queryFilter;
}
/*******************************************************************************
** Fluent setter for queryFilter
**
*******************************************************************************/
public DeleteInput withQueryFilter(QQueryFilter queryFilter)
{
this.queryFilter = queryFilter;
return this;
}
}

View File

@@ -0,0 +1,104 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
* Output for a delete action
*
*******************************************************************************/
public class DeleteOutput extends AbstractActionOutput implements Serializable
{
private int deletedRecordCount = 0;
private List<QRecord> recordsWithErrors;
/*******************************************************************************
** Getter for deletedRecordCount
**
*******************************************************************************/
public int getDeletedRecordCount()
{
return deletedRecordCount;
}
/*******************************************************************************
** Setter for deletedRecordCount
**
*******************************************************************************/
public void setDeletedRecordCount(int deletedRecordCount)
{
this.deletedRecordCount = deletedRecordCount;
}
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> getRecordsWithErrors()
{
return recordsWithErrors;
}
/*******************************************************************************
**
*******************************************************************************/
public void setRecordsWithErrors(List<QRecord> recordsWithErrors)
{
this.recordsWithErrors = recordsWithErrors;
}
/*******************************************************************************
**
*******************************************************************************/
public void addRecordWithError(QRecord recordWithError)
{
if(this.recordsWithErrors == null)
{
this.recordsWithErrors = new ArrayList<>();
}
this.recordsWithErrors.add(recordWithError);
}
/*******************************************************************************
**
*******************************************************************************/
public void addToDeletedRecordCount(int i)
{
deletedRecordCount += i;
}
}

View File

@@ -0,0 +1,79 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input data for the Insert action
**
*******************************************************************************/
public class InsertInput extends AbstractTableActionInput
{
private List<QRecord> records;
/*******************************************************************************
**
*******************************************************************************/
public InsertInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public InsertInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return records;
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.records = records;
}
}

View File

@@ -0,0 +1,57 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
* Output for an insert action
*
*******************************************************************************/
public class InsertOutput extends AbstractActionOutput
{
private List<QRecord> records;
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return records;
}
/*******************************************************************************
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.records = records;
}
}

View File

@@ -0,0 +1,49 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
/*******************************************************************************
** Possible query criteria field operators
**
*******************************************************************************/
public enum QCriteriaOperator
{
EQUALS,
NOT_EQUALS,
IN,
NOT_IN,
STARTS_WITH,
ENDS_WITH,
CONTAINS,
NOT_STARTS_WITH,
NOT_ENDS_WITH,
NOT_CONTAINS,
LESS_THAN,
LESS_THAN_OR_EQUALS,
GREATER_THAN,
GREATER_THAN_OR_EQUALS,
IS_BLANK,
IS_NOT_BLANK,
BETWEEN,
NOT_BETWEEN
}

View File

@@ -0,0 +1,139 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.List;
/*******************************************************************************
* A single criteria Component of a Query
*
*******************************************************************************/
public class QFilterCriteria implements Serializable
{
private String fieldName;
private QCriteriaOperator operator;
private List<Serializable> values;
/*******************************************************************************
** Getter for fieldName
**
*******************************************************************************/
public String getFieldName()
{
return fieldName;
}
/*******************************************************************************
** Setter for fieldName
**
*******************************************************************************/
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
/*******************************************************************************
** Setter for fieldName
**
*******************************************************************************/
public QFilterCriteria withFieldName(String fieldName)
{
this.fieldName = fieldName;
return this;
}
/*******************************************************************************
** Getter for operator
**
*******************************************************************************/
public QCriteriaOperator getOperator()
{
return operator;
}
/*******************************************************************************
** Setter for operator
**
*******************************************************************************/
public void setOperator(QCriteriaOperator operator)
{
this.operator = operator;
}
/*******************************************************************************
** Setter for operator
**
*******************************************************************************/
public QFilterCriteria withOperator(QCriteriaOperator operator)
{
this.operator = operator;
return this;
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public List<Serializable> getValues()
{
return values;
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(List<Serializable> values)
{
this.values = values;
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public QFilterCriteria withValues(List<Serializable> values)
{
this.values = values;
return this;
}
}

View File

@@ -0,0 +1,105 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
/*******************************************************************************
** Bean representing an element of a query order-by clause.
**
*******************************************************************************/
public class QFilterOrderBy implements Serializable
{
private String fieldName;
private boolean isAscending = true;
/*******************************************************************************
** Getter for fieldName
**
*******************************************************************************/
public String getFieldName()
{
return fieldName;
}
/*******************************************************************************
** Setter for fieldName
**
*******************************************************************************/
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
/*******************************************************************************
** Fluent Setter for fieldName
**
*******************************************************************************/
public QFilterOrderBy withFieldName(String fieldName)
{
this.fieldName = fieldName;
return (this);
}
/*******************************************************************************
** Getter for isAscending
**
*******************************************************************************/
public boolean getIsAscending()
{
return isAscending;
}
/*******************************************************************************
** Setter for isAscending
**
*******************************************************************************/
public void setIsAscending(boolean ascending)
{
isAscending = ascending;
}
/*******************************************************************************
** Fluent Setter for isAscending
**
*******************************************************************************/
public QFilterOrderBy withIsAscending(boolean ascending)
{
this.isAscending = ascending;
return (this);
}
}

View File

@@ -0,0 +1,133 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/*******************************************************************************
* Full "filter" for a query - a list of criteria and order-bys
*
*******************************************************************************/
public class QQueryFilter implements Serializable
{
private List<QFilterCriteria> criteria = new ArrayList<>();
private List<QFilterOrderBy> orderBys = new ArrayList<>();
/*******************************************************************************
** Getter for criteria
**
*******************************************************************************/
public List<QFilterCriteria> getCriteria()
{
return criteria;
}
/*******************************************************************************
** Setter for criteria
**
*******************************************************************************/
public void setCriteria(List<QFilterCriteria> criteria)
{
this.criteria = criteria;
}
/*******************************************************************************
** Getter for order
**
*******************************************************************************/
public List<QFilterOrderBy> getOrderBys()
{
return orderBys;
}
/*******************************************************************************
** Setter for order
**
*******************************************************************************/
public void setOrderBys(List<QFilterOrderBy> orderBys)
{
this.orderBys = orderBys;
}
/*******************************************************************************
**
*******************************************************************************/
public void addCriteria(QFilterCriteria qFilterCriteria)
{
if(criteria == null)
{
criteria = new ArrayList<>();
}
criteria.add(qFilterCriteria);
}
/*******************************************************************************
**
*******************************************************************************/
public QQueryFilter withCriteria(QFilterCriteria qFilterCriteria)
{
addCriteria(qFilterCriteria);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void addOrderBy(QFilterOrderBy qFilterOrderBy)
{
if(orderBys == null)
{
orderBys = new ArrayList<>();
}
orderBys.add(qFilterOrderBy);
}
/*******************************************************************************
**
*******************************************************************************/
public QQueryFilter withOrderBy(QFilterOrderBy qFilterOrderBy)
{
addOrderBy(qFilterOrderBy);
return (this);
}
}

View File

@@ -0,0 +1,161 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
** Input data for the Query action
**
*******************************************************************************/
public class QueryInput extends AbstractTableActionInput
{
private QQueryFilter filter;
private Integer skip;
private Integer limit;
private RecordPipe recordPipe;
/*******************************************************************************
**
*******************************************************************************/
public QueryInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public QueryInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
**
*******************************************************************************/
public QueryInput(QInstance instance, QSession session)
{
super(instance);
setSession(session);
}
/*******************************************************************************
** Getter for filter
**
*******************************************************************************/
public QQueryFilter getFilter()
{
return filter;
}
/*******************************************************************************
** Setter for filter
**
*******************************************************************************/
public void setFilter(QQueryFilter filter)
{
this.filter = filter;
}
/*******************************************************************************
** Getter for skip
**
*******************************************************************************/
public Integer getSkip()
{
return skip;
}
/*******************************************************************************
** Setter for skip
**
*******************************************************************************/
public void setSkip(Integer skip)
{
this.skip = skip;
}
/*******************************************************************************
** Getter for limit
**
*******************************************************************************/
public Integer getLimit()
{
return limit;
}
/*******************************************************************************
** Setter for limit
**
*******************************************************************************/
public void setLimit(Integer limit)
{
this.limit = limit;
}
/*******************************************************************************
** Getter for recordPipe
**
*******************************************************************************/
public RecordPipe getRecordPipe()
{
return recordPipe;
}
/*******************************************************************************
** Setter for recordPipe
**
*******************************************************************************/
public void setRecordPipe(RecordPipe recordPipe)
{
this.recordPipe = recordPipe;
}
}

View File

@@ -0,0 +1,91 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Output for a query action
**
*******************************************************************************/
public class QueryOutput extends AbstractActionOutput implements Serializable
{
private QueryOutputStorageInterface storage;
/*******************************************************************************
** Construct a new query output, based on a query input (which will drive some
** of how our output is structured... e.g., if we pipe the output)
*******************************************************************************/
public QueryOutput(QueryInput queryInput)
{
if(queryInput.getRecordPipe() != null)
{
storage = new QueryOutputRecordPipe(queryInput.getRecordPipe());
}
else
{
storage = new QueryOutputList();
}
}
/*******************************************************************************
** Add a record to this output. Note - we often don't care, in such a method,
** whether the record is "completed" or not (e.g., all of its values have been
** populated) - but - note in here - that this records MAY be going into a pipe
** that could be read asynchronously, at any time, by another thread - SO - only
** completely populated records should be passed into this method.
*******************************************************************************/
public void addRecord(QRecord record)
{
storage.addRecord(record);
}
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
public void addRecords(List<QRecord> records)
{
storage.addRecords(records);
}
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return storage.getRecords();
}
}

View File

@@ -0,0 +1,80 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Kinda the standard way that a QueryOutput would store its records - in a
** simple list.
*******************************************************************************/
class QueryOutputList implements QueryOutputStorageInterface
{
private List<QRecord> records = new ArrayList<>();
/*******************************************************************************
**
*******************************************************************************/
public QueryOutputList()
{
}
/*******************************************************************************
** add a record to this output
*******************************************************************************/
@Override
public void addRecord(QRecord record)
{
records.add(record);
}
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
@Override
public void addRecords(List<QRecord> records)
{
this.records.addAll(records);
}
/*******************************************************************************
** Get all stored records
*******************************************************************************/
@Override
public List<QRecord> getRecords()
{
return (records);
}
}

View File

@@ -0,0 +1,115 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Query output that uses a RecordPipe
*******************************************************************************/
class QueryOutputRecordPipe implements QueryOutputStorageInterface
{
private static final Logger LOG = LogManager.getLogger(QueryOutputRecordPipe.class);
private RecordPipe recordPipe;
/*******************************************************************************
**
*******************************************************************************/
public QueryOutputRecordPipe(RecordPipe recordPipe)
{
this.recordPipe = recordPipe;
}
/*******************************************************************************
** add a record to this output
*******************************************************************************/
@Override
public void addRecord(QRecord record)
{
if(!recordPipe.addRecord(record))
{
do
{
LOG.debug("Record pipe.add failed (due to full pipe). Blocking.");
SleepUtils.sleep(10, TimeUnit.MILLISECONDS);
}
while(!recordPipe.addRecord(record));
LOG.debug("Done blocking.");
}
}
/*******************************************************************************
**
*******************************************************************************/
private void blockIfPipeIsTooFull()
{
if(recordPipe.countAvailableRecords() >= 100_000)
{
LOG.info("Record pipe is kinda full. Blocking for a bit");
do
{
SleepUtils.sleep(10, TimeUnit.MILLISECONDS);
}
while(recordPipe.countAvailableRecords() >= 10_000);
LOG.info("Done blocking.");
}
}
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
@Override
public void addRecords(List<QRecord> records)
{
recordPipe.addRecords(records);
blockIfPipeIsTooFull();
}
/*******************************************************************************
** Get all stored records
*******************************************************************************/
@Override
public List<QRecord> getRecords()
{
throw (new IllegalStateException("getRecords may not be called on a piped query output"));
}
}

View File

@@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
** Interface used within QueryOutput, to handle diffrent ways we may store records
** (e.g., in a list (that holds them all), or a pipe, that streams them to a consumer thread))
*******************************************************************************/
interface QueryOutputStorageInterface
{
/*******************************************************************************
** add a records to this output
*******************************************************************************/
void addRecord(QRecord record);
/*******************************************************************************
** add a list of records to this output
*******************************************************************************/
void addRecords(List<QRecord> records);
/*******************************************************************************
** Get all stored records
*******************************************************************************/
List<QRecord> getRecords();
}

View File

@@ -0,0 +1,110 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.update;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input data handler for the update action
**
*******************************************************************************/
public class UpdateInput extends AbstractTableActionInput
{
private List<QRecord> records;
////////////////////////////////////////////////////////////////////////////////////////////
// allow a caller to specify that they KNOW this optimization (e.g., in SQL) can be made. //
// If you set this to true, but it isn't, then you may not get an accurate update. //
// If you set this to false, but it isn't, then you may not get the best performance. //
// Just leave it null if you don't know what you're dong. //
////////////////////////////////////////////////////////////////////////////////////////////
private Boolean areAllValuesBeingUpdatedTheSame = null;
/*******************************************************************************
**
*******************************************************************************/
public UpdateInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public UpdateInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for records
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return records;
}
/*******************************************************************************
** Setter for records
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.records = records;
}
/*******************************************************************************
** Getter for areAllValuesBeingUpdatedTheSame
**
*******************************************************************************/
public Boolean getAreAllValuesBeingUpdatedTheSame()
{
return areAllValuesBeingUpdatedTheSame;
}
/*******************************************************************************
** Setter for areAllValuesBeingUpdatedTheSame
**
*******************************************************************************/
public void setAreAllValuesBeingUpdatedTheSame(Boolean areAllValuesBeingUpdatedTheSame)
{
this.areAllValuesBeingUpdatedTheSame = areAllValuesBeingUpdatedTheSame;
}
}

View File

@@ -0,0 +1,57 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.update;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
* Output for an update action
*
*******************************************************************************/
public class UpdateOutput extends AbstractActionOutput
{
private List<QRecord> records;
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> getRecords()
{
return records;
}
/*******************************************************************************
**
*******************************************************************************/
public void setRecords(List<QRecord> records)
{
this.records = records;
}
}

View File

@@ -0,0 +1,410 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** Data Record within qqq. e.g., a single row from a database.
**
** Actual values (e.g., as stored in the backend system) are in the `values`
** map. Keys in this map are fieldNames from the QTableMetaData.
**
** "Display values" (e.g., labels for possible values, or formatted numbers
** (e.g., quantities with commas)) are in the displayValues map.
**
** backendDetails are additional data about a record, that aren't strictly
** values, but are more like meta-data - e.g., for a file-backend, what file the
** record came from.
**
** Errors are meant to hold information about things that went wrong when
** processing a record - e.g., in a list of records that may be the output of an
** action, like a bulk load. TODO - redo as some status object?
*******************************************************************************/
public class QRecord implements Serializable
{
private String tableName;
private Map<String, Serializable> values = new LinkedHashMap<>();
private Map<String, String> displayValues = new LinkedHashMap<>();
private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
private List<String> errors = new ArrayList<>();
/*******************************************************************************
** Default constructor.
*******************************************************************************/
public QRecord()
{
}
/*******************************************************************************
**
*******************************************************************************/
public QRecord(QTableMetaData tableMetaData, Serializable primaryKeyValue)
{
setTableName(tableMetaData.getName());
setValue(tableMetaData.getPrimaryKeyField(), primaryKeyValue);
}
/*******************************************************************************
** Copy constructor.
** TODO ... should this do deep copies?
*******************************************************************************/
public QRecord(QRecord record)
{
this.tableName = record.tableName;
this.values = record.values;
this.displayValues = record.displayValues;
this.backendDetails = record.backendDetails;
this.errors = record.errors;
}
/*******************************************************************************
**
*******************************************************************************/
public void setValue(String fieldName, Serializable value)
{
values.put(fieldName, value);
}
/*******************************************************************************
**
*******************************************************************************/
public QRecord withValue(String fieldName, Serializable value)
{
setValue(fieldName, value);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void setDisplayValue(String fieldName, String displayValue)
{
displayValues.put(fieldName, displayValue);
}
/*******************************************************************************
**
*******************************************************************************/
public QRecord withDisplayValue(String fieldName, String displayValue)
{
setDisplayValue(fieldName, displayValue);
return (this);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public QRecord withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return values;
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.values = values;
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Serializable getValue(String fieldName)
{
return (values.get(fieldName));
}
/*******************************************************************************
** Getter for displayValues
**
*******************************************************************************/
public Map<String, String> getDisplayValues()
{
return displayValues;
}
/*******************************************************************************
** Setter for displayValues
**
*******************************************************************************/
public void setDisplayValues(Map<String, String> displayValues)
{
this.displayValues = displayValues;
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getDisplayValue(String fieldName)
{
return (displayValues.get(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getValueString(String fieldName)
{
return (ValueUtils.getValueAsString(values.get(fieldName)));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return (ValueUtils.getValueAsInteger(values.get(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public BigDecimal getValueBigDecimal(String fieldName)
{
return (ValueUtils.getValueAsBigDecimal(values.get(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public Boolean getValueBoolean(String fieldName)
{
return (ValueUtils.getValueAsBoolean(values.get(fieldName)));
}
/*******************************************************************************
**
*******************************************************************************/
public LocalDate getValueLocalDate(String fieldName)
{
return (ValueUtils.getValueAsLocalDate(values.get(fieldName)));
}
/*******************************************************************************
** Getter for backendDetails
**
*******************************************************************************/
public Map<String, Serializable> getBackendDetails()
{
return backendDetails;
}
/*******************************************************************************
** Setter for backendDetails
**
*******************************************************************************/
public void setBackendDetails(Map<String, Serializable> backendDetails)
{
this.backendDetails = backendDetails;
}
/*******************************************************************************
** Add one backendDetail to this record
**
*******************************************************************************/
public void addBackendDetail(String key, Serializable value)
{
this.backendDetails.put(key, value);
}
/*******************************************************************************
** Fluently Add one backendDetail to this record
**
*******************************************************************************/
public QRecord withBackendDetail(String key, Serializable value)
{
addBackendDetail(key, value);
return (this);
}
/*******************************************************************************
** Get one backendDetail from this record
**
*******************************************************************************/
public Serializable getBackendDetail(String key)
{
return this.backendDetails.get(key);
}
/*******************************************************************************
** Get one backendDetail from this record as a String
**
*******************************************************************************/
public String getBackendDetailString(String key)
{
return (String) this.backendDetails.get(key);
}
/*******************************************************************************
** Getter for errors
**
*******************************************************************************/
public List<String> getErrors()
{
return (errors);
}
/*******************************************************************************
** Setter for errors
**
*******************************************************************************/
public void setErrors(List<String> errors)
{
this.errors = errors;
}
/*******************************************************************************
** Add one error to this record
**
*******************************************************************************/
public void addError(String error)
{
this.errors.add(error);
}
/*******************************************************************************
** Fluently Add one error to this record
**
*******************************************************************************/
public QRecord withError(String error)
{
addError(error);
return (this);
}
/*******************************************************************************
** Convert this record to an QRecordEntity
*******************************************************************************/
public <T extends QRecordEntity> T toEntity(Class<T> c) throws QException
{
return (QRecordEntity.fromQRecord(c, this));
}
}

View File

@@ -0,0 +1,213 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Base class for entity beans that are interoperable with QRecords.
*******************************************************************************/
public abstract class QRecordEntity
{
private static final Logger LOG = LogManager.getLogger(QRecordEntity.class);
private static final ListingHash<Class<? extends QRecordEntity>, QRecordEntityField> fieldMapping = new ListingHash<>();
/*******************************************************************************
** Build an entity of this QRecord type from a QRecord
**
*******************************************************************************/
public static <T extends QRecordEntity> T fromQRecord(Class<T> c, QRecord qRecord) throws QException
{
try
{
T entity = c.getConstructor().newInstance();
List<QRecordEntityField> fieldList = getFieldList(c);
for(QRecordEntityField qRecordEntityField : fieldList)
{
Serializable value = qRecord.getValue(qRecordEntityField.getFieldName());
Object typedValue = qRecordEntityField.convertValueType(value);
qRecordEntityField.getSetter().invoke(entity, typedValue);
}
return (entity);
}
catch(Exception e)
{
throw (new QException("Error building entity from qRecord.", e));
}
}
/*******************************************************************************
** Convert this entity to a QRecord.
**
*******************************************************************************/
public QRecord toQRecord() throws QException
{
try
{
QRecord qRecord = new QRecord();
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
for(QRecordEntityField qRecordEntityField : fieldList)
{
qRecord.setValue(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
}
return (qRecord);
}
catch(Exception e)
{
throw (new QException("Error building qRecord from entity.", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
private static List<QRecordEntityField> getFieldList(Class<? extends QRecordEntity> c)
{
if(!fieldMapping.containsKey(c))
{
List<QRecordEntityField> fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
if(isGetter(possibleGetter))
{
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
String name = getFieldNameFromGetter(possibleGetter);
fieldList.add(new QRecordEntityField(name, possibleGetter, setter.get(), possibleGetter.getReturnType()));
}
else
{
LOG.info("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
}
}
}
fieldMapping.put(c, fieldList);
}
return (fieldMapping.get(c));
}
/*******************************************************************************
**
*******************************************************************************/
public static String getFieldNameFromGetter(Method getter)
{
String nameWithoutGet = getter.getName().replaceFirst("^get", "");
if(nameWithoutGet.length() == 1)
{
return (nameWithoutGet.toLowerCase(Locale.ROOT));
}
return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isGetter(Method method)
{
if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
{
if(isSupportedFieldType(method.getReturnType()))
{
return (true);
}
else
{
if(!method.getName().equals("getClass"))
{
LOG.info("Method [" + method.getName() + "] looks like a getter, but its return type, [" + method.getReturnType() + "], isn't supported.");
}
}
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
private static Optional<Method> getSetterForGetter(Class<? extends QRecordEntity> c, Method getter)
{
String setterName = getter.getName().replaceFirst("^get", "set");
for(Method method : c.getMethods())
{
if(method.getName().equals(setterName))
{
if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(getter.getReturnType()))
{
return (Optional.of(method));
}
else
{
LOG.info("Method [" + method.getName() + "] looks like a setter for [" + getter.getName() + "], but its parameters, [" + Arrays.toString(method.getParameterTypes()) + "], don't match the getter's return type [" + getter.getReturnType() + "]");
}
}
}
return (Optional.empty());
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isSupportedFieldType(Class<?> returnType)
{
// todo - more types!!
return (returnType.equals(String.class)
|| returnType.equals(Integer.class)
|| returnType.equals(int.class)
|| returnType.equals(Boolean.class)
|| returnType.equals(boolean.class)
|| returnType.equals(BigDecimal.class));
}
}

View File

@@ -0,0 +1,138 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** Reflective information about a field in a QRecordEntity
*******************************************************************************/
public class QRecordEntityField
{
private final String fieldName;
private final Method getter;
private final Method setter;
private final Class<?> type;
/*******************************************************************************
** Constructor.
*******************************************************************************/
public QRecordEntityField(String fieldName, Method getter, Method setter, Class<?> type)
{
this.fieldName = fieldName;
this.getter = getter;
this.setter = setter;
this.type = type;
}
/*******************************************************************************
** Getter for fieldName
**
*******************************************************************************/
public String getFieldName()
{
return fieldName;
}
/*******************************************************************************
** Getter for getter
**
*******************************************************************************/
public Method getGetter()
{
return getter;
}
/*******************************************************************************
** Getter for setter
**
*******************************************************************************/
public Method getSetter()
{
return setter;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public Class<?> getType()
{
return type;
}
/*******************************************************************************
**
*******************************************************************************/
public Object convertValueType(Serializable value)
{
if(value == null)
{
return (null);
}
if(value.getClass().equals(type))
{
return (value);
}
if(type.equals(String.class))
{
return (ValueUtils.getValueAsString(value));
}
if(type.equals(Integer.class) || type.equals(int.class))
{
return (ValueUtils.getValueAsInteger(value));
}
if(type.equals(Boolean.class) || type.equals(boolean.class))
{
return (ValueUtils.getValueAsBoolean(value));
}
if(type.equals(BigDecimal.class))
{
return (ValueUtils.getValueAsBigDecimal(value));
}
throw (new QValueException("Unhandled value type [" + type + "] for field [" + fieldName + "]"));
}
}

View File

@@ -0,0 +1,59 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
/*******************************************************************************
** Enum to define the possible authentication types
**
*******************************************************************************/
@SuppressWarnings("rawtypes")
public enum QAuthenticationType
{
AUTH_0("auth0"),
FULLY_ANONYMOUS("fullyAnonymous"),
MOCK("mock");
private final String name;
/*******************************************************************************
** enum constructor
*******************************************************************************/
QAuthenticationType(String name)
{
this.name = name;
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return this.name;
}
}

View File

@@ -0,0 +1,161 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
/*******************************************************************************
** Meta-data to provide details of a backend (e.g., RDBMS instance, S3 buckets,
** NoSQL table, etc) within a qqq instance
**
*******************************************************************************/
@JsonDeserialize(using = QBackendMetaDataDeserializer.class)
public class QBackendMetaData
{
private String name;
private String backendType;
// todo - at some point, we may want to apply this to secret properties on subclasses?
// @JsonFilter("secretsFilter")
/*******************************************************************************
** Default Constructor.
*******************************************************************************/
public QBackendMetaData()
{
}
/*******************************************************************************
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter, returning generically, to help sub-class fluent flows
*******************************************************************************/
@SuppressWarnings("unchecked")
public <T extends QBackendMetaData> T withName(String name)
{
this.name = name;
return (T) this;
}
/*******************************************************************************
** Getter for backendType
**
*******************************************************************************/
public String getBackendType()
{
return backendType;
}
/*******************************************************************************
** Setter for backendType
**
*******************************************************************************/
public void setBackendType(String backendType)
{
this.backendType = backendType;
}
/*******************************************************************************
** Setter for backendType
**
*******************************************************************************/
public void setBackendType(Class<? extends QBackendModuleInterface> backendModuleClass)
{
try
{
QBackendModuleInterface qBackendModuleInterface = backendModuleClass.getConstructor().newInstance();
this.backendType = qBackendModuleInterface.getBackendType();
}
catch(Exception e)
{
throw new IllegalArgumentException("Error dynamically getting backend type (name) from class [" + backendModuleClass.getName() + "], e)");
}
}
/*******************************************************************************
** Fluent setter, returning generically, to help sub-class fluent flows
*******************************************************************************/
@SuppressWarnings("unchecked")
public <T extends QBackendMetaData> T withBackendType(String backendType)
{
this.backendType = backendType;
return (T) this;
}
/*******************************************************************************
**
*******************************************************************************/
public QBackendMetaData withBackendType(Class<? extends QBackendModuleInterface> backendModuleClass)
{
setBackendType(backendModuleClass);
return (this);
}
/*******************************************************************************
** Called by the QInstanceEnricher - to do backend-type-specific enrichments.
** Original use case is: reading secrets into fields (e.g., passwords).
*******************************************************************************/
public void enrich()
{
////////////////////////
// noop in base class //
////////////////////////
}
}

View File

@@ -0,0 +1,381 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
/*******************************************************************************
** Container for all meta-data in a running instance of a QQQ application.
**
*******************************************************************************/
public class QInstance
{
///////////////////////////////////////////////////////////////////////////////
// Do not let the backend data be serialized - e.g., sent to a frontend user //
///////////////////////////////////////////////////////////////////////////////
@JsonIgnore
private Map<String, QBackendMetaData> backends = new HashMap<>();
private QAuthenticationMetaData authentication = null;
private Map<String, QTableMetaData> tables = new HashMap<>();
private Map<String, QPossibleValueSource<?>> possibleValueSources = new HashMap<>();
private Map<String, QProcessMetaData> processes = new HashMap<>();
// todo - lock down the object (no more changes allowed) after it's been validated?
@JsonIgnore
private boolean hasBeenValidated = false;
/*******************************************************************************
** Get the backend for a given table name
*******************************************************************************/
public QBackendMetaData getBackendForTable(String tableName)
{
QTableMetaData table = tables.get(tableName);
if(table == null)
{
throw (new IllegalArgumentException("No table with name [" + tableName + "] found in this instance."));
}
//////////////////////////////////////////////////////////////////////////////////////////////
// validation should already let us know that this is valid, so no need to check/throw here //
//////////////////////////////////////////////////////////////////////////////////////////////
return (backends.get(table.getBackendName()));
}
/*******************************************************************************
** Get the list of processes associated with a given table name
*******************************************************************************/
public List<QProcessMetaData> getProcessesForTable(String tableName)
{
List<QProcessMetaData> rs = new ArrayList<>();
for(QProcessMetaData process : processes.values())
{
if(tableName.equals(process.getTableName()))
{
rs.add(process);
}
}
return (rs);
}
/*******************************************************************************
** Setter for hasBeenValidated
**
*******************************************************************************/
public void setHasBeenValidated(QInstanceValidationKey key)
{
this.hasBeenValidated = true;
}
/*******************************************************************************
**
*******************************************************************************/
public void addBackend(QBackendMetaData backend)
{
addBackend(backend.getName(), backend);
}
/*******************************************************************************
**
*******************************************************************************/
public void addBackend(String name, QBackendMetaData backend)
{
if(this.backends.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second backend with name: " + name));
}
this.backends.put(name, backend);
}
/*******************************************************************************
**
*******************************************************************************/
public QBackendMetaData getBackend(String name)
{
return (this.backends.get(name));
}
/*******************************************************************************
**
*******************************************************************************/
public void addTable(QTableMetaData table)
{
addTable(table.getName(), table);
}
/*******************************************************************************
**
*******************************************************************************/
public void addTable(String name, QTableMetaData table)
{
if(this.tables.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second table with name: " + name));
}
this.tables.put(name, table);
}
/*******************************************************************************
**
*******************************************************************************/
public QTableMetaData getTable(String name)
{
return (this.tables.get(name));
}
/*******************************************************************************
**
*******************************************************************************/
public void addPossibleValueSource(QPossibleValueSource<?> possibleValueSource)
{
this.addPossibleValueSource(possibleValueSource.getName(), possibleValueSource);
}
/*******************************************************************************
**
*******************************************************************************/
public void addPossibleValueSource(String name, QPossibleValueSource possibleValueSource)
{
if(this.possibleValueSources.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second possibleValueSource with name: " + name));
}
this.possibleValueSources.put(name, possibleValueSource);
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource getPossibleValueSource(String name)
{
return (this.possibleValueSources.get(name));
}
/*******************************************************************************
**
*******************************************************************************/
public QStepMetaData getProcessStep(String processName, String functionName)
{
QProcessMetaData qProcessMetaData = this.processes.get(processName);
if(qProcessMetaData == null)
{
return (null);
}
return (qProcessMetaData.getStep(functionName));
}
/*******************************************************************************
**
*******************************************************************************/
public void addProcess(QProcessMetaData process)
{
this.addProcess(process.getName(), process);
}
/*******************************************************************************
**
*******************************************************************************/
public void addProcess(String name, QProcessMetaData process)
{
if(this.processes.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second process with name: " + name));
}
this.processes.put(name, process);
}
/*******************************************************************************
**
*******************************************************************************/
public QProcessMetaData getProcess(String name)
{
return (this.processes.get(name));
}
/*******************************************************************************
** Getter for backends
**
*******************************************************************************/
public Map<String, QBackendMetaData> getBackends()
{
return backends;
}
/*******************************************************************************
** Setter for backends
**
*******************************************************************************/
public void setBackends(Map<String, QBackendMetaData> backends)
{
this.backends = backends;
}
/*******************************************************************************
** Getter for tables
**
*******************************************************************************/
public Map<String, QTableMetaData> getTables()
{
return tables;
}
/*******************************************************************************
** Setter for tables
**
*******************************************************************************/
public void setTables(Map<String, QTableMetaData> tables)
{
this.tables = tables;
}
/*******************************************************************************
** Getter for possibleValueSources
**
*******************************************************************************/
public Map<String, QPossibleValueSource<?>> getPossibleValueSources()
{
return possibleValueSources;
}
/*******************************************************************************
** Setter for possibleValueSources
**
*******************************************************************************/
public void setPossibleValueSources(Map<String, QPossibleValueSource<?>> possibleValueSources)
{
this.possibleValueSources = possibleValueSources;
}
/*******************************************************************************
** Getter for processes
**
*******************************************************************************/
public Map<String, QProcessMetaData> getProcesses()
{
return processes;
}
/*******************************************************************************
** Setter for processes
**
*******************************************************************************/
public void setProcesses(Map<String, QProcessMetaData> processes)
{
this.processes = processes;
}
/*******************************************************************************
** Getter for hasBeenValidated
**
*******************************************************************************/
public boolean getHasBeenValidated()
{
return hasBeenValidated;
}
/*******************************************************************************
** Getter for authentication
**
*******************************************************************************/
public QAuthenticationMetaData getAuthentication()
{
return authentication;
}
/*******************************************************************************
** Setter for authentication
**
*******************************************************************************/
public void setAuthentication(QAuthenticationMetaData authentication)
{
this.authentication = authentication;
}
}

View File

@@ -0,0 +1,193 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.code;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
/*******************************************************************************
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
** maybe process steps, maybe customization to a table, etc.
*******************************************************************************/
public class QCodeReference
{
private String name;
private QCodeType codeType;
private QCodeUsage codeUsage;
/*******************************************************************************
** Default empty constructor
*******************************************************************************/
public QCodeReference()
{
}
/*******************************************************************************
** Constructor that takes all args
*******************************************************************************/
public QCodeReference(String name, QCodeType codeType, QCodeUsage codeUsage)
{
this.name = name;
this.codeType = codeType;
this.codeUsage = codeUsage;
}
/*******************************************************************************
** Constructor that just takes a java class, and infers the other fields.
*******************************************************************************/
public QCodeReference(Class<?> javaClass)
{
this.name = javaClass.getName();
this.codeType = QCodeType.JAVA;
if(BackendStep.class.isAssignableFrom(javaClass))
{
this.codeUsage = QCodeUsage.BACKEND_STEP;
}
else
{
throw (new IllegalStateException("Unable to infer code usage type for class: " + javaClass.getName()));
}
}
/*******************************************************************************
** Constructor that just takes a java class and code usage.
*******************************************************************************/
public QCodeReference(Class<?> javaClass, QCodeUsage codeUsage)
{
this.name = javaClass.getName();
this.codeType = QCodeType.JAVA;
this.codeUsage = codeUsage;
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public QCodeReference withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for codeType
**
*******************************************************************************/
public QCodeType getCodeType()
{
return codeType;
}
/*******************************************************************************
** Setter for codeType
**
*******************************************************************************/
public void setCodeType(QCodeType codeType)
{
this.codeType = codeType;
}
/*******************************************************************************
** Setter for codeType
**
*******************************************************************************/
public QCodeReference withCodeType(QCodeType codeType)
{
this.codeType = codeType;
return (this);
}
/*******************************************************************************
** Getter for codeUsage
**
*******************************************************************************/
public QCodeUsage getCodeUsage()
{
return codeUsage;
}
/*******************************************************************************
** Setter for codeUsage
**
*******************************************************************************/
public void setCodeUsage(QCodeUsage codeUsage)
{
this.codeUsage = codeUsage;
}
/*******************************************************************************
** Setter for codeUsage
**
*******************************************************************************/
public QCodeReference withCodeUsage(QCodeUsage codeUsage)
{
this.codeUsage = codeUsage;
return (this);
}
}

View File

@@ -0,0 +1,32 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.code;
/*******************************************************************************
** Possible types for Q-Code entities
**
*******************************************************************************/
public enum QCodeType
{
JAVA
}

View File

@@ -0,0 +1,33 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.code;
/*******************************************************************************
** Possible usages for Q-Code entities
**
*******************************************************************************/
public enum QCodeUsage
{
BACKEND_STEP, // a backend-step in a process
CUSTOMIZER // a function to customize part of a QQQ table's behavior
}

View File

@@ -0,0 +1,357 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.io.Serializable;
import java.lang.reflect.Method;
import com.github.hervian.reflection.Fun;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Meta-data to represent a single field in a table.
**
*******************************************************************************/
public class QFieldMetaData
{
private String name;
private String label;
private String backendName;
private QFieldType type;
private boolean isRequired = false;
private boolean isEditable = true;
///////////////////////////////////////////////////////////////////////////////////
// if we need "only edit on insert" or "only edit on update" in the future, //
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
///////////////////////////////////////////////////////////////////////////////////
private Serializable defaultValue;
private String possibleValueSourceName;
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData()
{
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData(String name, QFieldType type)
{
this.name = name;
this.type = type;
}
/*******************************************************************************
** Initialize a fieldMetaData from a reference to a getter on an entity.
** e.g., new QFieldMetaData(Order::getOrderNo).
*******************************************************************************/
public <T> QFieldMetaData(Fun.With1ParamAndVoid<T> getterRef) throws QException
{
try
{
Method getter = Fun.toMethod(getterRef);
this.name = QRecordEntity.getFieldNameFromGetter(getter);
this.type = QFieldType.fromClass(getter.getReturnType());
}
catch(QException qe)
{
throw (qe);
}
catch(Exception e)
{
throw (new QException("Error constructing field from getterRef: " + getterRef, e));
}
}
/*******************************************************************************
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public QFieldType getType()
{
return type;
}
/*******************************************************************************
** Setter for type
**
*******************************************************************************/
public void setType(QFieldType type)
{
this.type = type;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withType(QFieldType type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Setter for label
**
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for backendName
**
*******************************************************************************/
public String getBackendName()
{
return backendName;
}
/*******************************************************************************
** Setter for backendName
**
*******************************************************************************/
public void setBackendName(String backendName)
{
this.backendName = backendName;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public String getPossibleValueSourceName()
{
return possibleValueSourceName;
}
/*******************************************************************************
**
*******************************************************************************/
public void setPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
return (this);
}
/*******************************************************************************
** Getter for defaultValue
**
*******************************************************************************/
public Serializable getDefaultValue()
{
return defaultValue;
}
/*******************************************************************************
** Setter for defaultValue
**
*******************************************************************************/
public void setDefaultValue(Serializable defaultValue)
{
this.defaultValue = defaultValue;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withDefaultValue(Serializable defaultValue)
{
this.defaultValue = defaultValue;
return (this);
}
/*******************************************************************************
** Getter for isRequired
**
*******************************************************************************/
public boolean getIsRequired()
{
return isRequired;
}
/*******************************************************************************
** Setter for isRequired
**
*******************************************************************************/
public void setIsRequired(boolean isRequired)
{
this.isRequired = isRequired;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withIsRequired(boolean isRequired)
{
this.isRequired = isRequired;
return (this);
}
/*******************************************************************************
** Getter for isEditable
**
*******************************************************************************/
public boolean getIsEditable()
{
return isEditable;
}
/*******************************************************************************
** Setter for isEditable
**
*******************************************************************************/
public void setIsEditable(boolean isEditable)
{
this.isEditable = isEditable;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withIsEditable(boolean isEditable)
{
this.isEditable = isEditable;
return (this);
}
}

View File

@@ -0,0 +1,71 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.math.BigDecimal;
import com.kingsrook.qqq.backend.core.exceptions.QException;
/*******************************************************************************
** Possible data types for Q-fields.
**
*******************************************************************************/
public enum QFieldType
{
STRING,
INTEGER,
DECIMAL,
DATE,
// TIME,
DATE_TIME,
TEXT,
HTML,
PASSWORD,
BLOB;
///////////////////////////////////////////////////////////////////////
// keep these values in sync with QFieldType.ts in qqq-frontend-core //
///////////////////////////////////////////////////////////////////////
/*******************************************************************************
** Get a field type enum constant for a java class.
*******************************************************************************/
public static QFieldType fromClass(Class<?> c) throws QException
{
if(c.equals(String.class))
{
return (STRING);
}
if(c.equals(Integer.class) || c.equals(int.class))
{
return (INTEGER);
}
if(c.equals(BigDecimal.class))
{
return (DECIMAL);
}
throw (new QException("Unrecognized class [" + c + "]"));
}
}

View File

@@ -0,0 +1,118 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
/*******************************************************************************
* Version of QFieldMetaData that's meant for transmitting to a frontend.
* e.g., it excludes backend-only details.
*
*******************************************************************************/
@JsonInclude(Include.NON_NULL)
public class QFrontendFieldMetaData
{
private String name;
private String label;
private QFieldType type;
private boolean isRequired;
private boolean isEditable;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
/*******************************************************************************
** Constructor
*******************************************************************************/
public QFrontendFieldMetaData(QFieldMetaData fieldMetaData)
{
this.name = fieldMetaData.getName();
this.label = fieldMetaData.getLabel();
this.type = fieldMetaData.getType();
this.isRequired = fieldMetaData.getIsRequired();
this.isEditable = fieldMetaData.getIsEditable();
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public QFieldType getType()
{
return type;
}
/*******************************************************************************
** Getter for isRequired
**
*******************************************************************************/
public boolean getIsRequired()
{
return isRequired;
}
/*******************************************************************************
** Getter for isEditable
**
*******************************************************************************/
public boolean getIsEditable()
{
return isEditable;
}
}

View File

@@ -0,0 +1,159 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
* Version of QProcessMetaData that's meant for transmitting to a frontend.
* e.g., it excludes backend-only details.
*
*******************************************************************************/
@JsonInclude(Include.NON_NULL)
public class QFrontendProcessMetaData
{
private String name;
private String label;
private String tableName;
private boolean isHidden;
private List<QFrontendStepMetaData> frontendSteps;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
/*******************************************************************************
**
*******************************************************************************/
public QFrontendProcessMetaData(QProcessMetaData processMetaData, boolean includeSteps)
{
this.name = processMetaData.getName();
this.label = processMetaData.getLabel();
this.tableName = processMetaData.getTableName();
this.isHidden = processMetaData.getIsHidden();
if(includeSteps)
{
if(CollectionUtils.nullSafeHasContents(processMetaData.getStepList()))
{
this.frontendSteps = processMetaData.getStepList().stream()
.filter(QFrontendStepMetaData.class::isInstance)
.map(QFrontendStepMetaData.class::cast)
.collect(Collectors.toList());
}
else
{
frontendSteps = new ArrayList<>();
}
}
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Getter for primaryKeyField
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Getter for frontendSteps
**
*******************************************************************************/
public List<QFrontendStepMetaData> getFrontendSteps()
{
return frontendSteps;
}
/*******************************************************************************
** Setter for frontendSteps
**
*******************************************************************************/
public void setFrontendSteps(List<QFrontendStepMetaData> frontendSteps)
{
this.frontendSteps = frontendSteps;
}
/*******************************************************************************
** Getter for isHidden
**
*******************************************************************************/
public boolean getIsHidden()
{
return isHidden;
}
/*******************************************************************************
** Setter for isHidden
**
*******************************************************************************/
public void setIsHidden(boolean isHidden)
{
this.isHidden = isHidden;
}
}

View File

@@ -0,0 +1,139 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
* Version of QTableMetaData that's meant for transmitting to a frontend.
* e.g., it excludes backend-only details.
*
*******************************************************************************/
@JsonInclude(Include.NON_NULL)
public class QFrontendTableMetaData
{
private String name;
private String label;
private boolean isHidden;
private String primaryKeyField;
private Map<String, QFrontendFieldMetaData> fields;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
/*******************************************************************************
**
*******************************************************************************/
public QFrontendTableMetaData(QTableMetaData tableMetaData, boolean includeFields)
{
this.name = tableMetaData.getName();
this.label = tableMetaData.getLabel();
this.isHidden = tableMetaData.getIsHidden();
if(includeFields)
{
this.primaryKeyField = tableMetaData.getPrimaryKeyField();
this.fields = new HashMap<>();
for(Map.Entry<String, QFieldMetaData> entry : tableMetaData.getFields().entrySet())
{
this.fields.put(entry.getKey(), new QFrontendFieldMetaData(entry.getValue()));
}
}
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Getter for primaryKeyField
**
*******************************************************************************/
public String getPrimaryKeyField()
{
return primaryKeyField;
}
/*******************************************************************************
** Getter for fields
**
*******************************************************************************/
public Map<String, QFrontendFieldMetaData> getFields()
{
return fields;
}
/*******************************************************************************
** Getter for isHidden
**
*******************************************************************************/
public boolean getIsHidden()
{
return isHidden;
}
/*******************************************************************************
** Setter for isHidden
**
*******************************************************************************/
public void setIsHidden(boolean isHidden)
{
this.isHidden = isHidden;
}
}

View File

@@ -0,0 +1,161 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
import java.util.ArrayList;
import java.util.List;
/*******************************************************************************
** Meta-data to represent a single field in a table.
**
*******************************************************************************/
public class QPossibleValueSource<T>
{
private String name;
private QPossibleValueSourceType type;
// should these be in sub-types??
private List<T> enumValues;
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource()
{
}
/*******************************************************************************
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource<T> withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSourceType getType()
{
return type;
}
/*******************************************************************************
**
*******************************************************************************/
public void setType(QPossibleValueSourceType type)
{
this.type = type;
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource<T> withType(QPossibleValueSourceType type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Getter for enumValues
**
*******************************************************************************/
public List<T> getEnumValues()
{
return enumValues;
}
/*******************************************************************************
** Setter for enumValues
**
*******************************************************************************/
public void setEnumValues(List<T> enumValues)
{
this.enumValues = enumValues;
}
/*******************************************************************************
** Fluent setter for enumValues
**
*******************************************************************************/
public QPossibleValueSource<T> withEnumValues(List<T> enumValues)
{
this.enumValues = enumValues;
return this;
}
/*******************************************************************************
** Fluent adder for enumValues
**
*******************************************************************************/
public QPossibleValueSource<T> addEnumValue(T enumValue)
{
if(this.enumValues == null)
{
this.enumValues = new ArrayList<>();
}
this.enumValues.add(enumValue);
return this;
}
}

Some files were not shown because too many files have changed in this diff Show More