http 통신 에러 D NetworkSecurityConfig: Using Network Security Config from resource network_security_config debugBuild: false
NetworkSecurityConfig: No Network Security Config specified, using platform default

 

1. 에러 내용

Flutter apk 빌드 후 http 통신 불가 로그 확인

logcat 확인 시 아래와 같은 로그 확인 함

"D NetworkSecurityConfig: Using Network Security Config from resource network_security_config debugBuild: false"

 

2. 해결

검색 결과 Android 설정 문제로 확인 되어 아래 처럼 내용 수정 후 빌드 시 정상 확인

 

1. main -> AndroidManifest.xml 수정

<manifest 
	(...)
    <uses-permission android:name="android.permission.INTERNET" /> <= 추가
    <application
        android:usesCleartextTraffic="true" <= 추가
        android:networkSecurityConfig="@xml/network_security_config" <= 추가
        (...)
        >
        <activity
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

2. main/res/xml 디렉토리 파일 생성 -> network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

'개발 > Flutter' 카테고리의 다른 글

플러터에서 웹 빌드 (Missing index.html. 에러처리)  (0) 2020.10.14

Overview

so 별 별도 로직이 존재하므로 반복과 중복 if 문을 써서 코드가 복잡해 지는 구조가 있다.

java reflect 를 이용하여 동적으로 class 및 method 호출 과정에 대해 기록한다.

전체 코드는 다음 링크에서 확인 (https://github.com/pari0130/reflection)


Service 버전

service 별 버전이 상이하여 별도 클래스로 분리 할 경우 호출하는 로직에서 if 문 중첩이 발생 될 수 있으므로 동적 호출 방안에 대해 기록 한다.

  1. annotation 구성
    1. class 상위에 선언 될 annotation 을 구성 한다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      /**
       * 설명
       *
       * Retention RunTime : compile time 과 binary 에도 포함되고, reflection 을 통해 접근 가능
       *
       * Target CLASS : class, interface, object, annotation class 에 사용 가능하도록 제한
       *
       * values : 서비스 버전 정보 입력을 위해 사용
       *
       * */
      @Retention(AnnotationRetention.RUNTIME)
      @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS)
      annotation class ServiceVersion(
          val values: Array<String> = []
      )
  2. interface 및 class 구성
    1. getBeanProvider 를 이용해 하위 class 를 호출 하려면 interface 를 동일 지정, Annotation value 로 버전을 기록하여 구분
      interface TestService {
          fun getTestItems(item: String): Map<String, Any>
          fun insertCharge(param: Map<String, Any>)
      }
       
      // v1 로 사용 될 class
      @Service("TestServiceV1Impl")
      @ServiceVersion(values = ["v1"])
      class TestServiceV1Impl(val testDiService: TestDiService) : TestService { }
       
      // v1.1 로 사용 될 class
      @Service("TestServiceV11Impl")
      @ServiceVersion(values = ["v1.1"])
      class TestServiceV11Impl(val testDiService: TestDiService) : TestService { }
       
      // v1.2 로 사용 될 class
      @Service("TestServiceV12Impl")
      @ServiceVersion(values = ["v1.2"])
      class TestServiceV12Impl(val testDiService: TestDiService) : TestService { }
  3. interface 동적 호출을 위한 DI class 구성 
    1. 해당 class 의 목적은 전달받은 version 정보를 이용하여 interface 검색 후 override method 를 return
      override fun getTestService(version: String): TestService {
              return applicationContext.getBeanProvider(TestService::class.java).stream() // TestService interface 를 상속한 Bean 조회
                  .filter { b -> !ObjectUtils.isEmpty(b.javaClass.getDeclaredAnnotation(ServiceVersion::class.java)) }
                  .filter { b ->
                      Arrays.stream(b.javaClass.getDeclaredAnnotation(ServiceVersion::class.java).values) // version annotation 을 사용하는 method 중 일치하는 값 조회
                          .anyMatch { v -> v.equals(version) }
                  }
                  .findAny()
                  .orElse(applicationContext.getBean("TestServiceV1Impl", TestService::class.java))
          }
  4. Test
    1. DI Service 에 version 정보를 전달하여 하위 interface 의 결과값을 조회 함
      @Test
          fun getTestService() {
              var version = "v1"
              val itemV1 = testDiService.getTestService(version).getTestItems("v1 item test")
       
              version = "v1.1"
              val itemV11 = testDiService.getTestService(version).getTestItems("v1.1 item test")
       
              version = "v1.2"
              val itemV12 = testDiService.getTestService(version).getTestItems("v1.2 item test")
       
              logger.info("[TEST] item -> { $itemV1, $itemV11, $itemV12 }") // [TEST] item -> { {item=v1 item test}, {item=v1.1 item test}, {item=v1.2 item test} }
       
              assertAll(
                  { Assertions.assertEquals(itemV1["item"], "v1 item test") },
                  { Assertions.assertEquals(itemV11["item"], "v1.1 item test") },
                  { Assertions.assertEquals(itemV12["item"], "v1.2 item test") }
              )
          }

