1313import java .util .Comparator ;
1414import java .util .List ;
1515import java .util .concurrent .ConcurrentHashMap ;
16+ import java .util .stream .Stream ;
1617import org .slf4j .Logger ;
1718import org .slf4j .LoggerFactory ;
18- import org .springframework .beans .factory .annotation .Autowired ;
1919import org .springframework .stereotype .Component ;
2020import org .springframework .web .socket .CloseStatus ;
2121import org .springframework .web .socket .TextMessage ;
2828@ Component
2929public class LspWebSocketHandler extends TextWebSocketHandler {
3030
31- @ Autowired private MetaModelService metaModelService ;
32-
3331 private static final Logger logger = LoggerFactory .getLogger (LspWebSocketHandler .class );
3432 private final ConcurrentHashMap <String , LspServerProcess > sessions = new ConcurrentHashMap <>();
33+ private final MetaModelService metaModelService ;
34+
35+ public LspWebSocketHandler (MetaModelService metaModelService ) {
36+ this .metaModelService = metaModelService ;
37+ }
3538
3639 @ Override
3740 public void afterConnectionEstablished (WebSocketSession session ) throws Exception {
@@ -40,9 +43,6 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio
4043
4144 // URI und Query-Parameter
4245 URI uri = session .getUri ();
43- logger .info ("URI: {}" , uri );
44- logger .info ("Query String: {}" , uri .getQuery ());
45- logger .info ("Path: {}" , uri .getPath ());
4646
4747 // Session Attributes
4848 logger .info ("Session Attributes:" );
@@ -55,18 +55,10 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio
5555 // Principal (User info)
5656 logger .info ("Principal: {}" , session .getPrincipal ());
5757
58- // Extrahierte Werte
59- Long userId = extractUserId (session );
6058 Long vsumId = extractProjectId (session );
61- logger .info ("Extracted userId: {}" , userId );
6259 logger .info ("Extracted projectId: {}" , vsumId );
6360 logger .info ("=== End WebSocket Info ===" );
6461
65- if (userId == null ) {
66- session .close (CloseStatus .POLICY_VIOLATION .withReason ("userId required" ));
67- return ;
68- }
69-
7062 Path sessionDir = Files .createTempDirectory ("lsp-session-" + session .getId ());
7163 Path userProject = sessionDir .resolve ("UserProject" );
7264 Path modelDir = userProject .resolve ("model" );
@@ -99,7 +91,7 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio
9991 new LspServerProcess (session , process , writer , reader , sessionDir , userProject );
10092 sessions .put (session .getId (), lspProcess );
10193
102- new Thread (() -> lspProcess . readFromLsp () ).start ();
94+ new Thread (lspProcess :: readFromLsp ).start ();
10395
10496 new Thread (
10597 () -> {
@@ -111,9 +103,11 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio
111103 "{\" type\" :\" workspaceReady\" ,\" rootUri\" :\" %s\" }" ,
112104 userProject .toUri ().toString ());
113105 session .sendMessage (new TextMessage (rootUriMessage ));
106+ } catch (InterruptedException e ) {
107+ Thread .currentThread ().interrupt ();
108+ logger .error ("💥 Failed to send workspaceReady: {}" , e .getMessage ());
114109 } catch (Exception e ) {
115- logger .error ("💥 Failed to send workspaceReady: " + e .getMessage ());
116- e .printStackTrace ();
110+ logger .error ("💥 Failed to send workspaceReady: {}" , e .getMessage ());
117111 }
118112 })
119113 .start ();
@@ -138,16 +132,20 @@ public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
138132 serverProcess .destroy ();
139133
140134 if (serverProcess .tempDir != null && Files .exists (serverProcess .tempDir )) {
141- Files .walk (serverProcess .tempDir )
142- .sorted (Comparator .reverseOrder ())
143- .forEach (
144- path -> {
145- try {
146- Files .delete (path );
147- } catch (IOException e ) {
148- logger .warn ("Cleanup failed: {}" , path );
149- }
150- });
135+ try (Stream <Path > paths = Files .walk (serverProcess .tempDir )) {
136+ paths
137+ .sorted (Comparator .reverseOrder ())
138+ .forEach (
139+ path -> {
140+ try {
141+ Files .delete (path );
142+ } catch (IOException e ) {
143+ logger .warn ("Cleanup failed: {}" , path , e );
144+ }
145+ });
146+ } catch (IOException e ) {
147+ logger .warn ("Failed to walk temp directory: {}" , serverProcess .tempDir , e );
148+ }
151149 }
152150 }
153151 }
@@ -167,7 +165,6 @@ private class LspServerProcess {
167165 final BufferedWriter writer ;
168166 final BufferedReader reader ;
169167 private final Path tempDir ;
170- private final Path userProject ;
171168
172169 LspServerProcess (
173170 WebSocketSession session ,
@@ -181,40 +178,55 @@ private class LspServerProcess {
181178 this .writer = writer ;
182179 this .reader = reader ;
183180 this .tempDir = tempDir ;
184- this .userProject = userProject ;
185181 }
186182
187- void readFromLsp () {
183+ private int parseContentLength (String line ) {
184+ return Integer .parseInt (line .split (":" )[1 ].trim ());
185+ }
186+
187+ private boolean handleContentLengthLine (String line ) {
188188 try {
189- String line ;
190- while ((line = reader .readLine ()) != null ) {
191- if (line .startsWith ("Content-Length:" )) {
192- try {
193- int contentLength = Integer .parseInt (line .split (":" )[1 ].trim ());
189+ int contentLength = parseContentLength (line );
190+
191+ String separatorLine = reader .readLine (); // header/body separator
192+ if (separatorLine == null || !separatorLine .isEmpty ()) {
193+ logger .warn (
194+ "Expected empty line after Content-Length header for session: {}, but got: '{}'" ,
195+ session .getId (),
196+ separatorLine );
197+ }
194198
195- reader .readLine (); // Skip empty line
199+ char [] content = new char [contentLength ];
200+ int read = reader .read (content , 0 , contentLength );
196201
197- char [] content = new char [contentLength ];
198- int read = reader .read (content , 0 , contentLength );
202+ if (read != contentLength ) {
203+ logger .warn (
204+ "Expected {} bytes but read {} bytes from LSP for session: {}" ,
205+ contentLength ,
206+ read ,
207+ session .getId ());
208+ }
199209
200- if (read != contentLength ) {
201- logger .warn (
202- "Expected {} bytes but read {} bytes from LSP for session: {}" ,
203- contentLength ,
204- read ,
205- session .getId ());
206- }
210+ String message = new String (content , 0 , read );
211+ session .sendMessage (new TextMessage (message ));
207212
208- String message = new String (content , 0 , read );
209- session .sendMessage (new TextMessage (message ));
213+ return true ;
214+ } catch (NumberFormatException e ) {
215+ logger .error ("Invalid Content-Length header from LSP for session: {}" , session .getId (), e );
216+ return true ; // malformed message, but keep reading
217+ } catch (IOException e ) {
218+ logger .error ("Failed to send LSP message to WebSocket session: {}" , session .getId (), e );
219+ return false ; // WebSocket is broken → caller should stop
220+ }
221+ }
210222
211- } catch ( NumberFormatException e ) {
212- logger . error (
213- "Invalid Content-Length header from LSP for session: {}" , session . getId (), e ) ;
214- } catch ( IOException e ) {
215- logger . error (
216- "Failed to send LSP message to WebSocket session: {}" , session . getId (), e );
217- break ; // WebSocket is broken, no point in continuing
223+ void readFromLsp ( ) {
224+ try {
225+ String line ;
226+ while (( line = reader . readLine ()) != null ) {
227+ if ( line . startsWith ( "Content-Length:" )) {
228+ if (! handleContentLengthLine ( line )) {
229+ break ; // stop reading if the WebSocket is broken
218230 }
219231 }
220232 }
@@ -245,78 +257,6 @@ void destroy() {
245257 }
246258 }
247259
248- private Long extractUserId (WebSocketSession session ) {
249- try {
250- String query = session .getUri ().getQuery ();
251- if (query != null && query .contains ("userId=" )) {
252- String userIdStr = extractQueryParam (query , "userId" );
253- if (userIdStr != null ) {
254- Long userId = Long .parseLong (userIdStr );
255- logger .debug ("Extracted userId from query parameter: {}" , userId );
256- return userId ;
257- }
258- }
259-
260- Object principal = session .getPrincipal ();
261- if (principal != null ) {
262- logger .debug ("Principal type: {}" , principal .getClass ().getName ());
263-
264- if (principal
265- instanceof
266- org .springframework .security .oauth2 .server .resource .authentication
267- .JwtAuthenticationToken ) {
268- org .springframework .security .oauth2 .server .resource .authentication .JwtAuthenticationToken
269- jwt =
270- (org .springframework .security .oauth2 .server .resource .authentication
271- .JwtAuthenticationToken )
272- principal ;
273-
274- String sub = jwt .getToken ().getClaim ("sub" );
275- if (sub != null ) {
276- try {
277- Long userId = Long .parseLong (sub );
278- logger .debug ("Extracted userId from JWT 'sub' claim: {}" , userId );
279- return userId ;
280- } catch (NumberFormatException e ) {
281- logger .warn ("JWT 'sub' claim is not a number: {}" , sub );
282- }
283- }
284-
285- Object userIdClaim = jwt .getToken ().getClaim ("userId" );
286- if (userIdClaim != null ) {
287- Long userId = Long .parseLong (userIdClaim .toString ());
288- logger .debug ("Extracted userId from JWT 'userId' claim: {}" , userId );
289- return userId ;
290- }
291-
292- String email = jwt .getToken ().getClaim ("preferred_username" );
293- if (email == null ) {
294- email = jwt .getToken ().getClaim ("email" );
295- }
296- if (email != null ) {
297- logger .debug ("Found email in JWT: {}, need to lookup userId" , email );
298- }
299- }
300-
301- logger .debug ("Principal toString: {}" , principal );
302- }
303-
304- Object userIdAttr = session .getAttributes ().get ("userId" );
305- if (userIdAttr != null ) {
306- Long userId = Long .parseLong (userIdAttr .toString ());
307- logger .debug ("Extracted userId from session attributes: {}" , userId );
308- return userId ;
309- }
310-
311- logger .warn ("Could not extract userId from WebSocket session. URI: {}" , session .getUri ());
312- return null ;
313-
314- } catch (Exception e ) {
315- logger .error ("Error extracting userId from WebSocket session" , e );
316- return null ;
317- }
318- }
319-
320260 private Long extractProjectId (WebSocketSession session ) {
321261 try {
322262 String query = session .getUri ().getQuery ();
0 commit comments