/hazelcast-wm/src/main/java/com/hazelcast/web/WebFilter.java

https://bitbucket.org/gabral6_gmailcom/hazelcast · Java · 651 lines · 529 code · 96 blank · 26 comment · 133 complexity · f27774a12fe115a5600a044eb19c8291 MD5 · raw file

  1. /*
  2. * Copyright (c) 2008-2013, Hazelcast, Inc. All Rights Reserved.
  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, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.hazelcast.web;
  17. import com.hazelcast.config.Config;
  18. import com.hazelcast.config.MapConfig;
  19. import com.hazelcast.core.*;
  20. import com.hazelcast.impl.ThreadContext;
  21. import com.hazelcast.logging.ILogger;
  22. import com.hazelcast.logging.Logger;
  23. import com.hazelcast.nio.Data;
  24. import com.hazelcast.util.Clock;
  25. import javax.servlet.*;
  26. import javax.servlet.http.*;
  27. import java.io.IOException;
  28. import java.io.NotSerializableException;
  29. import java.io.Serializable;
  30. import java.util.*;
  31. import java.util.concurrent.ConcurrentHashMap;
  32. import java.util.concurrent.ConcurrentMap;
  33. import java.util.logging.Level;
  34. import static com.hazelcast.web.HazelcastInstanceLoader.*;
  35. public class WebFilter implements Filter {
  36. private static final ILogger logger = Logger.getLogger(WebFilter.class.getName());
  37. private static final String HAZELCAST_REQUEST = "*hazelcast-request";
  38. private static final String HAZELCAST_SESSION_COOKIE_NAME = "hazelcast.sessionId";
  39. private static final ConcurrentMap<String, String> mapOriginalSessions = new ConcurrentHashMap<String, String>(1000);
  40. private static final ConcurrentMap<String, HazelcastHttpSession> mapSessions = new ConcurrentHashMap<String, HazelcastHttpSession>(1000);
  41. private HazelcastInstance hazelcastInstance;
  42. private String clusterMapName = "none";
  43. private String sessionCookieName = HAZELCAST_SESSION_COOKIE_NAME;
  44. private String sessionCookieDomain = null;
  45. private boolean sessionCookieSecure = false;
  46. private boolean sessionCookieHttpOnly = false;
  47. private boolean stickySession = true;
  48. private boolean debug = false;
  49. private boolean shutdownOnDestroy = true;
  50. private Properties properties;
  51. protected ServletContext servletContext;
  52. protected FilterConfig filterConfig;
  53. public WebFilter() {
  54. }
  55. public WebFilter(Properties properties) {
  56. this();
  57. this.properties = properties;
  58. }
  59. public final void init(final FilterConfig config) throws ServletException {
  60. filterConfig = config;
  61. servletContext = config.getServletContext();
  62. initInstance();
  63. String debugParam = getParam("debug");
  64. if (debugParam != null) {
  65. debug = Boolean.valueOf(debugParam);
  66. }
  67. String mapName = getParam("map-name");
  68. if (mapName != null) {
  69. clusterMapName = mapName;
  70. } else {
  71. clusterMapName = "_web_" + servletContext.getServletContextName();
  72. }
  73. Config hzConfig = hazelcastInstance.getConfig();
  74. String sessionTTL = getParam("session-ttl-seconds");
  75. if (sessionTTL != null) {
  76. MapConfig mapConfig = new MapConfig(clusterMapName);
  77. mapConfig.setTimeToLiveSeconds(Integer.valueOf(sessionTTL));
  78. hzConfig.addMapConfig(mapConfig);
  79. }
  80. String cookieName = getParam("cookie-name");
  81. if (cookieName != null) {
  82. sessionCookieName = cookieName;
  83. }
  84. String cookieDomain = getParam("cookie-domain");
  85. if (cookieDomain != null) {
  86. sessionCookieDomain = cookieDomain;
  87. }
  88. String cookieSecure = getParam("cookie-secure");
  89. if (cookieSecure != null) {
  90. sessionCookieSecure = Boolean.valueOf(cookieSecure);
  91. }
  92. String cookieHttpOnly = getParam("cookie-http-only");
  93. if (cookieHttpOnly != null) {
  94. sessionCookieHttpOnly = Boolean.valueOf(cookieHttpOnly);
  95. }
  96. String stickySessionParam = getParam("sticky-session");
  97. if (stickySessionParam != null) {
  98. stickySession = Boolean.valueOf(stickySessionParam);
  99. }
  100. String shutdownOnDestroyParam = getParam("shutdown-on-destroy");
  101. if (shutdownOnDestroyParam != null) {
  102. shutdownOnDestroy = Boolean.valueOf(shutdownOnDestroyParam);
  103. }
  104. if (!stickySession) {
  105. getClusterMap().addEntryListener(new EntryListener() {
  106. public void entryAdded(EntryEvent entryEvent) {
  107. }
  108. public void entryRemoved(EntryEvent entryEvent) {
  109. if (entryEvent.getMember() == null || // client events has no owner member
  110. !entryEvent.getMember().localMember()) {
  111. removeSessionLocally((String) entryEvent.getKey());
  112. }
  113. }
  114. public void entryUpdated(EntryEvent entryEvent) {
  115. if (entryEvent.getMember() == null || // client events has no owner member
  116. !entryEvent.getMember().localMember()) {
  117. markSessionDirty((String) entryEvent.getKey());
  118. }
  119. }
  120. public void entryEvicted(EntryEvent entryEvent) {
  121. entryRemoved(entryEvent);
  122. }
  123. }, false);
  124. }
  125. log("sticky:" + stickySession + ", debug: " + debug + ", shutdown-on-destroy: " + shutdownOnDestroy
  126. + ", map-name: " + clusterMapName);
  127. }
  128. private void initInstance() throws ServletException {
  129. if (properties == null) {
  130. properties = new Properties();
  131. }
  132. setProperty(CONFIG_LOCATION);
  133. setProperty(INSTANCE_NAME);
  134. setProperty(USE_CLIENT);
  135. setProperty(CLIENT_CONFIG_LOCATION);
  136. hazelcastInstance = (HazelcastInstance) getInstance(properties);
  137. }
  138. private void setProperty(String propertyName) {
  139. String value = getParam(propertyName);
  140. if (value != null) {
  141. properties.setProperty(propertyName, value);
  142. }
  143. }
  144. private void removeSessionLocally(String sessionId) {
  145. HazelcastHttpSession hazelSession = mapSessions.remove(sessionId);
  146. if (hazelSession != null) {
  147. mapOriginalSessions.remove(hazelSession.originalSession.getId());
  148. log("Destroying session locally " + hazelSession);
  149. hazelSession.destroy();
  150. }
  151. }
  152. private void markSessionDirty(String sessionId) {
  153. HazelcastHttpSession hazelSession = mapSessions.get(sessionId);
  154. if (hazelSession != null) {
  155. hazelSession.setDirty(true);
  156. }
  157. }
  158. static void destroyOriginalSession(HttpSession originalSession) {
  159. String hazelcastSessionId = mapOriginalSessions.remove(originalSession.getId());
  160. if (hazelcastSessionId != null) {
  161. HazelcastHttpSession hazelSession = mapSessions.remove(hazelcastSessionId);
  162. if (hazelSession != null) {
  163. hazelSession.webFilter.destroySession(hazelSession, false);
  164. }
  165. }
  166. }
  167. protected void log(final Object obj) {
  168. Level level = Level.FINEST;
  169. if (debug) {
  170. level = Level.INFO;
  171. }
  172. logger.log(level, obj.toString());
  173. }
  174. private HazelcastHttpSession createNewSession(RequestWrapper requestWrapper, String existingSessionId) {
  175. String id = existingSessionId != null ? existingSessionId : generateSessionId();
  176. if (requestWrapper.getOriginalSession(false) != null) {
  177. log("Original session exists!!!");
  178. }
  179. HttpSession originalSession = requestWrapper.getOriginalSession(true);
  180. HazelcastHttpSession hazelcastSession = new HazelcastHttpSession(WebFilter.this, id, originalSession);
  181. mapSessions.put(hazelcastSession.getId(), hazelcastSession);
  182. String oldHazelcastSessionId = mapOriginalSessions.put(originalSession.getId(), hazelcastSession.getId());
  183. if (oldHazelcastSessionId != null) {
  184. log("!!! Overriding an existing hazelcastSessionId " + oldHazelcastSessionId);
  185. }
  186. log("Created new session with id: " + id);
  187. log(mapSessions.size() + " is sessions.size and originalSessions.size: " + mapOriginalSessions.size());
  188. addSessionCookie(requestWrapper, id);
  189. return hazelcastSession;
  190. }
  191. /**
  192. * Destroys a session, determining if it should be destroyed clusterwide automatically or via expiry.
  193. *
  194. * @param session The session to be destroyed
  195. * @param removeGlobalSession boolean value - true if the session should be destroyed irrespective of active time
  196. */
  197. private void destroySession(HazelcastHttpSession session, boolean removeGlobalSession) {
  198. log("Destroying local session: " + session.getId());
  199. mapSessions.remove(session.getId());
  200. mapOriginalSessions.remove(session.originalSession.getId());
  201. session.destroy();
  202. if (removeGlobalSession) {
  203. log("Destroying cluster session: " + session.getId() + " => Ignore-timeout: true");
  204. getClusterMap().remove(session.getId());
  205. }
  206. }
  207. private IMap getClusterMap() {
  208. return hazelcastInstance.getMap(clusterMapName);
  209. }
  210. private HazelcastHttpSession getSessionWithId(final String sessionId) {
  211. HazelcastHttpSession session = mapSessions.get(sessionId);
  212. if (session != null && !session.isValid()) {
  213. session = null;
  214. destroySession(session, true);
  215. }
  216. return session;
  217. }
  218. private class RequestWrapper extends HttpServletRequestWrapper {
  219. HazelcastHttpSession hazelcastSession = null;
  220. final ResponseWrapper res;
  221. String requestedSessionId;
  222. public RequestWrapper(final HttpServletRequest req,
  223. final ResponseWrapper res) {
  224. super(req);
  225. this.res = res;
  226. req.setAttribute(HAZELCAST_REQUEST, this);
  227. }
  228. public void setHazelcastSession(HazelcastHttpSession hazelcastSession, String requestedSessionId) {
  229. this.hazelcastSession = hazelcastSession;
  230. this.requestedSessionId = requestedSessionId;
  231. }
  232. HttpSession getOriginalSession(boolean create) {
  233. return super.getSession(create);
  234. }
  235. @Override
  236. public RequestDispatcher getRequestDispatcher(final String path) {
  237. final ServletRequest original = getRequest();
  238. return new RequestDispatcher() {
  239. public void forward(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  240. // original.getRequestDispatcher(path).forward(original, servletResponse);
  241. original.getRequestDispatcher(path).forward(RequestWrapper.this, servletResponse);
  242. }
  243. public void include(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  244. // original.getRequestDispatcher(path).include(original, servletResponse);
  245. original.getRequestDispatcher(path).include(RequestWrapper.this, servletResponse);
  246. }
  247. };
  248. }
  249. public String fetchHazelcastSessionId() {
  250. if (requestedSessionId != null) {
  251. return requestedSessionId;
  252. }
  253. requestedSessionId = getSessionCookie(this);
  254. return requestedSessionId;
  255. }
  256. @Override
  257. public HttpSession getSession() {
  258. return getSession(true);
  259. }
  260. @Override
  261. public HazelcastHttpSession getSession(final boolean create) {
  262. if (hazelcastSession != null && !hazelcastSession.isValid()) {
  263. log("Session is invalid!");
  264. destroySession(hazelcastSession, true);
  265. hazelcastSession = null;
  266. }
  267. if (hazelcastSession == null) {
  268. HttpSession originalSession = getOriginalSession(false);
  269. if (originalSession != null) {
  270. String hazelcastSessionId = mapOriginalSessions.get(originalSession.getId());
  271. if (hazelcastSessionId != null) {
  272. hazelcastSession = mapSessions.get(hazelcastSessionId);
  273. }
  274. if (hazelcastSession == null) {
  275. mapOriginalSessions.remove(originalSession.getId());
  276. originalSession.invalidate();
  277. } else if (hazelcastSession.isDirty()) {
  278. hazelcastSession = null;
  279. }
  280. }
  281. }
  282. if (hazelcastSession != null)
  283. return hazelcastSession;
  284. final String requestedSessionId = fetchHazelcastSessionId();
  285. if (requestedSessionId != null) {
  286. hazelcastSession = getSessionWithId(requestedSessionId);
  287. if (hazelcastSession == null) {
  288. final Map mapSession = (Map) getClusterMap().get(requestedSessionId);
  289. if (mapSession != null) {
  290. // we already have the session in the cluster
  291. // loading it...
  292. hazelcastSession = createNewSession(RequestWrapper.this, requestedSessionId);
  293. overrideSession(hazelcastSession, mapSession);
  294. }
  295. }
  296. }
  297. if (hazelcastSession == null && create) {
  298. hazelcastSession = createNewSession(RequestWrapper.this, null);
  299. } else if (hazelcastSession != null && !stickySession && requestedSessionId != null && hazelcastSession.isDirty()) {
  300. log(requestedSessionId + " is dirty reloading.");
  301. final Map mapSession = (Map) getClusterMap().get(requestedSessionId);
  302. overrideSession(hazelcastSession, mapSession);
  303. }
  304. return hazelcastSession;
  305. }
  306. private void overrideSession(HazelcastHttpSession session, Map mapSession) {
  307. if (session == null || mapSession == null) return;
  308. final Enumeration<String> atts = session.getAttributeNames();
  309. while (atts.hasMoreElements()) {
  310. session.removeAttribute(atts.nextElement());
  311. }
  312. Map mapData = null;
  313. final Set<Map.Entry> entries = mapSession.entrySet();
  314. for (final Map.Entry entry : entries) {
  315. session.setAttribute((String) entry.getKey(), entry.getValue());
  316. if (mapData == null) {
  317. mapData = new HashMap<String, Object>();
  318. }
  319. mapData.put(entry.getKey(), entry.getValue());
  320. }
  321. session.sessionChanged(session.writeObject(mapData));
  322. session.setDirty(false);
  323. }
  324. } // END of RequestWrapper
  325. private class ResponseWrapper extends HttpServletResponseWrapper {
  326. RequestWrapper req = null;
  327. public ResponseWrapper(final HttpServletResponse original) {
  328. super(original);
  329. }
  330. public void setRequest(final RequestWrapper req) {
  331. this.req = req;
  332. }
  333. }
  334. private class HazelcastHttpSession implements HttpSession {
  335. private Data currentSessionData = null;
  336. volatile boolean valid = true;
  337. volatile boolean dirty = false;
  338. final String id;
  339. final HttpSession originalSession;
  340. final WebFilter webFilter;
  341. private final AtomicNumber timestamp;
  342. public HazelcastHttpSession(WebFilter webFilter, final String sessionId, HttpSession originalSession) {
  343. this.webFilter = webFilter;
  344. this.id = sessionId;
  345. this.originalSession = originalSession;
  346. timestamp = hazelcastInstance.getAtomicNumber(clusterMapName + "_" + id);
  347. }
  348. public Object getAttribute(final String name) {
  349. return originalSession.getAttribute(name);
  350. }
  351. public Enumeration getAttributeNames() {
  352. return originalSession.getAttributeNames();
  353. }
  354. public String getId() {
  355. return id;
  356. }
  357. public ServletContext getServletContext() {
  358. return servletContext;
  359. }
  360. public HttpSessionContext getSessionContext() {
  361. return originalSession.getSessionContext();
  362. }
  363. public Object getValue(final String name) {
  364. return getAttribute(name);
  365. }
  366. public String[] getValueNames() {
  367. return originalSession.getValueNames();
  368. }
  369. public boolean isDirty() {
  370. return dirty;
  371. }
  372. public void setDirty(boolean dirty) {
  373. this.dirty = dirty;
  374. }
  375. public void invalidate() {
  376. originalSession.invalidate();
  377. destroySession(this, true);
  378. }
  379. public boolean isNew() {
  380. return originalSession.isNew();
  381. }
  382. public void putValue(final String name, final Object value) {
  383. setAttribute(name, value);
  384. }
  385. public void removeAttribute(final String name) {
  386. originalSession.removeAttribute(name);
  387. }
  388. public void setAttribute(final String name, final Object value) {
  389. if (value != null && !(value instanceof Serializable)) {
  390. throw new IllegalArgumentException(new NotSerializableException(value.getClass().getName()));
  391. }
  392. originalSession.setAttribute(name, value);
  393. }
  394. public void removeValue(final String name) {
  395. removeAttribute(name);
  396. }
  397. public boolean sessionChanged(final Data data) {
  398. try {
  399. if (data == null) {
  400. return currentSessionData != null;
  401. }
  402. return currentSessionData == null || !data.equals(currentSessionData);
  403. } finally {
  404. currentSessionData = data;
  405. }
  406. }
  407. public long getCreationTime() {
  408. return originalSession.getCreationTime();
  409. }
  410. public long getLastAccessedTime() {
  411. return originalSession.getLastAccessedTime();
  412. }
  413. public int getMaxInactiveInterval() {
  414. return originalSession.getMaxInactiveInterval();
  415. }
  416. public void setMaxInactiveInterval(int maxInactiveSeconds) {
  417. originalSession.setMaxInactiveInterval(maxInactiveSeconds);
  418. }
  419. public synchronized Data writeObject(final Object obj) {
  420. if (obj == null)
  421. return null;
  422. return ThreadContext.get().toData(obj);
  423. }
  424. void destroy() {
  425. valid = false;
  426. timestamp.destroy();
  427. }
  428. public boolean isValid() {
  429. return valid;
  430. }
  431. void setAccessed() {
  432. timestamp.set(Clock.currentTimeMillis());
  433. }
  434. long getLastAccessed() {
  435. return hazelcastInstance.getLifecycleService().isRunning()
  436. ? timestamp.get() : 0L;
  437. }
  438. }// END of HazelSession
  439. private static synchronized String generateSessionId() {
  440. final String id = UUID.randomUUID().toString();
  441. final StringBuilder sb = new StringBuilder("HZ");
  442. final char[] chars = id.toCharArray();
  443. for (final char c : chars) {
  444. if (c != '-') {
  445. if (Character.isLetter(c)) {
  446. sb.append(Character.toUpperCase(c));
  447. } else
  448. sb.append(c);
  449. }
  450. }
  451. return sb.toString();
  452. }
  453. private void addSessionCookie(final RequestWrapper req, final String sessionId) {
  454. final Cookie sessionCookie = new Cookie(sessionCookieName, sessionId);
  455. String path = req.getContextPath();
  456. if ("".equals(path)) {
  457. path = "/";
  458. }
  459. sessionCookie.setPath(path);
  460. sessionCookie.setMaxAge(-1);
  461. if (sessionCookieDomain != null) {
  462. sessionCookie.setDomain(sessionCookieDomain);
  463. }
  464. try {
  465. sessionCookie.setHttpOnly(sessionCookieHttpOnly);
  466. } catch (NoSuchMethodError e) {
  467. // must be servlet spec before 3.0, don't worry about it!
  468. }
  469. sessionCookie.setSecure(sessionCookieSecure);
  470. req.res.addCookie(sessionCookie);
  471. }
  472. private String getSessionCookie(final RequestWrapper req) {
  473. final Cookie[] cookies = req.getCookies();
  474. if (cookies != null) {
  475. for (final Cookie cookie : cookies) {
  476. final String name = cookie.getName();
  477. final String value = cookie.getValue();
  478. if (name.equalsIgnoreCase(sessionCookieName)) {
  479. return value;
  480. }
  481. }
  482. }
  483. return null;
  484. }
  485. public final void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain)
  486. throws IOException, ServletException {
  487. if (!(req instanceof HttpServletRequest)) {
  488. chain.doFilter(req, res);
  489. } else {
  490. if (req instanceof RequestWrapper) {
  491. log("Request is instance of RequestWrapper! Continue...");
  492. chain.doFilter(req, res);
  493. return;
  494. }
  495. HttpServletRequest httpReq = (HttpServletRequest) req;
  496. RequestWrapper existingReq = (RequestWrapper) req.getAttribute(HAZELCAST_REQUEST);
  497. final ResponseWrapper resWrapper = new ResponseWrapper((HttpServletResponse) res);
  498. final RequestWrapper reqWrapper = new RequestWrapper(httpReq, resWrapper);
  499. resWrapper.setRequest(reqWrapper);
  500. if (existingReq != null) {
  501. reqWrapper.setHazelcastSession(existingReq.hazelcastSession, existingReq.requestedSessionId);
  502. }
  503. req = null;
  504. res = null;
  505. httpReq = null;
  506. chain.doFilter(reqWrapper, resWrapper);
  507. if (existingReq != null) return;
  508. req = null; // for easy debugging. reqWrapper should be used
  509. HazelcastHttpSession session = reqWrapper.getSession(false);
  510. if (session != null && session.isValid()) {
  511. session.setAccessed();
  512. final Enumeration<String> attNames = session.getAttributeNames();
  513. Map mapData = null;
  514. while (attNames.hasMoreElements()) {
  515. final String attName = attNames.nextElement();
  516. final Object value = session.getAttribute(attName);
  517. if (mapData == null) {
  518. mapData = new HashMap<String, Object>();
  519. }
  520. mapData.put(attName, value);
  521. }
  522. Data data = session.writeObject(mapData);
  523. boolean sessionChanged = session.sessionChanged(data);
  524. if (sessionChanged) {
  525. if (data == null) {
  526. mapData = new HashMap<String, Object>();
  527. data = session.writeObject(mapData);
  528. }
  529. log("PUTTING SESSION " + session.getId());
  530. getClusterMap().put(session.getId(), data);
  531. }
  532. }
  533. }
  534. }
  535. public final void destroy() {
  536. mapSessions.clear();
  537. mapOriginalSessions.clear();
  538. shutdownInstance();
  539. }
  540. protected HazelcastInstance getInstance(Properties properties) throws ServletException {
  541. return HazelcastInstanceLoader.createInstance(filterConfig, properties);
  542. }
  543. protected void shutdownInstance() {
  544. if (shutdownOnDestroy && hazelcastInstance != null) {
  545. hazelcastInstance.getLifecycleService().shutdown();
  546. }
  547. }
  548. private String getParam(String name) {
  549. if (properties != null && properties.containsKey(name)) {
  550. return properties.getProperty(name);
  551. } else {
  552. return filterConfig.getInitParameter(name);
  553. }
  554. }
  555. }// END of WebFilter