Service 에 대한 특정 Function 실행

so 별 특정 함수를 호출하는 로직에 대해 중첩된 if 문이 발생 될 수 있으므로 보완 방안으로 Reflection 을 이용한 동적 method 호출에 대해 기록한다.

  1. annotation 구성
    1. method 상위에 선언 될 annotation 을 구성 한다
      /**
       * 설명
       *
       * Retention RunTime : compile time 과 binary 에도 포함되고, reflection 을 통해 접근 가능합니다.
       *
       * Target FUNCTION : 생성자를 제외한 함수들에 사용 가능하도록 제한
       *
       * soIds : so 별 특정 함수 실행 시 so 입력을 위해 사용
       * actionItem : action item 이 다를 수 있으므로 item 입력을 위해 사용
       *
       * */
      @Retention(AnnotationRetention.RUNTIME)
      @Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) // method 에 선언 될 경우 FUNCTION 으로 지정
      annotation class SpecificActionItem(
          val soIds: Array<String> = [],
          val actionItem: Array<String> = []
      )
  2. interface 및 class 구성
    1. 동일 interface 및 param type 을 지정하여 호출에 대한 공통화, Annotation 에 so 및 action item 으로 구분 분리
      interface TestSpecificFunService {
          fun insertChargeByCarplat(param: Map<String, Any>)
          fun insertChargeByPeopleCar(param: Map<String, Any>)
          fun insertChargeByWay(param: Map<String, Any>)
      }
       
      @SpecificActionItem(soIds = ["carplat"], actionItem = ["insert_charge"])
      override fun insertChargeByCarplat(param: Map<String, Any>) {
         logger.info("insertCharge -> { so : carplat, item : insert_charge, param : $param }")
      }
       
      @SpecificActionItem(soIds = ["peopleCar"], actionItem = ["insert_charge"])
      override fun insertChargeByPeopleCar(param: Map<String, Any>) {
         logger.info("insertCharge -> { so : peopleCar, item : insert_charge, param : $param }")
      }
       
      @SpecificActionItem(soIds = ["way"], actionItem = ["insert_charge"])
      override fun insertChargeByWay(param: Map<String, Any>) {
         logger.info("insertCharge -> { so : way, item : insert_charge, param : $param }")
      }
  3. interface 동적 호출을 위한 DI class 구성
    1. TestSpecificFunService interfaces 및 하위 method 를 조회하여 soid, actionItem 에 해당하는 함수를 실행
      override fun invokeServiceFun(so: String, actionItem: String, param: Map<*, *>): Any? {
              if (so.isNullOrEmpty() || actionItem.isNullOrEmpty()) {
                  logger.info("[DI LOG] invoke func so or actionItem is Empty -> { so : $so, actionItem : $actionItem }")
                  return null
              }
       
              var invokeResult: Any? = null
              applicationContext.getBeanProvider(TestSpecificFunService::class.java).stream()
                  .forEach extraClass@{ clazz ->
                      clazz.javaClass.declaredMethods.forEach extraMethod@{ method ->
                          val extraMethod = method.getAnnotation(SpecificActionItem::class.java) // Annotation method 조회
                          if (!ObjectUtils.isEmpty(extraMethod)) {
                              if (extraMethod.soIds.contains(so) && extraMethod.actionItem.contains(actionItem)) { // soid, actionItem
                                  invokeResult = clazz.javaClass.getMethod(method.name, Map::class.java).invoke(clazz, param)
                                  return@extraClass // 일치하는 값이 있을 경우 조회 중인 interface loop 를 종료
                              }
                          } else {
                              return@extraClass
                          }
                      }
                  }
       
              return invokeResult
          }
  4. Test
    1. 함수 실행 결과 로그 확인
      @Test
          fun invokeServiceFun() {
              var so = "carplat"
              val actionItem = "insert_charge"
              val param = mapOf("param1" to 1, "param2" to 2)
       
              testDiService.invokeServiceFun(so, actionItem, param) // insertCharge -> { so : carplat, item : insert_charge, param : {param1=1, param2=2} }
       
              so = "peopleCar"
              testDiService.invokeServiceFun(so, actionItem, param) // insertCharge -> { so : peopleCar, item : insert_charge, param : {param1=1, param2=2} }
       
              so = "way"
              testDiService.invokeServiceFun(so, actionItem, param) // insertCharge -> { so : way, item : insert_charge, param : {param1=1, param2=2} }
          }

