001/* 002 * Copyright 2024 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet.servlet.javax; 018 019import javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.NotThreadSafe; 022import javax.servlet.ServletContext; 023import javax.servlet.http.HttpSession; 024import javax.servlet.http.HttpSessionBindingEvent; 025import javax.servlet.http.HttpSessionBindingListener; 026import javax.servlet.http.HttpSessionContext; 027import java.time.Instant; 028import java.util.Collections; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035import java.util.UUID; 036 037import static java.util.Objects.requireNonNull; 038 039/** 040 * @author <a href="https://www.revetkn.com">Mark Allen</a> 041 */ 042@NotThreadSafe 043public class SokletHttpSession implements HttpSession { 044 @Nonnull 045 private static final HttpSessionContext SHARED_HTTP_SESSION_CONTEXT; 046 047 static { 048 SHARED_HTTP_SESSION_CONTEXT = new SokletHttpSessionContext(); 049 } 050 051 @Nonnull 052 private UUID sessionId; 053 @Nonnull 054 private final Instant createdAt; 055 @Nonnull 056 private final Map<String, Object> attributes; 057 @Nonnull 058 private final ServletContext servletContext; 059 private boolean invalidated; 060 private int maxInactiveInterval; 061 062 public SokletHttpSession(@Nonnull ServletContext servletContext) { 063 requireNonNull(servletContext); 064 065 this.sessionId = UUID.randomUUID(); 066 this.createdAt = Instant.now(); 067 this.attributes = new HashMap<>(); 068 this.servletContext = servletContext; 069 this.invalidated = false; 070 this.maxInactiveInterval = 0; 071 } 072 073 public void setSessionId(@Nonnull UUID sessionId) { 074 requireNonNull(sessionId); 075 this.sessionId = sessionId; 076 } 077 078 @Nonnull 079 protected UUID getSessionId() { 080 return this.sessionId; 081 } 082 083 @Nonnull 084 protected Instant getCreatedAt() { 085 return this.createdAt; 086 } 087 088 @Nonnull 089 protected Map<String, Object> getAttributes() { 090 return this.attributes; 091 } 092 093 protected boolean isInvalidated() { 094 return this.invalidated; 095 } 096 097 protected void setInvalidated(boolean invalidated) { 098 this.invalidated = invalidated; 099 } 100 101 protected void ensureNotInvalidated() { 102 if (isInvalidated()) 103 throw new IllegalStateException("Session is invalidated"); 104 } 105 106 // Implementation of HttpSession methods below: 107 108 @Override 109 public long getCreationTime() { 110 ensureNotInvalidated(); 111 return getCreatedAt().toEpochMilli(); 112 } 113 114 @Override 115 @Nonnull 116 public String getId() { 117 return getSessionId().toString(); 118 } 119 120 @Override 121 public long getLastAccessedTime() { 122 ensureNotInvalidated(); 123 return getCreatedAt().toEpochMilli(); 124 } 125 126 @Override 127 @Nonnull 128 public ServletContext getServletContext() { 129 return this.servletContext; 130 } 131 132 @Override 133 public void setMaxInactiveInterval(int interval) { 134 this.maxInactiveInterval = interval; 135 } 136 137 @Override 138 public int getMaxInactiveInterval() { 139 return this.maxInactiveInterval; 140 } 141 142 @Override 143 @Nonnull 144 @Deprecated 145 public HttpSessionContext getSessionContext() { 146 return SHARED_HTTP_SESSION_CONTEXT; 147 } 148 149 @Override 150 @Nullable 151 public Object getAttribute(@Nullable String name) { 152 ensureNotInvalidated(); 153 return getAttributes().get(name); 154 } 155 156 @Override 157 @Nullable 158 @Deprecated 159 public Object getValue(@Nullable String name) { 160 ensureNotInvalidated(); 161 return getAttribute(name); 162 } 163 164 @Override 165 @Nonnull 166 public Enumeration<String> getAttributeNames() { 167 ensureNotInvalidated(); 168 return Collections.enumeration(getAttributes().keySet()); 169 } 170 171 @Override 172 @Nonnull 173 @Deprecated 174 public String[] getValueNames() { 175 ensureNotInvalidated(); 176 List<String> valueNames = Collections.list(getAttributeNames()); 177 return valueNames.toArray(new String[0]); 178 } 179 180 @Override 181 public void setAttribute(@Nonnull String name, 182 @Nullable Object value) { 183 requireNonNull(name); 184 185 ensureNotInvalidated(); 186 187 if (value == null) { 188 removeAttribute(name); 189 } else { 190 Object existingValue = getAttributes().get(name); 191 192 if (existingValue != null && existingValue instanceof HttpSessionBindingListener) 193 ((HttpSessionBindingListener) existingValue).valueUnbound(new HttpSessionBindingEvent(this, name, existingValue)); 194 195 getAttributes().put(name, value); 196 197 if (value instanceof HttpSessionBindingListener) 198 ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value)); 199 } 200 } 201 202 @Override 203 @Deprecated 204 public void putValue(@Nonnull String name, 205 @Nonnull Object value) { 206 requireNonNull(name); 207 requireNonNull(value); 208 209 ensureNotInvalidated(); 210 setAttribute(name, value); 211 } 212 213 @Override 214 public void removeAttribute(@Nonnull String name) { 215 requireNonNull(name); 216 217 ensureNotInvalidated(); 218 219 Object existingValue = getAttributes().get(name); 220 221 if (existingValue != null && existingValue instanceof HttpSessionBindingListener) 222 ((HttpSessionBindingListener) existingValue).valueUnbound(new HttpSessionBindingEvent(this, name, existingValue)); 223 224 getAttributes().remove(name); 225 } 226 227 @Override 228 @Deprecated 229 public void removeValue(@Nonnull String name) { 230 requireNonNull(name); 231 232 ensureNotInvalidated(); 233 removeAttribute(name); 234 } 235 236 @Override 237 public void invalidate() { 238 setInvalidated(true); 239 240 // Copy to prevent modification while iterating 241 Set<String> namesToRemove = new HashSet<>(getAttributes().keySet()); 242 243 for (String name : namesToRemove) 244 removeAttribute(name); 245 } 246 247 @Override 248 public boolean isNew() { 249 return true; 250 } 251}