Adhoc Query
This commit is contained in:
+170
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.fineract.adhocquery.data.AdHocData;
|
||||
import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/adhocquery")
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
public class AdHocApiResource {
|
||||
|
||||
/**
|
||||
* The set of parameters that are supported in response for {@link AdhocData}
|
||||
*/
|
||||
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "query", "tableName","tableField","isActive","createdBy","createdOn","createdById","updatedById","updatedOn","email"));
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final AdHocReadPlatformService adHocReadPlatformService;
|
||||
private final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer;
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
public AdHocApiResource(final PlatformSecurityContext context, final AdHocReadPlatformService readPlatformService,
|
||||
final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer,
|
||||
final ApiRequestParameterHelper apiRequestParameterHelper,
|
||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
|
||||
this.context = context;
|
||||
this.adHocReadPlatformService = readPlatformService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveAll(@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllAdHocQuery();
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, adhocs, this.RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Path("template")
|
||||
public String template(@Context final UriInfo uriInfo) {
|
||||
this.context.authenticatedUser();
|
||||
final AdHocData user = this.adHocReadPlatformService.retrieveNewAdHocDetails();
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, user, this.RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String createAdHocQuery(final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.createAdHoc() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{adHocId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String retrieveAdHocQuery(@PathParam("adHocId") final Long adHocId, @Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
|
||||
final AdHocData adhoc = this.adHocReadPlatformService.retrieveOne(adHocId);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(settings, adhoc, this.RESPONSE_DATA_PARAMETERS);
|
||||
}
|
||||
@PUT
|
||||
@Path("{adHocId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String update(@PathParam("adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.updateAdHoc(adHocId) //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
/**
|
||||
* Delete AdHocQuery
|
||||
*
|
||||
* @param adHocId
|
||||
* @return
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{adHocId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String deleteAdHocQuery(@PathParam("adHocId") final Long adHocId) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.deleteAdHoc(adHocId) //
|
||||
.build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
private boolean is(final String commandParam, final String commandValue) {
|
||||
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.api;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/***
|
||||
* Enum of all parameters passed in while creating/updating a AdHocQuery
|
||||
***/
|
||||
public enum AdHocJsonInputParams {
|
||||
ID("id"), NAME("name"),QUERY("query"),TABLENAME("tableName"),TABLEFIELD("tableFields"), ISACTIVE("isActive"),EMAIL("email");
|
||||
|
||||
private final String value;
|
||||
|
||||
private AdHocJsonInputParams(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static final Set<String> values = new HashSet<>();
|
||||
static {
|
||||
for (final AdHocJsonInputParams type : AdHocJsonInputParams.values()) {
|
||||
values.add(type.value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> getAllValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toString().replaceAll("_", " ");
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.data;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.fineract.organisation.office.data.OfficeData;
|
||||
import org.apache.fineract.useradministration.data.AppUserData;
|
||||
import org.apache.fineract.useradministration.data.RoleData;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Immutable data object represent note or case information AdHocData
|
||||
*
|
||||
*/
|
||||
public class AdHocData {
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long id;
|
||||
@SuppressWarnings("unused")
|
||||
private final String name;
|
||||
@SuppressWarnings("unused")
|
||||
private final String query;
|
||||
@SuppressWarnings("unused")
|
||||
private final String tableName;
|
||||
@SuppressWarnings("unused")
|
||||
private final String tableFields;
|
||||
@SuppressWarnings("unused")
|
||||
private final String email;
|
||||
@SuppressWarnings("unused")
|
||||
private final boolean isActive;
|
||||
@SuppressWarnings("unused")
|
||||
private final DateTime createdOn;
|
||||
@SuppressWarnings("unused")
|
||||
private final Long createdById;
|
||||
@SuppressWarnings("unused")
|
||||
private final Long updatedById;
|
||||
@SuppressWarnings("unused")
|
||||
private final DateTime updatedOn;
|
||||
@SuppressWarnings("unused")
|
||||
private final String createdBy;
|
||||
|
||||
|
||||
|
||||
|
||||
public AdHocData(final Long id, final String name,final String query, final String tableName,final String tableFields,
|
||||
final boolean isActive, final DateTime createdOn, final Long createdById,final Long updatedById,
|
||||
final DateTime updatedOn,final String createdBy,final String email
|
||||
) {
|
||||
this.id = id;
|
||||
this.name=name;
|
||||
this.query=query;
|
||||
this.tableName = tableName;
|
||||
this.tableFields = tableFields;
|
||||
this.isActive = isActive;
|
||||
this.createdOn = createdOn;
|
||||
this.createdById = createdById;
|
||||
this.updatedById=updatedById;
|
||||
this.updatedOn=updatedOn;
|
||||
this.createdBy=createdBy;
|
||||
this.email=email;
|
||||
}
|
||||
public static AdHocData template() {
|
||||
AdHocData adHocData = new AdHocData(null,null,null,null,null,false,null,null,null,null,null,null);
|
||||
return adHocData;
|
||||
}
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
public String getQuery() {
|
||||
return this.query;
|
||||
}
|
||||
public String getTableName() {
|
||||
return this.tableName;
|
||||
}
|
||||
public String getTableFields() {
|
||||
return this.tableFields;
|
||||
}
|
||||
public String getEmail() {
|
||||
return this.email;
|
||||
}
|
||||
public boolean isActive() {
|
||||
return this.isActive;
|
||||
}
|
||||
public DateTime getCreatedOn() {
|
||||
return this.createdOn;
|
||||
}
|
||||
public Long getCreatedById() {
|
||||
return this.createdById;
|
||||
}
|
||||
public Long getUpdatedById() {
|
||||
return this.updatedById;
|
||||
}
|
||||
public DateTime getUpdatedOn() {
|
||||
return this.updatedOn;
|
||||
}
|
||||
public String getCreatedBy() {
|
||||
return this.createdBy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.domain;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.fineract.adhocquery.api.AdHocJsonInputParams;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
|
||||
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_adhoc")
|
||||
public class AdHoc extends AbstractAuditableCustom<AppUser, Long> {
|
||||
|
||||
@Column(name = "name", length = 100)
|
||||
private String name;
|
||||
|
||||
@Column(name = "query", length = 2000)
|
||||
private String query;
|
||||
|
||||
@Column(name = "table_name", length = 100)
|
||||
private String tableName;
|
||||
|
||||
|
||||
@Column(name = "table_fields", length = 2000)
|
||||
private String tableFields;
|
||||
|
||||
@Column(name = "email", length = 500)
|
||||
private String email;
|
||||
|
||||
|
||||
@Column(name = "IsActive", nullable = false)
|
||||
private boolean isActive = false;
|
||||
|
||||
private AdHoc(final String name, final String query,final String tableName,final String tableFields ,final String email,final boolean isActive) {
|
||||
this.name = StringUtils.defaultIfEmpty(name, null);
|
||||
this.query=StringUtils.defaultIfEmpty(query,null);
|
||||
this.tableName=StringUtils.defaultIfEmpty(tableName,null);
|
||||
this.tableFields=StringUtils.defaultIfEmpty(tableFields,null);
|
||||
this.email=StringUtils.defaultIfEmpty(email,null);
|
||||
this.isActive = BooleanUtils.toBooleanDefaultIfNull(isActive, false);
|
||||
|
||||
}
|
||||
public static AdHoc fromJson(final JsonCommand command) {
|
||||
final String name = command.stringValueOfParameterNamed(AdHocJsonInputParams.NAME.getValue());
|
||||
|
||||
String commandQuery=command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
|
||||
|
||||
SQLInjectionValidator.validateAdhocQuery(commandQuery);
|
||||
final String query = commandQuery;
|
||||
final String tableName = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLENAME.getValue());
|
||||
final String tableFields = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLEFIELD.getValue());
|
||||
final String email = command.stringValueOfParameterNamed(AdHocJsonInputParams.EMAIL.getValue());
|
||||
final boolean isActive = command.booleanPrimitiveValueOfParameterNamed(AdHocJsonInputParams.ISACTIVE.getValue());
|
||||
return new AdHoc(name,query,tableName,tableFields ,email,isActive);
|
||||
}
|
||||
|
||||
public Map<String, Object> update(final JsonCommand command) {
|
||||
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
|
||||
|
||||
final String nameParamName = "name";
|
||||
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(nameParamName);
|
||||
actualChanges.put(nameParamName, newValue);
|
||||
this.name = newValue;
|
||||
}
|
||||
|
||||
final String descriptionParamName = "query";
|
||||
if (command.isChangeInStringParameterNamed(descriptionParamName, this.query)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
|
||||
actualChanges.put(descriptionParamName, newValue);
|
||||
this.query = newValue;
|
||||
}
|
||||
final String tableName = "tableName";
|
||||
if (command.isChangeInStringParameterNamed(tableName, this.tableName)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(tableName);
|
||||
actualChanges.put(tableName, newValue);
|
||||
this.tableName = newValue;
|
||||
}
|
||||
final String tableField = "tableField";
|
||||
if (command.isChangeInStringParameterNamed(tableField, this.tableFields)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(tableField);
|
||||
actualChanges.put(tableField, newValue);
|
||||
this.tableFields = newValue;
|
||||
}
|
||||
final String email = "email";
|
||||
if (command.isChangeInStringParameterNamed(email, this.email)) {
|
||||
final String newValue = command.stringValueOfParameterNamed(email);
|
||||
actualChanges.put(email, newValue);
|
||||
this.email = newValue;
|
||||
}
|
||||
final String paramisActive = "isActive";
|
||||
if (command.isChangeInBooleanParameterNamed(paramisActive, this.isActive)) {
|
||||
final Boolean newValue = command.booleanObjectValueOfParameterNamed(paramisActive);
|
||||
actualChanges.put(paramisActive, newValue);
|
||||
this.isActive = newValue;
|
||||
}
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
public String getTableFields() {
|
||||
return tableFields;
|
||||
}
|
||||
public boolean isActive() {
|
||||
return this.isActive;
|
||||
}
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
public void disableActive() {
|
||||
this.isActive = true;
|
||||
}
|
||||
public void enableActive() {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface AdHocRepository extends JpaRepository<AdHoc, Long>, JpaSpecificationExecutor<AdHoc> {
|
||||
// no added behaviour
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} thrown when AdHoc resources are not
|
||||
* found.
|
||||
*/
|
||||
public class AdHocNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public AdHocNotFoundException(final Long id) {
|
||||
super("error.msg.adhocquery.adhoc.id.invalid", "Adhoc Record with identifier " + id + " does not exist", id);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.handler;
|
||||
|
||||
import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "ADHOC", action = "CREATE")
|
||||
public class CreateAdHocCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AdHocWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.createAdHocQuery(command);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "ADHOC", action = "DELETE")
|
||||
public class DeleteAdHocCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AdHocWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public DeleteAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
return this.writePlatformService.deleteAdHocQuery(command.entityId());
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "ADHOC", action = "UPDATE")
|
||||
public class UpdateAdHocCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AdHocWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
|
||||
final Long adHocId = command.entityId();
|
||||
return this.writePlatformService.updateAdHocQuery(adHocId, command);
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@Component
|
||||
public final class AdHocDataValidator {
|
||||
|
||||
/**
|
||||
* The parameters supported for this command.
|
||||
*/
|
||||
private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name","query","tableName","tableFields","email","isActive"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AdHocDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("adhoc");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
final String description = this.fromApiJsonHelper.extractStringNamed("query", element);
|
||||
baseDataValidator.reset().parameter("query").value(description).notBlank().notExceedingLengthOf(2000);
|
||||
|
||||
final String tableName = this.fromApiJsonHelper.extractStringNamed("tableName", element);
|
||||
baseDataValidator.reset().parameter("tableName").value(tableName).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
final String tableFields = this.fromApiJsonHelper.extractStringNamed("tableFields", element);
|
||||
baseDataValidator.reset().parameter("tableFields").value(tableFields).notBlank().notExceedingLengthOf(1000);
|
||||
|
||||
final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
|
||||
baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(500);
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("adhoc");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("name", element)) {
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
|
||||
baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("query", element)) {
|
||||
final String query = this.fromApiJsonHelper.extractStringNamed("query", element);
|
||||
baseDataValidator.reset().parameter("query").value(query).notBlank().notExceedingLengthOf(2000);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("tableName", element)) {
|
||||
final String tableName = this.fromApiJsonHelper.extractStringNamed("tableName", element);
|
||||
baseDataValidator.reset().parameter("tableName").value(tableName).notBlank().notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("tableFields", element)) {
|
||||
final String tableField = this.fromApiJsonHelper.extractStringNamed("tableFields", element);
|
||||
baseDataValidator.reset().parameter("tableFields").value(tableField).notBlank().notExceedingLengthOf(2000);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("email", element)) {
|
||||
final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
|
||||
baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(500);
|
||||
}
|
||||
/*if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
|
||||
final Integer isActive = this.fromApiJsonHelper.extractIntegerNamed("isActive", element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter("isActive").value(isActive).notNull().inMinMaxRange(1, 2);
|
||||
}*/
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.fineract.adhocquery.data.AdHocData;
|
||||
|
||||
public interface AdHocReadPlatformService {
|
||||
|
||||
Collection<AdHocData> retrieveAllAdHocQuery();
|
||||
|
||||
Collection<AdHocData> retrieveAllActiveAdHocQuery();
|
||||
|
||||
AdHocData retrieveOne(Long adHocId);
|
||||
AdHocData retrieveNewAdHocDetails();
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.fineract.adhocquery.data.AdHocData;
|
||||
import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||
import org.joda.time.DateTime;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final AdHocMapper adHocRowMapper;
|
||||
|
||||
@Autowired
|
||||
public AdHocReadPlatformServiceImpl(final RoutingDataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.adHocRowMapper = new AdHocMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AdHocData> retrieveAllAdHocQuery() {
|
||||
final String sql = "select " + this.adHocRowMapper.schema() + " order by r.id";
|
||||
|
||||
return this.jdbcTemplate.query(sql, this.adHocRowMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AdHocData> retrieveAllActiveAdHocQuery() {
|
||||
final String sql = "select " + this.adHocRowMapper.schema() + " where r.IsActive = 1 order by r.id";
|
||||
|
||||
return this.jdbcTemplate.query(sql, this.adHocRowMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdHocData retrieveOne(final Long id) {
|
||||
|
||||
try {
|
||||
final String sql = "select " + this.adHocRowMapper.schema() + " where r.id=?";
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id });
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AdHocNotFoundException(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final class AdHocMapper implements RowMapper<AdHocData> {
|
||||
|
||||
@Override
|
||||
public AdHocData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = JdbcSupport.getLong(rs, "id");
|
||||
final String name = rs.getString("name");
|
||||
final String query = rs.getString("query");
|
||||
final String tableName=rs.getString("tableName");
|
||||
final String tableFields=rs.getString("tableField");
|
||||
final Boolean isActive = rs.getBoolean("isActive");
|
||||
final DateTime createdDate = JdbcSupport.getDateTime(rs, "createdDate");
|
||||
final Long createdById = JdbcSupport.getLong(rs, "createdById");
|
||||
final Long updatedById=JdbcSupport.getLong(rs, "updatedById");
|
||||
final DateTime updatedOn=JdbcSupport.getDateTime(rs, "updatedOn");
|
||||
final String createdByUsername=rs.getString("createdBy");
|
||||
final String email=rs.getString("email");
|
||||
|
||||
return new AdHocData(id,name,query, tableName,tableFields,isActive,createdDate,createdById,updatedById,updatedOn,createdByUsername,email);
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return " r.id as id, r.name as name, r.query as query, r.table_name as tableName,r.table_fields as tableField ,r.IsActive as isActive ,r.email as email ,"
|
||||
+" r.created_date as createdDate, r.createdby_id as createdById,cb.username as createdBy,r.lastmodifiedby_id as updatedById ,r.lastmodified_date as updatedOn "
|
||||
+ " from m_adhoc r left join m_appuser cb on cb.id=r.createdby_id left join m_appuser mb on mb.id=r.lastmodifiedby_id";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdHocData retrieveNewAdHocDetails() {
|
||||
return AdHocData.template();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
public interface AdHocScheduledJobRunnerService {
|
||||
void generateClientSchedule();
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.fineract.adhocquery.data.AdHocData;
|
||||
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service(value = "adHocScheduledJobRunnerService")
|
||||
public class AdHocScheduledJobRunnerServiceImpl implements AdHocScheduledJobRunnerService {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(AdHocScheduledJobRunnerServiceImpl.class);
|
||||
private final AdHocReadPlatformService adHocReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public AdHocScheduledJobRunnerServiceImpl(final RoutingDataSource dataSource,
|
||||
final AdHocReadPlatformService adHocReadPlatformService
|
||||
) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.adHocReadPlatformService = adHocReadPlatformService;
|
||||
|
||||
}
|
||||
@Transactional
|
||||
@Override
|
||||
@CronTarget(jobName = JobName.GENERATE_ADHOCCLIENT_SCEHDULE)
|
||||
public void generateClientSchedule() {
|
||||
final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllActiveAdHocQuery();
|
||||
if(adhocs.size()>0){
|
||||
adhocs.forEach(adhoc->{
|
||||
//jdbcTemplate.execute("truncate table "+adhoc.getTableName());
|
||||
final StringBuilder insertSqlBuilder = new StringBuilder(900);
|
||||
insertSqlBuilder
|
||||
.append("INSERT INTO ")
|
||||
.append(adhoc.getTableName()+"(")
|
||||
.append(adhoc.getTableFields()+") ")
|
||||
.append(adhoc.getQuery());
|
||||
if (insertSqlBuilder.length() > 0) {
|
||||
final int result = this.jdbcTemplate.update(insertSqlBuilder.toString());
|
||||
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by inserted: " + result);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
logger.info(ThreadLocalContextUtil.getTenant().getName() + "Nothing to update ");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface AdHocWritePlatformService {
|
||||
|
||||
CommandProcessingResult createAdHocQuery(JsonCommand command);
|
||||
|
||||
CommandProcessingResult updateAdHocQuery(Long adHocId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult deleteAdHocQuery(Long adHocId);
|
||||
|
||||
CommandProcessingResult disableAdHocQuery(Long adHocId);
|
||||
|
||||
CommandProcessingResult enableAdHocQuery(Long adHocId);
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.fineract.adhocquery.domain.AdHoc;
|
||||
import org.apache.fineract.adhocquery.domain.AdHocRepository;
|
||||
import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(AdHocWritePlatformServiceJpaRepositoryImpl.class);
|
||||
private final PlatformSecurityContext context;
|
||||
private final AdHocRepository adHocRepository;
|
||||
private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
|
||||
|
||||
|
||||
@Autowired
|
||||
public AdHocWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AdHocRepository adHocRepository,
|
||||
final AdHocDataValidator adHocCommandFromApiJsonDeserializer) {
|
||||
this.context = context;
|
||||
this.adHocRepository = adHocRepository;
|
||||
this.adHocCommandFromApiJsonDeserializer = adHocCommandFromApiJsonDeserializer;
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createAdHocQuery(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
this.adHocCommandFromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
final AdHoc entity = AdHoc.fromJson(command);
|
||||
this.adHocRepository.save(entity);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(entity.getId()).build();
|
||||
} catch (final DataIntegrityViolationException dve) {
|
||||
handleDataIntegrityIssues(command, dve);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Guaranteed to throw an exception no matter what the data integrity issue
|
||||
* is.
|
||||
*/
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
|
||||
|
||||
final Throwable realCause = dve.getMostSpecificCause();
|
||||
if (realCause.getMessage().contains("unq_name")) {
|
||||
|
||||
final String name = command.stringValueOfParameterNamed("name");
|
||||
throw new PlatformDataIntegrityException("error.msg.adhocquery.duplicate.name", "AdHocQuery with name `" + name + "` already exists",
|
||||
"name", name);
|
||||
}
|
||||
|
||||
logAsErrorUnexpectedDataIntegrityException(dve);
|
||||
throw new PlatformDataIntegrityException("error.msg.adhocquery.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource.");
|
||||
}
|
||||
|
||||
private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
|
||||
logger.error(dve.getMessage(), dve);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateAdHocQuery(final Long adHocId, final JsonCommand command) {
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
this.adHocCommandFromApiJsonDeserializer.validateForUpdate(command.json());
|
||||
|
||||
final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
|
||||
if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
|
||||
|
||||
final Map<String, Object> changes = adHoc.update(command);
|
||||
if (!changes.isEmpty()) {
|
||||
this.adHocRepository.saveAndFlush(adHoc);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(adHocId) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
} catch (final DataIntegrityViolationException dve) {
|
||||
handleDataIntegrityIssues(command, dve);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Method for Delete adhoc
|
||||
*/
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult deleteAdHocQuery(Long adHocId) {
|
||||
|
||||
try {
|
||||
/**
|
||||
* Checking the adhocQuery present in DB or not using adHocId
|
||||
*/
|
||||
final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
|
||||
if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
|
||||
|
||||
this.adHocRepository.delete(adHoc);
|
||||
return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
|
||||
} catch (final DataIntegrityViolationException e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + e.getMostSpecificCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for disabling the adhocquery
|
||||
*/
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult disableAdHocQuery(Long adHocId) {
|
||||
try {
|
||||
/**
|
||||
* Checking the adhocquery present in DB or not using adHocId
|
||||
*/
|
||||
final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
|
||||
if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
|
||||
adHoc.disableActive();
|
||||
this.adHocRepository.save(adHoc);
|
||||
return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
|
||||
|
||||
} catch (final DataIntegrityViolationException e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + e.getMostSpecificCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for Enabling the Active
|
||||
*/
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult enableAdHocQuery(Long adHocId) {
|
||||
try {
|
||||
/**
|
||||
* Checking the adHoc present in DB or not using id
|
||||
*/
|
||||
final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
|
||||
if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
|
||||
adHoc.enableActive();
|
||||
this.adHocRepository.save(adHoc);
|
||||
return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
|
||||
|
||||
} catch (final DataIntegrityViolationException e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + e.getMostSpecificCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -2977,4 +2977,43 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/savingsaccounts/" + accountId + "?command=unblock";
|
||||
return this;
|
||||
}
|
||||
public CommandWrapperBuilder disableAdHoc(Long adHocId) {
|
||||
this.actionName = "DISABLE";
|
||||
this.entityName = "ADHOC";
|
||||
this.entityId = adHocId;
|
||||
this.href = "/adhoc/" + adHocId + "/disbale";
|
||||
this.json = "{}";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder enableAdHoc(Long adHocId) {
|
||||
this.actionName = "ENABLE";
|
||||
this.entityName = "ADHOC";
|
||||
this.entityId = adHocId;
|
||||
this.href = "/adhoc/" + adHocId + "/enable";
|
||||
this.json = "{}";
|
||||
return this;
|
||||
}
|
||||
public CommandWrapperBuilder createAdHoc() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "ADHOC";
|
||||
this.href = "/adhocquery/template";
|
||||
return this;
|
||||
}
|
||||
public CommandWrapperBuilder updateAdHoc(final Long adHocId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "ADHOC";
|
||||
this.entityId = adHocId;
|
||||
this.href = "/adhocquery/" + adHocId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder deleteAdHoc(Long adHocId) {
|
||||
this.actionName = "DELETE";
|
||||
this.entityName = "ADHOC";
|
||||
this.entityId = adHocId;
|
||||
this.href = "/adhocquery/" + adHocId;
|
||||
this.json = "{}";
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -45,8 +45,8 @@ public enum JobName {
|
||||
EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"),
|
||||
UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update SMS Outbound with Campaign Message"),
|
||||
SEND_MESSAGES_TO_SMS_GATEWAY("Send Messages to SMS Gateway"),
|
||||
GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway");
|
||||
|
||||
GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway"),
|
||||
GENERATE_ADHOCCLIENT_SCEHDULE("Generate AdhocClient Schedule");
|
||||
private final String name;
|
||||
|
||||
private JobName(final String name) {
|
||||
|
||||
+77
@@ -46,6 +46,83 @@ public class SQLInjectionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
for (String comments : COMMENTS) {
|
||||
if (lowerCaseSQL.contains(comments)) {
|
||||
throw new SQLInjectionException();
|
||||
}
|
||||
}
|
||||
|
||||
//Removing the space before and after '=' operator
|
||||
//String s = " \" OR 1 = 1"; For the cases like this
|
||||
boolean injectionFound = false;
|
||||
String inputSqlString = lowerCaseSQL;
|
||||
while (inputSqlString.indexOf(" =") > 0) { //Don't remove space before = operator
|
||||
inputSqlString = inputSqlString.replaceAll(" =", "=");
|
||||
}
|
||||
|
||||
while (inputSqlString.indexOf("= ") > 0) { //Don't remove space after = operator
|
||||
inputSqlString = inputSqlString.replaceAll("= ", "=");
|
||||
}
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(inputSqlString, " ");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken().trim();
|
||||
if (token.equals("'")) {
|
||||
if (tokenizer.hasMoreElements()) {
|
||||
String nextToken = tokenizer.nextToken().trim();
|
||||
if (!nextToken.equals("'")) {
|
||||
injectionFound = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
injectionFound = true;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
if (token.equals("\"")) {
|
||||
if (tokenizer.hasMoreElements()) {
|
||||
String nextToken = tokenizer.nextToken().trim();
|
||||
if (!nextToken.equals("\"")) {
|
||||
injectionFound = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
injectionFound = true;
|
||||
break ;
|
||||
}
|
||||
} else if (token.indexOf('=') > 0) {
|
||||
StringTokenizer operatorToken = new StringTokenizer(token, "=");
|
||||
String operand = operatorToken.nextToken().trim();
|
||||
if (!operatorToken.hasMoreTokens()) {
|
||||
injectionFound = true;
|
||||
break;
|
||||
}
|
||||
String value = operatorToken.nextToken().trim();
|
||||
if (operand.equals(value)) {
|
||||
injectionFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (injectionFound) {
|
||||
throw new SQLInjectionException();
|
||||
}
|
||||
|
||||
Pattern pattern = Pattern.compile(SQL_PATTERN);
|
||||
Matcher matcher = pattern.matcher(sqlSearch);
|
||||
if (!matcher.matches()) {
|
||||
throw new SQLInjectionException();
|
||||
}
|
||||
}
|
||||
public final static void validateAdhocQuery(final String sqlSearch) {
|
||||
String lowerCaseSQL = sqlSearch.toLowerCase();
|
||||
for (String ddl : DDL_COMMANDS) {
|
||||
if (lowerCaseSQL.contains(ddl)) {
|
||||
throw new SQLInjectionException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (String comments : COMMENTS) {
|
||||
if (lowerCaseSQL.contains(comments)) {
|
||||
throw new SQLInjectionException();
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
org.apache.fineract.template.*,
|
||||
org.apache.fineract.template.service.*,
|
||||
org.apache.fineract.useradministration.*,
|
||||
org.apache.fineract.batch">
|
||||
org.apache.fineract.batch,
|
||||
org.apache.fineract.adhocquery.*">
|
||||
<context:exclude-filter expression="org.springframework.stereotype.Controller"
|
||||
type="annotation" />
|
||||
|
||||
@@ -80,6 +81,7 @@
|
||||
<jpa:repositories base-package="org.apache.fineract.scheduledjobs.domain" />
|
||||
<jpa:repositories base-package="org.apache.fineract.template.domain" />
|
||||
<jpa:repositories base-package="org.apache.fineract.infrastructure.campaigns.sms.domain" />
|
||||
<jpa:repositories base-package="org.apache.fineract.adhocquery.domain" />
|
||||
|
||||
<import resource="infrastructure.xml" />
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
--
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you under the Apache License, Version 2.0 (the
|
||||
-- "License"); you may not use this file except in compliance
|
||||
-- with the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing,
|
||||
-- software distributed under the License is distributed on an
|
||||
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
-- KIND, either express or implied. See the License for the
|
||||
-- specific language governing permissions and limitations
|
||||
-- under the License.
|
||||
--
|
||||
|
||||
CREATE TABLE `m_adhoc` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(100) NULL DEFAULT NULL,
|
||||
`query` VARCHAR(2000) NULL DEFAULT NULL,
|
||||
`table_name` VARCHAR(100) NULL DEFAULT NULL,
|
||||
`table_fields` VARCHAR(1000) NULL DEFAULT NULL,
|
||||
`email` VARCHAR(500) NOT NULL,
|
||||
`IsActive` TINYINT(1) NOT NULL DEFAULT '0',
|
||||
`created_date` DATETIME NULL DEFAULT NULL,
|
||||
`createdby_id` BIGINT NOT NULL,
|
||||
`lastmodifiedby_id` BIGINT(20) NOT NULL,
|
||||
`lastmodified_date` DATETIME NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `createdby_id` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
|
||||
CONSTRAINT `lastmodifiedby_id` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
|
||||
)
|
||||
COLLATE='latin1_swedish_ci'
|
||||
ENGINE=InnoDB
|
||||
;
|
||||
|
||||
INSERT INTO `m_permission`
|
||||
(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES
|
||||
('authorisation','UPDATE_ADHOC','ADHOC','UPDATE',1),
|
||||
('authorisation','UPDATE_ADHOC_CHECKER','ADHOC','UPDATE',0),
|
||||
('authorisation','DELETE_ADHOC','ADHOC','DELETE',1),
|
||||
('authorisation','DELETE_ADHOC_CHECKER','ADHOC','DELETE',0),
|
||||
('authorisation','CREATE_ADHOC','ADHOC','CREATE',1),
|
||||
('authorisation','CREATE_ADHOC_CHECKER','ADHOC','CREATE',0);
|
||||
|
||||
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Generate AdhocClient Schedule', 'Generate AdhocClient Schedule', '0 0 12 1/1 * ? *', now());
|
||||
Reference in New Issue
Block a user