성능 이슈에 대한 검색 내용

초기 호출을 제외하고는 Reflection API를 사용하는 것이 성능에 대한 별 차이가 없는 것에 대한 테스트 블로그 내용 (https://lob-dev.tistory.com/entry/Java%EC%9D%98-Reflection-API)

'개발 > Kotlin' 카테고리의 다른 글

Kotlin 고차함수에 대한 Callback param 전달  (0) 2022.05.03
@Test
    fun callbackTest(){
        listLoop(callBack = {
            logger.info("callBack index -> ${it["index"]}")
            Thread.sleep(500)
        })
    }

    fun listLoop(callBack:(Map<String, Any>) -> Unit){
        var index = 0
        while (index < 10) {
            logger.info("loop -> $index")
            callBack.invoke(mapOf("index" to index))
            index++
        }
    }

 

'개발 > Kotlin' 카테고리의 다른 글

Kotlin(java) Reflect 를 활용한 중첩 if 문 제거  (0) 2022.06.13
gradle build 에러 "CreateProcess error=206, 파일 이름이나 확장명이 너무 깁니다"

1. 에러 내용

bootRun 을 통해 서비스 실행 후 gradle build 중 아래와 같은 에러가 발생함

 

2. 해결

검색 시 경로가 길어서 문제가 될수 있다고 하여 해당 서비스 경로도 변경 해봤으나 증상이 동일

다른 케이스를 확인 중 build version 에 대한 문제가 발생 할 수 있다 하여 gradle version 을 update

  • 현재 버전 : gradle 6.7.1
  • 업데이트 버전 : gradle 6.8
  • 업데이트 방법 : ./gradlew wrapper --gradle-version 6.8
  • 참고 : 빌드는 bootRun service 디렉토리에서 실행해야합니다.(당연..)
QueryDsl 사용 시 date 시작/종료 구간 검색

 

JPAQuery 부분 
query.where(qOrder.createdDate.between(dto.getSearchStartDate(), dto.getSearchEndDate()));


dto 부분
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime searchStartDate;

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime searchEndDate;

 

LocalDateTime 을 사용할때에는 DateTimeFormat 을 입력해줘야 param 에 2021-02-01 09:52:53 같은 date time을 변환하여 받을 수 있다

개발 중 insert, update 쿼리 중 Exception 이 발생 하였지만 쿼리 요청이 rollback 되지않는 현상 정리

 

1. 액션

 - insert 혹은 update 문이 false 로 떨어질 경우 throw new Exception 을 처리 함

if(!dao.updateBl("updateQuery", dto)){
	throw new BaseException(SystemException.DB_NOT_UPDATE_ACTION);
}

 

2. 기대동작

 - RuntimeException 을 상속 받은 Exception 처리를 했으니 @Transactional 에 의해 rollback 처리를 기대함

 

3. 증상

 - DB update 가 rollback 되지 않고 정상적으로 업데이트가 됨

 

4. 원인 정리

 - update 문을 감싸는 try-catch 문을 신경쓰지 않았으며, catch 문에 처리되는 Exception 이 throw new Exception(e.getMessage()) 만 타고 있었음

 - Exception() 의 경우 checked exception 이므로 rollback 처리가 안됨

 

5. 해결방안

- 첫번째 방법 : try-catch 문의 Exception을 다음과 같이 변경 (RuntimeException 상속받은 클래스)

} catch (Exception e) {
	throw new BaseException(e.getMessage());
}

 - 두번째 방법 : throw new Exception() 으로 처리하고 @Transactional 의 옵션값을 보완함

