|
@@ -1,29 +1,33 @@
|
|
|
package com.zhiqiyun.open.core.video.impl;
|
|
package com.zhiqiyun.open.core.video.impl;
|
|
|
|
|
|
|
|
-import com.alibaba.fastjson.JSON;
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.google.common.collect.Maps;
|
|
|
import com.zhiqiyun.open.core.video.VideoDockingService;
|
|
import com.zhiqiyun.open.core.video.VideoDockingService;
|
|
|
import com.zhiqiyun.open.exception.ServiceException;
|
|
import com.zhiqiyun.open.exception.ServiceException;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import okhttp3.*;
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+import org.apache.http.client.config.CookieSpecs;
|
|
|
|
|
+import org.apache.http.client.config.RequestConfig;
|
|
|
|
|
+import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
|
|
+import org.apache.http.client.methods.HttpGet;
|
|
|
|
|
+import org.apache.http.client.methods.HttpPost;
|
|
|
|
|
+import org.apache.http.entity.StringEntity;
|
|
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
|
|
+import org.assertj.core.util.Lists;
|
|
|
import org.springframework.data.redis.core.BoundValueOperations;
|
|
import org.springframework.data.redis.core.BoundValueOperations;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
|
|
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
-import javax.net.ssl.*;
|
|
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
-import java.rmi.ServerException;
|
|
|
|
|
-import java.security.SecureRandom;
|
|
|
|
|
-import java.security.cert.CertificateException;
|
|
|
|
|
-import java.security.cert.X509Certificate;
|
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
-import java.util.Objects;
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
|
|
+import static com.zhiqiyun.open.camera.config.CameraConfig.*;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* @author xin yang
|
|
* @author xin yang
|
|
|
* @date 2022/8/4
|
|
* @date 2022/8/4
|
|
@@ -32,37 +36,36 @@ import java.util.concurrent.TimeUnit;
|
|
|
@Service
|
|
@Service
|
|
|
public class VideoDockingServiceImpl implements VideoDockingService {
|
|
public class VideoDockingServiceImpl implements VideoDockingService {
|
|
|
|
|
|
|
|
- private final static String USER_NAME = "duijie";
|
|
|
|
|
- private final static String PASSWORD = "Huawei@12#$";
|
|
|
|
|
- private final static String URI = "https://172.169.12.21:18531";
|
|
|
|
|
- private final static String RESULT_CODE = "resultCode";
|
|
|
|
|
|
|
+ // public final static String URI = "http://222.84.250.39:9800";
|
|
|
|
|
+ public final static String URI = "https://172.169.12.21:18531";
|
|
|
|
|
|
|
|
- private final static String VIDEO_JSESSIONID_TOKEN = "VIDEO_JSESSIONID_TOKEN";
|
|
|
|
|
|
|
+ public final static String VIDEO_JSESSIONID_TOKEN = "VIDEO_JSESSIONID_TOKEN";
|
|
|
|
|
|
|
|
@Resource
|
|
@Resource
|
|
|
private RedisTemplate<String, String> redisTemplate;
|
|
private RedisTemplate<String, String> redisTemplate;
|
|
|
|
|
|
|
|
@Resource
|
|
@Resource
|
|
|
- private OkHttpClient okHttpClient;
|
|
|
|
|
|
|
+ private ThreadPoolTaskExecutor taskExecutor;
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public String getValidToken() throws IOException {
|
|
|
|
|
|
|
+ public String getValidToken() throws Exception {
|
|
|
BoundValueOperations<String, String> boundValueOperations = this.redisTemplate.boundValueOps(VIDEO_JSESSIONID_TOKEN);
|
|
BoundValueOperations<String, String> boundValueOperations = this.redisTemplate.boundValueOps(VIDEO_JSESSIONID_TOKEN);
|
|
|
String token = boundValueOperations.get();
|
|
String token = boundValueOperations.get();
|
|
|
- if (StringUtils.isBlank(token)) {
|
|
|
|
|
- Map<String, String> paramValues = new HashMap<>(2);
|
|
|
|
|
|
|
+ if (StringUtils.isBlank(token) || !token.contains("JSESSIONID")) {
|
|
|
|
|
+ Map<String, Object> paramValues = new HashMap<>(2);
|
|
|
paramValues.put("userName", USER_NAME);
|
|
paramValues.put("userName", USER_NAME);
|
|
|
paramValues.put("password", PASSWORD);
|
|
paramValues.put("password", PASSWORD);
|
|
|
|
|
|
|
|
- Response response = okHttpPost("/loginInfo/login/v1.0", paramValues);
|
|
|
|
|
- log.info(JSONObject.toJSONString(response));
|
|
|
|
|
-// analysisRequestSuccess(response);
|
|
|
|
|
- List<String> cookies = response.headers().values("Set-Cookie");
|
|
|
|
|
- cookies.forEach(s -> {
|
|
|
|
|
- log.info("name:{}", s);
|
|
|
|
|
- });
|
|
|
|
|
- token = response.header("Set-Cookie");
|
|
|
|
|
- log.info("用户登录TOKEN:{}", token);
|
|
|
|
|
|
|
+ CloseableHttpResponse response = httpPost("/loginInfo/login/v1.0", paramValues, null);
|
|
|
|
|
+ getApacheBody(response);
|
|
|
|
|
+ String setCookie = getApacheHeader(response, "Set-Cookie");
|
|
|
|
|
+ //JSESSIONID=7F304CFA74C75A69DBC8F807A136529376F9F4BDFB2E8D94BA5231243BCAA6A4; Path=/; Secure; HttpOnly
|
|
|
|
|
+ log.info("Set-Cookie JSESSIONID:{}", setCookie);
|
|
|
|
|
+ if (StringUtils.isBlank(setCookie)) {
|
|
|
|
|
+ throw new ServiceException("COOKIE加载失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ token = setCookie.split(";")[0];
|
|
|
|
|
+ log.info("cookie JSESSIONID:{}", token);
|
|
|
if (StringUtils.isNotBlank(token)) {
|
|
if (StringUtils.isNotBlank(token)) {
|
|
|
boundValueOperations.set(token, 30, TimeUnit.MINUTES);
|
|
boundValueOperations.set(token, 30, TimeUnit.MINUTES);
|
|
|
}
|
|
}
|
|
@@ -71,20 +74,115 @@ public class VideoDockingServiceImpl implements VideoDockingService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public void refreshValidToken() throws IOException {
|
|
|
|
|
- Map<String, String> paramValues = new HashMap<>(1);
|
|
|
|
|
- paramValues.put("Cookie", getValidToken());
|
|
|
|
|
- Response response = okHttpPost("/common/keepAlive", paramValues);
|
|
|
|
|
- analysisRequestSuccess(response);
|
|
|
|
|
|
|
+ public void refreshValidToken() throws Exception {
|
|
|
|
|
+ BoundValueOperations<String, String> boundValueOperations = this.redisTemplate.boundValueOps(VIDEO_JSESSIONID_TOKEN);
|
|
|
|
|
+ String cookie = boundValueOperations.get();
|
|
|
|
|
+ if (StringUtils.isBlank(cookie)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ CloseableHttpResponse response = httpPost("/common/keepAlive", Maps.newHashMap(), cookie);
|
|
|
|
|
+ JSONObject body = getApacheBody(response);
|
|
|
|
|
+ log.info("用户登录信息保活:{}", body.toJSONString());
|
|
|
|
|
+ if (StringUtils.isNotBlank(cookie)) {
|
|
|
|
|
+ boundValueOperations.set(cookie, 30, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public JSONObject loadDeviceList(Map<String, String> paramValues) throws Exception {
|
|
|
|
|
+ paramValues.put("deviceType", "33");
|
|
|
|
|
+ CloseableHttpResponse response = httpGet("/device/deviceList/v1.0", paramValues, getValidToken());
|
|
|
|
|
+ JSONObject body = getApacheBody(response);
|
|
|
|
|
+ log.info("获取到的设备列表:{}", body.toJSONString());
|
|
|
|
|
+ return body;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public void loadDeviceList() throws IOException {
|
|
|
|
|
- Map<String, String> paramValues = new HashMap<>(1);
|
|
|
|
|
- paramValues.put("Cookie", getValidToken());
|
|
|
|
|
- Response response = okHttpPost("/device/deviceList/v1.0?deviceType=33&fromIndex=1&toIndex=10", paramValues);
|
|
|
|
|
- analysisRequestSuccess(response);
|
|
|
|
|
- log.info("获取到的设备列表:" + Objects.requireNonNull(response.body()).string());
|
|
|
|
|
|
|
+ public JSONObject loadVideoRtspUrl(String cameraCode) throws Exception {
|
|
|
|
|
+ Map<String, Object> paramValues = Maps.newHashMap();
|
|
|
|
|
+ paramValues.put("cameraCode", cameraCode);
|
|
|
|
|
+ Map<String, Object> mediaURLParam = Maps.newHashMap();
|
|
|
|
|
+ mediaURLParam.put("broadCastType", 0);
|
|
|
|
|
+ mediaURLParam.put("packProtocolType", 1);
|
|
|
|
|
+ mediaURLParam.put("protocolType", 2);
|
|
|
|
|
+ mediaURLParam.put("serviceType", 1);
|
|
|
|
|
+ mediaURLParam.put("streamType", 1);
|
|
|
|
|
+ mediaURLParam.put("transMode", 0);
|
|
|
|
|
+ mediaURLParam.put("clientType", 5);
|
|
|
|
|
+ paramValues.put("mediaURLParam", mediaURLParam);
|
|
|
|
|
+ CloseableHttpResponse response = httpPost("/video/rtspurl/v1.0", paramValues, getValidToken());
|
|
|
|
|
+ return getApacheBody(response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void startRealPlayByIpex(String cameraCode) throws Exception {
|
|
|
|
|
+ Map<String, Object> paramValues = Maps.newHashMap();
|
|
|
|
|
+ paramValues.put("cameraCode", cameraCode);
|
|
|
|
|
+ Map<String, Object> realplayParam = Maps.newHashMap();
|
|
|
|
|
+ realplayParam.put("streamType", 1);
|
|
|
|
|
+ realplayParam.put("protocolType", 1);
|
|
|
|
|
+ realplayParam.put("directFirst", 0);
|
|
|
|
|
+ realplayParam.put("multiCast", 0);
|
|
|
|
|
+ paramValues.put("realplayParam", realplayParam);
|
|
|
|
|
+ Map<String, Object> mediaAddrDst = Maps.newHashMap();
|
|
|
|
|
+ Map<String, Object> dstIP = Maps.newHashMap();
|
|
|
|
|
+ dstIP.put("ipType", 0);
|
|
|
|
|
+ dstIP.put("ip", "192.168.0.15");
|
|
|
|
|
+ mediaAddrDst.put("dstIP", dstIP);
|
|
|
|
|
+ mediaAddrDst.put("audioPort", "40000");
|
|
|
|
|
+ mediaAddrDst.put("videoPort", "40000");
|
|
|
|
|
+ mediaAddrDst.put("reserve", cameraCode);
|
|
|
|
|
+ paramValues.put("mediaAddrDst", mediaAddrDst);
|
|
|
|
|
+ paramValues.put("clientType", 1);
|
|
|
|
|
+ CloseableHttpResponse response = httpPost("/device/startrealplaybyipex", paramValues, getValidToken());
|
|
|
|
|
+ getApacheBody(response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void startIntelligentDataSubscribes(String ids) {
|
|
|
|
|
+ taskExecutor.execute(() -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, Object> params = Maps.newHashMap();
|
|
|
|
|
+ Map<String, Object> subscribeObject = Maps.newHashMap();
|
|
|
|
|
+ List<Map<String, String>> list = Lists.newArrayList();
|
|
|
|
|
+ Map<String, String> data = new HashMap<>(10);
|
|
|
|
|
+ data.put("Title", "实时智能数据订阅接口");
|
|
|
|
|
+ data.put("SubscribeDetail", "0");
|
|
|
|
|
+ data.put("ResourceClass", "1");
|
|
|
|
|
+ data.put("ResourceURI", ids);
|
|
|
|
|
+ data.put("ApplicantName", "yzx22");
|
|
|
|
|
+ data.put("ApplicantOrg", "hw");
|
|
|
|
|
+ data.put("BeginTime", "20210801100000");
|
|
|
|
|
+ data.put("EndTime", "20210830094059");
|
|
|
|
|
+ data.put("ReceiveAddr", "http://171.104.234.207:9800");
|
|
|
|
|
+ data.put("Reason", "hw");
|
|
|
|
|
+ data.put("OperateType", "0");
|
|
|
|
|
+ data.put("SubscribeStatus", "0");
|
|
|
|
|
+ data.put("ResultImageDeclare", "-1");
|
|
|
|
|
+ data.put("ResultFeatureDeclare", "-1");
|
|
|
|
|
+ data.put("SubscribeDataType", "1");
|
|
|
|
|
+ data.put("DeviceCodeType", "0");
|
|
|
|
|
+ list.add(data);
|
|
|
|
|
+ subscribeObject.put("SubscribeObject", list);
|
|
|
|
|
+ params.put("SubscribeListObject", subscribeObject);
|
|
|
|
|
+
|
|
|
|
|
+ log.info("添加实时智能数据订阅 params:{}", JSONObject.toJSONString(params));
|
|
|
|
|
+ CloseableHttpResponse response = httpPost("/IntelligentData/Subscribes", params, getValidToken());
|
|
|
|
|
+ JSONObject body = getApacheBody(response);
|
|
|
|
|
+ log.info("添加实时智能数据订阅:{}", body.toJSONString());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void deleteIntelligentDataSubscribes(String ids) throws Exception {
|
|
|
|
|
+// Map<String, String> params = Maps.newHashMap();
|
|
|
|
|
+// params.put("IDList", ids);
|
|
|
|
|
+// CloseableHttpResponse response = httpDelete("/IntelligentData/Subscribes", params, getValidToken());
|
|
|
|
|
+// JSONObject body = getApacheBody(response);
|
|
|
|
|
+// log.info("删除实时智能数据订阅:{}", body.toJSONString());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -94,49 +192,68 @@ public class VideoDockingServiceImpl implements VideoDockingService {
|
|
|
* @param paramValues paramValues
|
|
* @param paramValues paramValues
|
|
|
* @return String
|
|
* @return String
|
|
|
*/
|
|
*/
|
|
|
- private Response okHttpPost(String uri, Map<String, String> paramValues) throws IOException {
|
|
|
|
|
- String cookie = paramValues.get("Cookie");
|
|
|
|
|
- if (StringUtils.isNotBlank(cookie)) {
|
|
|
|
|
- paramValues.remove("Cookie");
|
|
|
|
|
- }
|
|
|
|
|
- FormBody.Builder formBuilder = new FormBody.Builder();
|
|
|
|
|
- paramValues.forEach(formBuilder::add);
|
|
|
|
|
-
|
|
|
|
|
- Request.Builder builder = new Request.Builder();
|
|
|
|
|
|
|
+ private static CloseableHttpResponse httpPost(String uri, Map<String, Object> paramValues, String cookie) throws IOException {
|
|
|
|
|
+ // 1. 创建HttpClient
|
|
|
|
|
+ CloseableHttpClient httpClient = createSSLClientDefault();
|
|
|
String url = String.format("%s" + uri, URI);
|
|
String url = String.format("%s" + uri, URI);
|
|
|
- log.info("URL:{}", url);
|
|
|
|
|
- builder.url(url);
|
|
|
|
|
- builder.header("Content-Type", "application/json;charset=UTF-8");
|
|
|
|
|
|
|
+ log.info("REQUEST URI:{},cooke:{}", url, cookie);
|
|
|
|
|
+ RequestConfig defaultConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 创建POST请求
|
|
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
|
|
+ httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
|
|
|
|
|
+ httpPost.setHeader("Connection", "keep-alive");
|
|
|
|
|
+ httpPost.setHeader("Accept", "application/json, text/plain, */*");
|
|
|
|
|
+ httpPost.setHeader("Accept-Encoding", "gzip, deflate, br");
|
|
|
|
|
+ httpPost.setHeader("Sec-Fetch-Site", "same-origin");
|
|
|
|
|
+ httpPost.setHeader("Sec-Fetch-Mode", "cors");
|
|
|
|
|
+ httpPost.setHeader("Sec-Fetch-Dest", "empty");
|
|
|
|
|
+ httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");
|
|
|
if (StringUtils.isNotBlank(cookie)) {
|
|
if (StringUtils.isNotBlank(cookie)) {
|
|
|
- builder.header("Cookie", cookie);
|
|
|
|
|
|
|
+ httpPost.setHeader("Cookie", cookie);
|
|
|
}
|
|
}
|
|
|
- builder.post(formBuilder.build());
|
|
|
|
|
- Response response = this.okHttpClient.newCall(builder.build()).execute();
|
|
|
|
|
- if (response.isSuccessful()) {
|
|
|
|
|
- return response;
|
|
|
|
|
- } else {
|
|
|
|
|
- try {
|
|
|
|
|
- String result = Objects.requireNonNull(response.body()).string();
|
|
|
|
|
- log.error(result);
|
|
|
|
|
- JSONObject jsonObject = JSON.parseObject(result);
|
|
|
|
|
- throw new ServiceException(jsonObject.getString("error_description"));
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("okHttpPost error:" + e.getMessage(), e);
|
|
|
|
|
- throw new ServiceException("网络异常" + response.message());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ httpPost.setConfig(defaultConfig);
|
|
|
|
|
+ if (paramValues != null && paramValues.size() > 0) {
|
|
|
|
|
+ log.info("http post body:", paramValues);
|
|
|
|
|
+ httpPost.setEntity(new StringEntity(JSONObject.toJSONString(paramValues)));
|
|
|
}
|
|
}
|
|
|
|
|
+ return httpClient.execute(httpPost);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 解析请求是否成功
|
|
|
|
|
|
|
+ * 统一GET请求方法
|
|
|
*
|
|
*
|
|
|
- * @param response response
|
|
|
|
|
|
|
+ * @param uri uri
|
|
|
|
|
+ * @param paramValues paramValues
|
|
|
|
|
+ * @return String
|
|
|
*/
|
|
*/
|
|
|
- private void analysisRequestSuccess(Response response) throws IOException {
|
|
|
|
|
- String result = Objects.requireNonNull(response.body()).string();
|
|
|
|
|
- JSONObject jsonObject = JSON.parseObject(result);
|
|
|
|
|
- if (0 != jsonObject.getIntValue(RESULT_CODE)) {
|
|
|
|
|
- throw new ServerException(jsonObject.getString(""));
|
|
|
|
|
|
|
+ private static CloseableHttpResponse httpGet(String uri, Map<String, String> paramValues, String cookie) throws IOException {
|
|
|
|
|
+ // 1. 创建HttpClient
|
|
|
|
|
+ CloseableHttpClient httpClient = createSSLClientDefault();
|
|
|
|
|
+ String url = String.format("%s" + uri, URI);
|
|
|
|
|
+ log.info("REQUEST URI:{}", url);
|
|
|
|
|
+ url = getRequestGetUri(url, paramValues);
|
|
|
|
|
+ log.info("REQUEST getRequestGetUri URI:{}", url);
|
|
|
|
|
+ RequestConfig defaultConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
|
|
|
|
+ HttpGet httpGet = new HttpGet(url);
|
|
|
|
|
+ httpGet.setHeader("Content-Type", "application/json;charset=UTF-8");
|
|
|
|
|
+ httpGet.setHeader("Connection", "keep-alive");
|
|
|
|
|
+ httpGet.setHeader("Accept", "application/json, text/plain, */*");
|
|
|
|
|
+ httpGet.setHeader("Accept-Encoding", "gzip, deflate, br");
|
|
|
|
|
+ httpGet.setHeader("Sec-Fetch-Site", "same-origin");
|
|
|
|
|
+ httpGet.setHeader("Sec-Fetch-Mode", "cors");
|
|
|
|
|
+ httpGet.setHeader("Sec-Fetch-Dest", "empty");
|
|
|
|
|
+ httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");
|
|
|
|
|
+ if (StringUtils.isNotBlank(cookie)) {
|
|
|
|
|
+ log.info("==============cookie=================={}", cookie);
|
|
|
|
|
+ httpGet.setHeader("Cookie", cookie);
|
|
|
|
|
+// CookieStore cookieStore = new BasicCookieStore();
|
|
|
|
|
+// //添加cookie
|
|
|
|
|
+// BasicClientCookie basicClientCookie = new BasicClientCookie(cookies[0], cookies[1]);
|
|
|
|
|
+// cookieStore.addCookie(basicClientCookie);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ httpGet.setConfig(defaultConfig);
|
|
|
|
|
+ return httpClient.execute(httpGet);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|