1 /*
2 * Copyright 2012-2025 CodeLibs Project and the Others.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 * either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
15 */
16 package org.codelibs.fess.entity;
17
18 import java.time.LocalDateTime;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.UUID;
22 import java.util.concurrent.CopyOnWriteArrayList;
23
24 /**
25 * Represents a chat session containing conversation history.
26 *
27 * @author FessProject
28 */
29 public class ChatSession {
30
31 /** The unique session identifier. */
32 private String sessionId;
33
34 /** The user ID associated with this session. */
35 private String userId;
36
37 /** The timestamp when the session was created. */
38 private LocalDateTime createdAt;
39
40 /** The timestamp when the session was last accessed. */
41 private volatile LocalDateTime lastAccessedAt;
42
43 /** The list of messages in this session. */
44 private List<ChatMessage> messages;
45
46 /** Lock object for thread-safe message operations. */
47 private final Object messagesLock = new Object();
48
49 /**
50 * Default constructor.
51 */
52 public ChatSession() {
53 this.sessionId = UUID.randomUUID().toString();
54 this.createdAt = LocalDateTime.now();
55 this.lastAccessedAt = this.createdAt;
56 this.messages = new CopyOnWriteArrayList<>();
57 }
58
59 /**
60 * Creates a new chat session for the specified user.
61 *
62 * @param userId the user ID
63 */
64 public ChatSession(final String userId) {
65 this();
66 this.userId = userId;
67 }
68
69 /**
70 * Gets the session ID.
71 *
72 * @return the session ID
73 */
74 public String getSessionId() {
75 return sessionId;
76 }
77
78 /**
79 * Sets the session ID.
80 *
81 * @param sessionId the session ID
82 */
83 public void setSessionId(final String sessionId) {
84 this.sessionId = sessionId;
85 }
86
87 /**
88 * Gets the user ID.
89 *
90 * @return the user ID
91 */
92 public String getUserId() {
93 return userId;
94 }
95
96 /**
97 * Sets the user ID.
98 *
99 * @param userId the user ID
100 */
101 public void setUserId(final String userId) {
102 this.userId = userId;
103 }
104
105 /**
106 * Gets the creation timestamp.
107 *
108 * @return the creation timestamp
109 */
110 public LocalDateTime getCreatedAt() {
111 return createdAt;
112 }
113
114 /**
115 * Sets the creation timestamp.
116 *
117 * @param createdAt the creation timestamp
118 */
119 public void setCreatedAt(final LocalDateTime createdAt) {
120 this.createdAt = createdAt;
121 }
122
123 /**
124 * Gets the last accessed timestamp.
125 *
126 * @return the last accessed timestamp
127 */
128 public LocalDateTime getLastAccessedAt() {
129 return lastAccessedAt;
130 }
131
132 /**
133 * Sets the last accessed timestamp.
134 *
135 * @param lastAccessedAt the last accessed timestamp
136 */
137 public void setLastAccessedAt(final LocalDateTime lastAccessedAt) {
138 this.lastAccessedAt = lastAccessedAt;
139 }
140
141 /**
142 * Returns a copy of the message list in this session.
143 *
144 * @return a new list containing all messages
145 */
146 public List<ChatMessage> getMessages() {
147 synchronized (messagesLock) {
148 if (messages == null) {
149 return new ArrayList<>();
150 }
151 return new ArrayList<>(messages);
152 }
153 }
154
155 /**
156 * Sets the message list for this session.
157 *
158 * @param messages the messages to set
159 */
160 public void setMessages(final List<ChatMessage> messages) {
161 synchronized (messagesLock) {
162 if (messages == null) {
163 this.messages = null;
164 } else if (messages instanceof CopyOnWriteArrayList) {
165 this.messages = messages;
166 } else {
167 this.messages = new CopyOnWriteArrayList<>(messages);
168 }
169 }
170 }
171
172 /**
173 * Adds a message to this session and updates the last accessed timestamp.
174 *
175 * @param message the message to add
176 */
177 public void addMessage(final ChatMessage message) {
178 synchronized (messagesLock) {
179 if (messages == null) {
180 messages = new CopyOnWriteArrayList<>();
181 }
182 messages.add(message);
183 }
184 this.lastAccessedAt = LocalDateTime.now();
185 }
186
187 /**
188 * Adds a user message to this session.
189 *
190 * @param content the message content
191 */
192 public void addUserMessage(final String content) {
193 addMessage(ChatMessage.userMessage(content));
194 }
195
196 /**
197 * Adds an assistant message to this session.
198 *
199 * @param content the message content
200 */
201 public void addAssistantMessage(final String content) {
202 addMessage(ChatMessage.assistantMessage(content));
203 }
204
205 /**
206 * Updates the last accessed timestamp to the current time.
207 */
208 public void touch() {
209 this.lastAccessedAt = LocalDateTime.now();
210 }
211
212 /**
213 * Returns the number of messages in this session.
214 *
215 * @return the message count
216 */
217 public int getMessageCount() {
218 synchronized (messagesLock) {
219 return messages != null ? messages.size() : 0;
220 }
221 }
222
223 /**
224 * Clears all messages in this session and updates the last accessed timestamp.
225 */
226 public void clearMessages() {
227 synchronized (messagesLock) {
228 if (messages != null) {
229 messages.clear();
230 }
231 }
232 this.lastAccessedAt = LocalDateTime.now();
233 }
234
235 /**
236 * Trims the message history to keep only the most recent messages.
237 *
238 * @param maxMessages the maximum number of messages to retain
239 */
240 public void trimHistory(final int maxMessages) {
241 synchronized (messagesLock) {
242 if (messages != null && messages.size() > maxMessages) {
243 int start = messages.size() - maxMessages;
244 // Ensure trimmed history starts with a user message, not an assistant message
245 if (start < messages.size() && ChatMessage.ROLE_ASSISTANT.equals(messages.get(start).getRole())) {
246 start = Math.max(0, start - 1);
247 }
248 final List<ChatMessage> trimmed = new ArrayList<>(messages.subList(start, messages.size()));
249 messages.clear();
250 messages.addAll(trimmed);
251 }
252 }
253 }
254 }