@Transactional(rollbackFor = {RuntimeException.class, Exception.class})

 

현재 첫번째 방법으로 처리 했습니다.

 

 

참고 : cheese10yun.github.io/checked-exception/

플러터에서 웹 빌드 (Missing index.html. 에러처리)

 

flutter에서 flutter build web 수행 시 Missing index.html. 에러가 발생하는 경우는 flutter create가 수행되지 않은 문제

 

아래 순서대로 수행시 문제없습니다.

 

  1. flutter channel beta
  2. flutter upgrade (필요시)
  3. flutter config --enable-web
  4. flutter create . (<- . 점 현재 디렉토리 까지 선택)
  5. flutter devices
  6. flutter run -d chrome

 

구형 브라우저에서 es6 사용하기 (es6 to es5 webpack 빌드)


일반적인 웹 프로젝트에서는 es5를 쓰며 es6를 사용하기 위해서는 browser.js 혹은 babel.js를 load 해야 합니다.

그마저도 모든 es6 코드를 커버하지는 못하여 아래와 같이 es6 코드를 es5로 컴파일 해주는 webpack bundling 과정에 대해 설명 하겠습니다.


1. Node js 설치

  • 1-1. 윈도우 환경 기준으로 아래 사이트 접속 후 nodejs 를 다운 받습니다.



  • 1-2. 다운로드 후 설치 파일을 실행하여 NEXT 버튼 클릭으로 기본 설정 설치를 완료 합니다.


  • 1-3. 설치 완료 후 noedjs를 전역에서 실행 할 수 있도록 환경 설정을 진행 합니다.
    • 시스템속성 > 고급 > 환경변수 를 클릭하여 환경변수 설정을 진행 합니다.
    • 사용자 변수에 npm 위치 추가 ( C:\Users\사용자이름\AppData\Roaming\npm )

    • 시스템변수 > path > 새로만들기를 클릭하여 시스템 변수 nodejs를 추가합니다.

    • 윈도우 + R > node -v 로 nodejs 버전 확인이 가능하면 nodejs 설치가 끝났습니다.


