· 6 years ago · Sep 24, 2019, 03:42 PM
1package com.comarch.iaa.bd.servicescore.base.control
2
3import java.time.LocalDateTime
4
5import com.comarch.iaa.bd.servicescore.base.entity.{ParamWithOperator, Parameters}
6import com.comarch.iaa.bd.servicescore.sort.SortRequest
7import com.comarch.oss.bd.utils.{ContainsColumnValidation, OracleLimitWrapper}
8import scalikejdbc.{SQLSyntax, sqls}
9import skinny.orm.SkinnyCRUDMapper
10import skinny.orm.feature.FinderFeature
11
12trait FilterCondition[EntityType] extends ContainsColumnValidation[EntityType] with OracleLimitWrapper with FinderFeature[EntityType] {
13 relatedDAO: SkinnyCRUDMapper[EntityType] =>
14
15 def filterConditions(params: Parameters, excludeFilter: String*): Option[SQLSyntax] = {
16 val filters = simpleCondition(params.filterParams, columnLike, excludeFilter: _*)
17 val ranges = rangeCondition(params)
18 val unions = unionCondition(params, excludeFilter: _*)
19 val equals = simpleCondition(params.filterEqualParams, columnEqual, excludeFilter: _*)
20
21 Seq(equals, filters, ranges, unions).flatten.reduceOption(_ and _)
22 }
23
24 def filterConditionsWithRelation(params: Parameters): Option[SQLSyntax] = {
25 val relationFilters = simpleCondition(params.filterParams.filter(queryParamToValues => isRelationalFilter(queryParamToValues._1)), columnLikeUnsafely)
26 val simpleFilter = simpleCondition(params.filterParams.filterNot(queryParamToValues => isRelationalFilter(queryParamToValues._1)), columnLike)
27
28 val ranges = rangeCondition(params)
29 val unions = unionCondition(params)
30 val equals = simpleCondition(params.filterEqualParams.filter(queryParamToValues => isRelationalFilter(queryParamToValues._1)), columnEqual)
31 val hierarchicalFilterCondition = mergeHierarchicalFilter(params.hierarchicalEqualParams)
32
33 Seq(equals, hierarchicalFilterCondition, simpleFilter, relationFilters, ranges, unions).flatten.reduceOption(_ and _)
34 }
35
36 private def isRelationalFilter(key: String): Boolean = {
37 key.matches("[^.]*[.][^.]*")
38 }
39
40 private def mergeHierarchicalFilter(filterParams: Map[ParamWithOperator, List[String]]): Option[SQLSyntax] = {
41 val hierarchicalFilter = filterParams.toSeq.map { case (queryParam, values) =>
42 (queryParam.operator, sqls.roundBracket(values.map(value => columnEqual(queryParam.paramName, value)).reduce(_ or _)))
43 }.reduceOption[(String, SQLSyntax)] { case (left, right) =>
44 right._1 match {
45 case "or" => (right._1, sqls.roundBracket(sqls.joinWithOr(left._2, right._2)))
46 case "and" => (right._1, sqls.roundBracket(sqls.joinWithAnd(left._2, right._2)))
47 case _ => (right._1, sqls.roundBracket(sqls.joinWithAnd(left._2, right._2)))
48 }
49 }
50 hierarchicalFilter.map(_._2)
51 }
52
53 private def simpleCondition(filterParams: Map[String, List[String]], sqlOperatorFunction: (String, String) => SQLSyntax, excludeFilter: String*): Option[SQLSyntax] = {
54 filterParams
55 .filter { case (queryParam, values) => nonEmpty(values) && !shouldExcludeFilter(excludeFilter, queryParam) }
56 .map({ case (queryParam, values) =>
57 sqls.roundBracket(values.map(value => sqlOperatorFunction(queryParam, value)).reduce(_ or _))
58 }).reduceOption(_ and _)
59 }
60
61 private def rangeCondition(params: Parameters): Option[SQLSyntax] = {
62 params.rangeParams
63 .filter { case (_, values) => nonEmpty(values) }
64 .flatMap({ case (key, values) =>
65 values.flatMap(value => columnRange(key, value)).reduceOption(_ or _).map(sqls.roundBracket)
66 }).reduceOption(_ and _)
67 }
68
69 private def unionCondition(params: Parameters, excludeFilter: String*): Option[SQLSyntax] = {
70 params.unionParams
71 .filter { case (queryParam, values) => nonEmpty(values) && !shouldExcludeFilter(excludeFilter, queryParam) }
72 .map({ case (key, values) =>
73 sqls.roundBracket(values.map(value => columnLike(key, value)).reduce(_ or _))
74 }).reduceOption(_ or _)
75 }
76
77 private def columnLike(key: String, value: String): SQLSyntax = {
78 sqls
79 .like(
80 column = sqls.upper(relatedDAO.defaultAlias.column(key)),
81 value = s"%${value.toUpperCase}%")
82 }
83
84 private def columnLikeUnsafely(key: String, value: String): SQLSyntax = {
85 val col = if (key.contains(".")) {
86 sqls.upper(SQLSyntax.createUnsafely(key))
87 }
88 else {
89 sqls.upper(relatedDAO.defaultAlias.column(key))
90 }
91 sqls
92 .like(
93 column = col,
94 value = s"%${value.toUpperCase}%")
95 }
96
97 private def columnEqual(key: String, value: String): SQLSyntax = {
98 val col = if (key.contains(".")) {
99 SQLSyntax.createUnsafely(key)
100 } else {
101 relatedDAO.defaultAlias.column(key)
102 }
103
104 sqls.eq(col, value)
105 }
106
107 private def columnRange(key: String, value: String): Option[SQLSyntax] = {
108 key match {
109 case r"start[.]$dbColumn(.*)" => Some(sqls.gt(column = relatedDAO.defaultAlias.column(dbColumn), value = LocalDateTime.parse(value)))
110 case r"end[.]$dbColumn(.*)" => Some(sqls.lt(column = relatedDAO.defaultAlias.column(dbColumn), value = LocalDateTime.parse(value)))
111 case _ => None
112 }
113 }
114
115 private def shouldExcludeFilter(excludeFilter: Seq[String], param: String): Boolean = {
116 excludeFilter.exists(filter => param.contains(filter))
117 }
118
119 def mappedOrderWithLimit(offset: Int, limit: Int, sortByColumns: Iterable[String]): Seq[SQLSyntax] = {
120 orderWithLimit(offset, limit, mappedOrder(sortByColumns))
121 }
122
123 def mappedOrder(sortByColumns: Iterable[String]): Seq[SQLSyntax] = {
124 SortRequest(sortByColumns.filter(containsColumn))
125 .map(request => SortRequest.ordering(relatedDAO.defaultAlias.column(request.columnName), request)).toSeq
126 }
127
128 private def nonEmpty(filterParam: List[String]): Boolean = {
129 filterParam.exists(_.nonEmpty)
130 }
131
132 implicit class Regex(sc: StringContext) {
133 def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*)
134 }
135
136}
137
138################################################
139
140package com.comarch.iaa.bd.servicescore.base.boundary
141
142import akka.http.scaladsl.model._
143import akka.http.scaladsl.server.Route
144import com.comarch.iaa.bd.cudevent.{CUDEvent, OperationType}
145import com.comarch.iaa.bd.servicescore.base.control.{BaseService, FilterCondition, KafkaBaseService, Relations}
146import com.comarch.iaa.bd.servicescore.utils.ServiceUtil
147import com.comarch.iaa.metadata.model.BaseEntity
148import com.comarch.iaa.transport.logmessage.SeverityDTO
149import com.comarch.oss.bd.jdbc.ConnectionPool
150import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
151import io.circe.{Decoder, Encoder}
152import scalikejdbc.{DB, using}
153import skinny.orm.SkinnyCRUDMapper
154
155import scala.util.{Failure, Success, Try}
156
157abstract class BaseRestController[A, B <: BaseEntity](basePath: String,
158 baseService: BaseService[A, B, SkinnyCRUDMapper[A] with FilterCondition[A] with Relations[A]] with KafkaBaseService,
159 connectionPool: ConnectionPool)
160 (implicit encode: Encoder[A],
161 decoder: Decoder[B])
162 extends BaseReadController(basePath, connectionPool, baseService)(encode) {
163
164 private val invalidCreateObjectId: Long = -1L
165 private val count: String = "count"
166
167 override val routes: Route = extractRequest { request =>
168 implicit val token: String = ServiceUtil.accessToken(request).getOrElse("")
169 pathPrefix(basePath) {
170 pathEndOrSingleSlash {
171 getAllObjects ~ post {
172 entity(as[B]) { entityObj =>
173 using(connectionPool.getConnection) { _ =>
174 if (baseService.exists(entityObj)) {
175 complete(StatusCodes.Conflict)
176 } else {
177 Try(DB localTx { implicit session =>
178 baseService.create(entityObj).get
179 }) match {
180 case Success(success) =>
181 baseService.kafkaServicesProducer.sendPartitionedMessage(baseService.logMessageTopicName,
182 baseService.logMessagePartitions, baseService.generateLogMessage(success, baseService.successMessage
183 .format(OperationType.CREATE, entityObj.name)))
184 baseService.kafkaServicesProducer.sendMessage(baseService.cudEventTopicName, CUDEvent(success, OperationType.CREATE))
185 complete(StatusCodes.Created, success)
186 case Failure(exception) =>
187 baseService.kafkaServicesProducer.sendPartitionedMessage(baseService.logMessageTopicName,
188 baseService.logMessagePartitions, baseService.generateLogMessage(invalidCreateObjectId, baseService.failureMessage
189 .format(OperationType.CREATE, entityObj.name), SeverityDTO.ERROR))
190 complete(StatusCodes.InternalServerError, exception.getMessage)
191 }
192 }
193 }
194 }
195 }
196 } ~ path(LongNumber) { id =>
197 getById(id) ~ delete {
198 using(connectionPool.getConnection) { _ =>
199 Try(DB localTx { implicit session =>
200 baseService.delete(id).get
201 }) match {
202 case Success(success) =>
203 baseService.kafkaServicesProducer.sendMessage(baseService.cudEventTopicName, CUDEvent(id, OperationType.DELETE))
204 complete(StatusCodes.NoContent, success)
205 case Failure(exception) =>
206 complete(StatusCodes.InternalServerError, exception.getMessage)
207 }
208 }
209 } ~ put {
210 entity(as[B]) { entityObj =>
211 using(connectionPool.getConnection) { _ =>
212 Try(DB localTx { implicit session =>
213 baseService.update(entityObj, id).get
214 }) match {
215 case Success(success) =>
216 baseService.kafkaServicesProducer.sendPartitionedMessage(baseService.logMessageTopicName,
217 baseService.logMessagePartitions, baseService.generateLogMessage(id, baseService.successMessage
218 .format(OperationType.UPDATE, entityObj.name)))
219 baseService.kafkaServicesProducer.sendMessage(baseService.cudEventTopicName, CUDEvent(id, OperationType.UPDATE))
220 complete(StatusCodes.NoContent, success)
221 case Failure(exception) =>
222 baseService.kafkaServicesProducer.sendPartitionedMessage(baseService.logMessageTopicName,
223 baseService.logMessagePartitions, baseService.generateLogMessage(id, baseService.failureMessage
224 .format(OperationType.UPDATE, entityObj.name), SeverityDTO.ERROR))
225 complete(StatusCodes.InternalServerError, exception.getMessage)
226 }
227 }
228 }
229 }
230 } ~ path(count) {
231 countAll
232 }
233 }
234 }
235}
236
237#####################################################################
238
239package com.comarch.oss.bd.conf
240
241import java.io.{File, Reader}
242import java.util.Properties
243
244import org.apache.log4j.Logger
245import org.apache.spark.SparkFiles
246
247import scala.collection.JavaConverters._
248import scala.collection.mutable
249import scala.io.Source
250import scala.util.{Failure, Success, Try}
251
252object AppPropertiesLoader {
253
254 private val RootDirectory = "."
255
256 private[conf] val log = Logger.getLogger(getClass.getName)
257 private[conf] val props = mutable.HashMap[String, String]()
258
259 locally {
260 AppPropertiesLoader.loadProperties()
261 AppPropertiesLoader.loadExternalProperties()
262 }
263
264 implicit class PropertiesEnhancement(propName: String) {
265 def extract: String =
266 Try {
267 props(propName)
268 } getOrElse (throw new PropertyNotFoundException(propName))
269
270 }
271
272 /**
273 * Load properties from .properties files using java.util.Properties API
274 *
275 * @param propFileName property file location (classpath or filesystem)
276 */
277
278 def loadProperties(propFileName: String = "config/default_core.properties"): Unit = {
279 loadPropertiesFromClassPath(propFileName).
280 orElse(loadPropertiesFromFileSystem(propFileName)) match {
281 case Failure(e) => log.error(s"Failed to load properties from $propFileName, $e");
282 case Success(extracted) => log.info(s"Successfully extracted $extracted properties from $propFileName, " +
283 s"current properties size: ${props.size}")
284 }
285 }
286
287 private def loadExternalProperties(): Unit = {
288 Try(new File(RootDirectory).listFiles().filter(_.getName.endsWith(".properties")).foreach { file =>
289 loadProperties(file.getAbsolutePath)
290 }) match {
291 case Failure(e) => log.error(s"Failed to load external properties. $e")
292 case Success(s) => log.info(s"Successfully extracted external properties, current properties size: ${props.size}")
293 }
294 }
295
296 private def loadPropertiesFromClassPath(propFileName: String): Try[Int] = {
297 try {
298 val inStream = getClass.getClassLoader.getResourceAsStream(propFileName)
299 val reader = Source.fromInputStream(inStream).bufferedReader()
300 extractProperties(reader)
301 } catch {
302 case e: Exception =>
303 log.debug(s"Unable to read properties from classpath. Proceeding to read from system. $e"); Failure(e)
304 }
305 }
306
307 private def loadPropertiesFromFileSystem(propFileName: String): Try[Int] = {
308 val reader = Source.fromFile(propFileName).bufferedReader()
309 extractProperties(reader)
310 }
311
312 /**
313 *
314 * @param source java.io.Reader from properties file
315 * @return Number of extracted rows from given source
316 */
317 private def extractProperties(source: Reader): Try[Int] = {
318 Try {
319 val newProperties = new Properties()
320 newProperties.load(source)
321 props ++= newProperties.asScala
322 newProperties.size
323 }
324 }
325
326 /**
327 * Method only for test purposes
328 * Removes all set properties and set default ones.
329 */
330 def restoreDefaultProperties(): Unit = {
331 props.clear()
332 loadProperties()
333 }
334}
335
336class PropertyNotFoundException(propName: String) extends Exception(propName)