diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/api/IBuriedPointType.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/api/IBuriedPointType.java new file mode 100644 index 000000000..06dc02af6 --- /dev/null +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/api/IBuriedPointType.java @@ -0,0 +1,10 @@ +package com.ai.cloud.skywalking.api; + +import com.ai.cloud.skywalking.model.CallType; + +public interface IBuriedPointType { + + String getTypeName(); + + CallType getCallType(); +} diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/DubboBuriedPointType.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/DubboBuriedPointType.java new file mode 100644 index 000000000..281d9da6b --- /dev/null +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/DubboBuriedPointType.java @@ -0,0 +1,16 @@ +package com.ai.cloud.skywalking.buriedpoint.type; + +import com.ai.cloud.skywalking.api.IBuriedPointType; +import com.ai.cloud.skywalking.model.CallType; + +public class DubboBuriedPointType implements IBuriedPointType { + @Override + public String getTypeName() { + return "D"; + } + + @Override + public CallType getCallType() { + return CallType.ASYNC; + } +} diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/JDBCBuriedPointType.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/JDBCBuriedPointType.java new file mode 100644 index 000000000..e3f147eeb --- /dev/null +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/JDBCBuriedPointType.java @@ -0,0 +1,16 @@ +package com.ai.cloud.skywalking.buriedpoint.type; + +import com.ai.cloud.skywalking.api.IBuriedPointType; +import com.ai.cloud.skywalking.model.CallType; + +public class JDBCBuriedPointType implements IBuriedPointType { + @Override + public String getTypeName() { + return "J"; + } + + @Override + public CallType getCallType() { + return CallType.LOCAL; + } +} diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/SpringBuriedPointType.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/SpringBuriedPointType.java new file mode 100644 index 000000000..e50941d1d --- /dev/null +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/SpringBuriedPointType.java @@ -0,0 +1,18 @@ +package com.ai.cloud.skywalking.buriedpoint.type; + +import com.ai.cloud.skywalking.api.IBuriedPointType; +import com.ai.cloud.skywalking.model.CallType; + +public class SpringBuriedPointType implements IBuriedPointType { + + @Override + public String getTypeName() { + return "M"; + } + + @Override + public CallType getCallType() { + return CallType.LOCAL; + } + +} diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/WEBBuriedPointType.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/WEBBuriedPointType.java new file mode 100644 index 000000000..a866b180a --- /dev/null +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/buriedpoint/type/WEBBuriedPointType.java @@ -0,0 +1,16 @@ +package com.ai.cloud.skywalking.buriedpoint.type; + +import com.ai.cloud.skywalking.api.IBuriedPointType; +import com.ai.cloud.skywalking.model.CallType; + +public class WEBBuriedPointType implements IBuriedPointType { + @Override + public String getTypeName() { + return "W"; + } + + @Override + public CallType getCallType() { + return CallType.ASYNC; + } +} diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/CallType.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/CallType.java new file mode 100644 index 000000000..f1a0bf20f --- /dev/null +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/CallType.java @@ -0,0 +1,31 @@ +package com.ai.cloud.skywalking.model; + +public enum CallType { + + LOCAL('L'), SYNC('S'), ASYNC('A'); + + private char value; + + CallType(char value) { + this.value = value; + } + + public CallType convert(String id) { + char v = id.charAt(0); + switch (v) { + case 'L': + return LOCAL; + case 'S': + return SYNC; + case 'A': + return ASYNC; + default: + throw new IllegalStateException("Failed to convert callType[" + id + "]"); + } + } + + @Override + public String toString() { + return String.valueOf(value); + } +} diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/Identification.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/Identification.java index fd0507e05..deee44c1c 100644 --- a/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/Identification.java +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/model/Identification.java @@ -1,9 +1,14 @@ package com.ai.cloud.skywalking.model; +import com.ai.cloud.skywalking.api.IBuriedPointType; +import com.ai.cloud.skywalking.util.StringUtil; +import com.sun.xml.internal.txw2.IllegalSignatureException; + public class Identification { private String viewPoint; private String businessKey; private String spanType; + private String callType; public Identification() { //Non @@ -16,9 +21,13 @@ public class Identification { public String getBusinessKey() { return businessKey; } - - public String getSpanType(){ - return spanType; + + public String getSpanType() { + return spanType; + } + + public String getCallType() { + return callType; } public static IdentificationBuilder newBuilder() { @@ -45,9 +54,13 @@ public class Identification { sendData.businessKey = businessKey; return this; } - - public IdentificationBuilder spanType(String spanType) { - sendData.spanType = spanType; + + public IdentificationBuilder spanType(IBuriedPointType spanType) { + if (StringUtil.isEmpty(spanType.getTypeName())) { + throw new IllegalSignatureException("Span Type name cannot be null"); + } + sendData.spanType = spanType.getTypeName(); + sendData.callType = spanType.getCallType().toString(); return this; } diff --git a/skywalking-api/src/main/java/com/ai/cloud/skywalking/util/ContextGenerator.java b/skywalking-api/src/main/java/com/ai/cloud/skywalking/util/ContextGenerator.java index 0958d9d26..46b26a324 100644 --- a/skywalking-api/src/main/java/com/ai/cloud/skywalking/util/ContextGenerator.java +++ b/skywalking-api/src/main/java/com/ai/cloud/skywalking/util/ContextGenerator.java @@ -45,6 +45,8 @@ public final class ContextGenerator { spanData.setSpanType(id.getSpanType()); spanData.setViewPointId(id.getViewPoint()); spanData.setBusinessKey(id.getBusinessKey()); + //FIX Add Call Type field + spanData.setCallType(id.getCallType()); // 设置基本信息 spanData.setStartDate(System.currentTimeMillis()); spanData.setProcessNo(BuriedPointMachineUtil.getProcessNo()); diff --git a/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/Span.java b/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/Span.java index 479b16678..d4ea024df 100644 --- a/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/Span.java +++ b/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/Span.java @@ -48,6 +48,7 @@ public class Span extends SpanData { processNo = fieldValues[12].trim(); applicationId = fieldValues[13].trim(); userId = fieldValues[14].trim(); + callType = fieldValues[15].trim(); this.originData = originData; } @@ -121,11 +122,13 @@ public class Span extends SpanData { } if (isNonBlank(userId)) { - toStringValue.append(userId); + toStringValue.append(userId + SPAN_FIELD_SPILT_PATTERN); } else { toStringValue.append(" " + SPAN_FIELD_SPILT_PATTERN); } + toStringValue.append(callType); + return toStringValue.toString(); } diff --git a/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/SpanData.java b/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/SpanData.java index 0c3421ed2..3291a779d 100644 --- a/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/SpanData.java +++ b/skywalking-protocol/src/main/java/com/ai/cloud/skywalking/protocol/SpanData.java @@ -17,6 +17,7 @@ public abstract class SpanData { protected byte statusCode = 0; protected String exceptionStack; protected String spanType = ""; + protected String callType = ""; protected boolean isReceiver = false; protected String businessKey = ""; protected String processNo = ""; @@ -132,4 +133,8 @@ public abstract class SpanData { public String getUserId() { return userId; } + + public void setCallType(String callType) { + this.callType = callType; + } } diff --git a/skywalking-sdk-plugin/dubbo-plugin/src/main/java/com/ai/cloud/skywalking/plugin/dubbo/SWDubboEnhanceFilter.java b/skywalking-sdk-plugin/dubbo-plugin/src/main/java/com/ai/cloud/skywalking/plugin/dubbo/SWDubboEnhanceFilter.java index bf872fbb0..5ac31f4da 100644 --- a/skywalking-sdk-plugin/dubbo-plugin/src/main/java/com/ai/cloud/skywalking/plugin/dubbo/SWDubboEnhanceFilter.java +++ b/skywalking-sdk-plugin/dubbo-plugin/src/main/java/com/ai/cloud/skywalking/plugin/dubbo/SWDubboEnhanceFilter.java @@ -2,6 +2,7 @@ package com.ai.cloud.skywalking.plugin.dubbo; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointReceiver; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.DubboBuriedPointType; import com.ai.cloud.skywalking.conf.AuthDesc; import com.ai.cloud.skywalking.model.ContextData; import com.ai.cloud.skywalking.model.Identification; @@ -14,10 +15,10 @@ import com.alibaba.dubbo.rpc.*; public class SWDubboEnhanceFilter implements Filter { public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { - if(!AuthDesc.isAuth()){ - return invoker.invoke(invocation); - } - + if (!AuthDesc.isAuth()) { + return invoker.invoke(invocation); + } + RpcContext context = RpcContext.getContext(); boolean isConsumer = context.isConsumerSide(); Result result = null; @@ -101,7 +102,7 @@ public class SWDubboEnhanceFilter implements Filter { } viewPoint.append(")"); - return Identification.newBuilder().viewPoint(viewPoint.toString()).spanType("D").build(); + return Identification.newBuilder().viewPoint(viewPoint.toString()).spanType(new DubboBuriedPointType()).build(); } diff --git a/skywalking-sdk-plugin/httpclient-4.2.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v42x/HttpClientTracing.java b/skywalking-sdk-plugin/httpclient-4.2.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v42x/HttpClientTracing.java index ab8ccb828..af5de9167 100644 --- a/skywalking-sdk-plugin/httpclient-4.2.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v42x/HttpClientTracing.java +++ b/skywalking-sdk-plugin/httpclient-4.2.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v42x/HttpClientTracing.java @@ -1,9 +1,9 @@ package com.ai.cloud.skywalking.plugin.httpclient.v42x; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.WEBBuriedPointType; import com.ai.cloud.skywalking.conf.AuthDesc; import com.ai.cloud.skywalking.model.Identification; - import org.apache.http.HttpRequest; import java.io.IOException; @@ -13,15 +13,15 @@ public class HttpClientTracing { private static RPCBuriedPointSender sender = new RPCBuriedPointSender(); public static R execute(String url, String traceHearName, HttpRequest httpRequest, Executor executor) throws IOException { - if(!AuthDesc.isAuth()){ - return executor.execute(); - } - + if (!AuthDesc.isAuth()) { + return executor.execute(); + } + try { httpRequest.setHeader(traceHearName, "ContextData=" + sender.beforeSend(Identification.newBuilder() .viewPoint(url) - .spanType("W") + .spanType(new WEBBuriedPointType()) .build()) .toString()); return executor.execute(); diff --git a/skywalking-sdk-plugin/httpclient-4.3.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v43x/HttpClientTracing.java b/skywalking-sdk-plugin/httpclient-4.3.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v43x/HttpClientTracing.java index 2bd5c94a0..b39f091c8 100644 --- a/skywalking-sdk-plugin/httpclient-4.3.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v43x/HttpClientTracing.java +++ b/skywalking-sdk-plugin/httpclient-4.3.x-plugin/src/main/java/com/ai/cloud/skywalking/plugin/httpclient/v43x/HttpClientTracing.java @@ -1,6 +1,7 @@ package com.ai.cloud.skywalking.plugin.httpclient.v43x; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.WEBBuriedPointType; import com.ai.cloud.skywalking.conf.AuthDesc; import com.ai.cloud.skywalking.model.Identification; import org.apache.http.HttpRequest; @@ -20,7 +21,7 @@ public class HttpClientTracing { httpRequest.setHeader(traceHearName, "ContextData=" + sender.beforeSend(Identification.newBuilder() .viewPoint(url) - .spanType("W") + .spanType(new WEBBuriedPointType()) .build()) .toString()); return executor.execute(); diff --git a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/CallableStatementTracing.java b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/CallableStatementTracing.java index 9838e6bae..8e20fdf84 100644 --- a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/CallableStatementTracing.java +++ b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/CallableStatementTracing.java @@ -3,6 +3,7 @@ package com.ai.cloud.skywalking.plugin.jdbc.tracing; import java.sql.SQLException; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.JDBCBuriedPointType; import com.ai.cloud.skywalking.model.Identification; /** @@ -25,7 +26,7 @@ public class CallableStatementTracing { "callableStatement." + method + (sql == null || sql.length() == 0 ? "" - : ":" + sql)).spanType("J").build()); + : ":" + sql)).spanType(new JDBCBuriedPointType()).build()); return exec.exe(realStatement, sql); } catch (SQLException e) { sender.handleException(e); diff --git a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/ConnectionTracing.java b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/ConnectionTracing.java index c5cb12143..175ccc943 100644 --- a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/ConnectionTracing.java +++ b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/ConnectionTracing.java @@ -3,6 +3,7 @@ package com.ai.cloud.skywalking.plugin.jdbc.tracing; import java.sql.SQLException; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.JDBCBuriedPointType; import com.ai.cloud.skywalking.model.Identification; /** @@ -25,7 +26,7 @@ public class ConnectionTracing { "connection." + method + (sql == null || sql.length() == 0 ? "" - : ":" + sql)).spanType("J").build()); + : ":" + sql)).spanType(new JDBCBuriedPointType()).build()); return exec.exe(realConnection, sql); } catch (SQLException e) { sender.handleException(e); diff --git a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/PreparedStatementTracing.java b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/PreparedStatementTracing.java index 5226bd844..7786e827b 100644 --- a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/PreparedStatementTracing.java +++ b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/PreparedStatementTracing.java @@ -3,6 +3,7 @@ package com.ai.cloud.skywalking.plugin.jdbc.tracing; import java.sql.SQLException; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.JDBCBuriedPointType; import com.ai.cloud.skywalking.model.Identification; /** @@ -25,7 +26,7 @@ public class PreparedStatementTracing { "preaparedStatement." + method + (sql == null || sql.length() == 0 ? "" - : ":" + sql)).spanType("J").build()); + : ":" + sql)).spanType(new JDBCBuriedPointType()).build()); return exec.exe(realStatement, sql); } catch (SQLException e) { sender.handleException(e); diff --git a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/StatementTracing.java b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/StatementTracing.java index ed485eeaf..c05e76e3e 100644 --- a/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/StatementTracing.java +++ b/skywalking-sdk-plugin/jdbc-plugin/src/main/java/com/ai/cloud/skywalking/plugin/jdbc/tracing/StatementTracing.java @@ -3,6 +3,7 @@ package com.ai.cloud.skywalking.plugin.jdbc.tracing; import java.sql.SQLException; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.JDBCBuriedPointType; import com.ai.cloud.skywalking.model.Identification; /** @@ -25,7 +26,7 @@ public class StatementTracing { "statement." + method + (sql == null || sql.length() == 0 ? "" - : ":" + sql)).spanType("J").build()); + : ":" + sql)).spanType(new JDBCBuriedPointType()).build()); return exec.exe(realStatement, sql); } catch (SQLException e) { sender.handleException(e); diff --git a/skywalking-sdk-plugin/spring-plugin/src/main/java/com/ai/cloud/skywalking/plugin/spring/TracingAspect.java b/skywalking-sdk-plugin/spring-plugin/src/main/java/com/ai/cloud/skywalking/plugin/spring/TracingAspect.java index fb8a8077b..c29660244 100644 --- a/skywalking-sdk-plugin/spring-plugin/src/main/java/com/ai/cloud/skywalking/plugin/spring/TracingAspect.java +++ b/skywalking-sdk-plugin/spring-plugin/src/main/java/com/ai/cloud/skywalking/plugin/spring/TracingAspect.java @@ -1,11 +1,10 @@ package com.ai.cloud.skywalking.plugin.spring; import com.ai.cloud.skywalking.buriedpoint.LocalBuriedPointSender; +import com.ai.cloud.skywalking.buriedpoint.type.SpringBuriedPointType; import com.ai.cloud.skywalking.model.Identification; import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.reflect.MethodSignature; -import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; @@ -15,7 +14,7 @@ public class TracingAspect { LocalBuriedPointSender _sender = new LocalBuriedPointSender(); try { StringBuilder viewPoint = new StringBuilder(); - viewPoint.append(proceedingJoinPoint.getTarget().getClass().getName() + "." + proceedingJoinPoint.getSignature().getName() + "("); + viewPoint.append(proceedingJoinPoint.getTarget().getClass().getName() + "." + proceedingJoinPoint.getSignature().getName() + "("); boolean first = true; for (Object arg : proceedingJoinPoint.getArgs()) { if (!first) { @@ -26,7 +25,7 @@ public class TracingAspect { viewPoint.append(arg.getClass().getName()); } viewPoint.append(")"); - _sender.beforeSend(Identification.newBuilder().viewPoint(viewPoint.toString()).spanType("M").build()); + _sender.beforeSend(Identification.newBuilder().viewPoint(viewPoint.toString()).spanType(new SpringBuriedPointType()).build()); return proceedingJoinPoint.proceed(); } catch (Throwable e) { _sender.handleException(e); diff --git a/skywalking-sdk-plugin/web-plugin/src/main/java/com/ai/cloud/skywalking/plugin/web/SkyWalkingFilter.java b/skywalking-sdk-plugin/web-plugin/src/main/java/com/ai/cloud/skywalking/plugin/web/SkyWalkingFilter.java index 9a7e73163..c868cef81 100644 --- a/skywalking-sdk-plugin/web-plugin/src/main/java/com/ai/cloud/skywalking/plugin/web/SkyWalkingFilter.java +++ b/skywalking-sdk-plugin/web-plugin/src/main/java/com/ai/cloud/skywalking/plugin/web/SkyWalkingFilter.java @@ -3,6 +3,7 @@ package com.ai.cloud.skywalking.plugin.web; import com.ai.cloud.skywalking.api.Tracing; import com.ai.cloud.skywalking.buriedpoint.RPCBuriedPointReceiver; +import com.ai.cloud.skywalking.buriedpoint.type.WEBBuriedPointType; import com.ai.cloud.skywalking.conf.AuthDesc; import com.ai.cloud.skywalking.model.ContextData; import com.ai.cloud.skywalking.model.Identification; @@ -10,7 +11,6 @@ import com.ai.cloud.skywalking.model.Identification; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import java.io.IOException; public class SkyWalkingFilter implements Filter { @@ -28,11 +28,11 @@ public class SkyWalkingFilter implements Filter { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - if(!AuthDesc.isAuth()){ - filterChain.doFilter(servletRequest, servletResponse); - return; - } - + if (!AuthDesc.isAuth()) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + RPCBuriedPointReceiver receiver = null; try { HttpServletRequest request = (HttpServletRequest) servletRequest; @@ -71,7 +71,7 @@ public class SkyWalkingFilter implements Filter { private Identification generateIdentification(HttpServletRequest request) { return Identification.newBuilder() .viewPoint(request.getRequestURL().toString()) - .spanType("W") + .spanType(new WEBBuriedPointType()) .build(); }