2. Babel, webpack 빌드를 위한 설정

  • 2-1. 일반적은 프론트엔드 프로젝트의 경우 root 디렉토리, 스프링의 경우 src/main 위치에서 아래 순서대로 명령어를 수행 한다.

    • npm init -y : 프로젝트에서 npm 프로그램 시작을 의미하는 명령어 (실행 후 package.json 파일 자동 생성 완료)
    • npm install --save-dev webpack webpack-cli : 스크립트 간 의존성 관계를 하나의 파일로 bundling 해주는 webpack 설치
    • npm install --save-dev babel-loader : es6++ 이상의 문법에 대해 브러우저에 관계없이 동작되도록 컴파일 해주는 컴파일러
    • npm install --save-dev @babel/cli @babel/core @babel/preset-env : 위와 동일
    • npm install @babel/polyfill : babel 컴파일러만 설치 시 es6++ 문법에 대해 변환이 안되는 몇몇 가지 문제를 해결한 패키지


  • 2-2. 2-1 과 동일한 위치에 .babelrc 파일 생성
    • babel 환경 설정 

      {
        "presets": [
          [
            "@babel/preset-env",
            {
              "targets": {
                "browsers": [
                  "last 2 versions",
                  "not ie <= 9"
                ]
              }
            }
          ]
        ]
      }
  • 2-3. 2-1과 동일한 위치에 webpack.config.js 파일 생성
    • webpack 환경 설정 파일

      const path = require('path');
       
      module.exports = {
          entry: {
              index : ['@babel/polyfill''./webapp/js/bundle/index.js'// 빌드를 진행 하려는 파일 위치
              /*
              test1 : ['@babel/polyfill', './webapp/js/bundle/test1.js'], // 각각 파일로 빌드를 할 수 있짐만 비효율 적이므로 index.js 파일에 export 하여 한곳으로 빌드 추천
              test2: ['@babel/polyfill', './webapp/js/bundle/test2.js']
              */
          },
          output: { // 빌드 된 파일이 저장될 위치
              path: path.resolve(__dirname, './webapp/js/bundle'),
              filename: 'webpack.bundle.js'// filename : '[name].bundle.js',
              // library: "bundle", // 이름을 지정 할 경우 bundle.es6Code() 형태로 호출
              libraryTarget: "umd"// output.libraryTarget을 umd로 설정하면 모듈은 <script src=""> 로드 뿐만 아니라 모든 방식의 로더에서 사용할 수 있음
          },
          module: {
              rules: [
                  {
                      test: /\.js$/,
                      exclude: /node_modules/,
                      use: {
                          loader: 'babel-loader',
                          options: {
                              presets: ['@babel/preset-env']
                          }
                      }
                  }
              ]
          },
          devtool: 'source-map',
          mode: 'development'
      };
  • 2-4. package.json의 스크립트 수정
    • package.json 상단 부분에서 "script" 명령어를 아래와 같이 변경 한다.

    • 아래 설정의 의미는 npm run dev 명령어 수행 시 webpack 빌드를 진행할 것이고, -w의 경우 빌드 대상 파일을 watch 하며 변경이 일어날 경우 자동 재 빌드를 수행 함

      "scripts": {
        "dev""webpack -w --mode development --devtool inline-source-map",
        "build""webpack --mode production"
      },

3. TEST ES6 코드 작성 및 빌드 테스트

  • 3-1. TEST ES6 코드

    • src/main/webapp/js/bundle/classTest.js

      export class ClassTest {
          constructor(data) {
              this.data = data || "data";
          }
       
          call(){
              console.log("class test call : " this.data)
          }
      }
    • src/main/webapp/js/bundle/index.js

      export { ClassTest } from './classTest';
  • 3-2. npm 빌드 수행
    • npm run dev : 테스트 시
    • npm run build : 배포 시 (스크립트를 압축-minify  하여 코드라인 축소 및 난독화)
    • 빌드 시 2-3 output 위치에 webpack.bundle.js 파일 생성 됨


  • 3-3. jsp 혹은 html 파일에서 번들링된 파일을 로드 후 동작 테스트

    • src/main/webapp/WEB-INF/views/test.jsp

      <script src="${pageContext.request.contextPath}/js/bundle/webpack.bundle.js"></script>
      <script>
          let testCode = new ClassTest('code test');
          testCode.call();
      </script>

4. 빌드에 필요한 파일 목록

  • .babelrc
  • package.json
  • package-lock.json
  • webpack.config.js


JSONException 에러 관련


JSONArray, JSONException, JSONObject 등 json 처리 후 서버에 업로드 후 부팅 시 아래와 같은 에러가 발생하였음



 Caused by: java.lang.IllegalStateException: Failed to introspect Class [ 파일이름 ] from ClassLoader [ParallelWebappClassLoader

  context: ROOT

  delegate: false

----------> Parent Classloader:

java.net.URLClassLoader@37f8bb67

]

        at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:481) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]

        at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]

        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:267) ~[spring-beans-5.2.7.RELEASE.jar:5.2.7.RELEASE]

        ... 48 common frames omitted

Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/configurationprocessor/json/JSONException

        at java.lang.Class.getDeclaredMethods0(Native Method) ~[na:1.8.0_201]

        at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) ~[na:1.8.0_201]

        at java.lang.Class.getDeclaredMethods(Class.java:1975) ~[na:1.8.0_201]

        at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]

        ... 50 common frames omitted

Caused by: java.lang.ClassNotFoundException: org.springframework.boot.configurationprocessor.json.JSONException

        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1365) ~[catalina.jar:9.0.37]

        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188) ~[catalina.jar:9.0.37]

        ... 54 common frames omitted



여기저기 검색해 보니 json 관련해서 잘못 dependency 되어 있는 문제였음


기존 : org.springframework.boot.configurationprocessor.json.JSONArray

변경 : org.json.JSONArray


maven


<dependency>

   <groupId>org.json</groupId>

   <artifactId>json</artifactId>

   <version>20200518</version>

</dependency>

 


gradle


implementation 'org.json:json:20200518'

 


디펜던시 참고 : https://javalibs.com/artifact/org.json/json





Xss custom filter (lucy 적용 안되어 임시방편 코드)


유지보수 프로젝트 수행 중 취약점 처리 요청이 들어와서 확인해보니 xss 필터 사용이 안되고 있었다.
lucy 필터가 쉽게 적용이 되어 lucy 필터를 적용 하려고 했으나 레거시 소스 + 특이사항 등.. 문제점이 있어서 정부에서 제공한 취약점 가이드에서 예외 처리 해야할 스크립트 동작 코드만 replace 처리 되도록 필터를 만들었다.

  • 아래 코드는 취약점 가이드에 표시되는 모든 스크립트 코드임 ( <, >, style 등.. 몇가지는 제외시킴 )
  • 해외에서 사용하는 Jsoup.clean() 을 사용하려고 코드를 넣었으나.. 혹시모를 레거시 코드 사이드이펙트를 우려하여 스크립트 코드만 처리함
public class XssEscape {

private static List<String> targetList = Arrays.asList(
"javascript", "script", "iframe", "document", "vbscript", "applet",
"embed", "object", "frame", "grameset", "layer", "bgsound",
"alert", "onblur", "onchange", "onclick", "ondblclick", "enerror",
"onfocus", "onload", "onmouse", "expression", "meta", "xml",
"onreset", "onmove", "onstop", "eval", "cookie", "onstart",
"onresize", "onmousewheel", "ondataavailable",
"onafterprint", "onafterupdate", "onmousedown",
"onbeforeactivate", "onbeforecopy", "ondatasetchanged",
"onbeforedeactivate", "onbeforeeditfocus", "onbeforepaste",
"onbeforeprint", "onbeforeunload", "onbeforeupdate",
"onpropertychange", "ondatasetcomplete", "oncellchange",
"onlayoutcomplete", "onmousemove", "oncontextmenu",
"oncontrolselect", "onreadystatechange", "onselectionchange",
"onrowsinserted", "onactivae", "oncopy", "oncut", "onbeforecut", "ondeactivate",
"ondrag", "ondragend", "ondragenter", "ondragleave", "ondragover", "ondragstart",
"ondrop", "onerror", "onerrorupdate", "onfilterchange", "onfinish", "onresizestart",
"onunload", "onselectstart", "onfocusin", "onfocusout", "onhelp", "onkeydown",
"onkeypress", "onkeyup", "onrowsdelete", "onlosecapture", "onbounce", "onmouseenter",
"onmouseleave", "onbefore", "onmouseout", "onmouseover", "onmouseup", "onresizeend",
"onabort", "onmoveend", "onmovestart", "onrowenter", "onsubmit"
);

public static String replaceValue(String str) {

String safeDoc = str;

if (StringUtils.isNotEmpty(str)) {
safeDoc = Jsoup.clean(str, Whitelist.relaxed().addTags("address"));
}

return safeDoc;
}

public static String replaceParam(String str) {

String returnStr = str;

if (StringUtils.isNotEmpty(str)) {

String str_low = str.toLowerCase();

for (String target : targetList) {
if (str_low.contains(target)) {
str = str.replaceAll(target, "");
returnStr = str;
}
}
}

return returnStr;
}

}


+ Recent posts