001/** 002 * Copyright 2005-2018 The Kuali Foundation 003 * 004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php 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 */ 016package org.kuali.rice.krad.web.listener; 017 018import org.apache.commons.logging.Log; 019import org.apache.commons.logging.LogFactory; 020import org.kuali.rice.core.api.config.property.Config; 021import org.kuali.rice.core.api.config.property.ConfigContext; 022 023import javax.servlet.http.HttpSessionAttributeListener; 024import javax.servlet.http.HttpSessionBindingEvent; 025import java.io.ByteArrayOutputStream; 026import java.io.IOException; 027import java.io.ObjectOutputStream; 028import java.io.Serializable; 029 030/** 031 * A session listener that detects when a non-serializable attributes is added to session. 032 * 033 * @author Kuali Rice Team (rice.collab@kuali.org) 034 */ 035public class NonSerializableSessionListener implements HttpSessionAttributeListener { 036 private static final Log LOG = LogFactory.getLog(NonSerializableSessionListener.class); 037 private static final String ENABLE_SERIALIZATION_CHECK = "enableSerializationCheck"; 038 private Boolean serializationCheckEnabled; 039 040 @Override 041 public void attributeAdded(HttpSessionBindingEvent se) { 042 logSerializationViolations(se, "added"); 043 } 044 045 @Override 046 public void attributeRemoved(HttpSessionBindingEvent se) { 047 //do nothing 048 } 049 050 @Override 051 public void attributeReplaced(HttpSessionBindingEvent se) { 052 logSerializationViolations(se, "replaced"); 053 } 054 055 /** 056 * Tests and logs serialization violations in non-production environments 057 */ 058 private void logSerializationViolations(HttpSessionBindingEvent se, String action) { 059 if (!productionEnvironmentDetected() && isSerializationCheckEnabled()) { 060 checkSerialization(se, action); 061 } 062 } 063 064 /** 065 * Determines whether we are running in a production environment. Factored out for testability. 066 */ 067 private static boolean productionEnvironmentDetected() { 068 Config c = ConfigContext.getCurrentContextConfig(); 069 return c != null && c.isProductionEnvironment(); 070 } 071 072 /** 073 * Determines whether we are running in a production environment. Factored out for testability. 074 */ 075 private Boolean isSerializationCheckEnabled() { 076 if (serializationCheckEnabled == null) { 077 Config c = ConfigContext.getCurrentContextConfig(); 078 serializationCheckEnabled = c != null && c.getBooleanProperty(ENABLE_SERIALIZATION_CHECK); 079 } 080 return serializationCheckEnabled; 081 } 082 083 084 085 /** 086 * Tests whether the attribute value is serializable and logs an error if it isn't. Note, this can be expensive 087 * so we avoid it in production environments. 088 * @param se the session binding event 089 * @param action the listener event for logging purposes (added or replaced) 090 */ 091 protected void checkSerialization(final HttpSessionBindingEvent se, String action) { 092 final Object o = se.getValue(); 093 if(o != null) { 094 if (!isSerializable(o)) { 095 LOG.error("Attribute of class " + o.getClass().getName() + " with name " + se.getName() + " from source " + se.getSource().getClass().getName() + " was " + action + " to session and does not implement " + Serializable.class.getName()); 096 } else if (!canBeSerialized((Serializable) o)){ 097 LOG.error("Attribute of class " + o.getClass().getName() + " with name " + se.getName() + " from source " + se.getSource().getClass().getName() + " was " + action + " to session and cannot be Serialized"); 098 } 099 } 100 } 101 102 /** 103 * Simply tests whether the object implements the Serializable interface 104 */ 105 private static boolean isSerializable(Object o) { 106 return o instanceof Serializable; 107 } 108 109 /** 110 * Performs an expensive test of serializability by attempting to serialize the object graph 111 */ 112 private static boolean canBeSerialized(Serializable o) { 113 ByteArrayOutputStream baos = null; 114 ObjectOutputStream out = null; 115 try { 116 baos = new ByteArrayOutputStream(512); 117 out = new ObjectOutputStream(baos); 118 out.writeObject((Serializable) o); 119 return true; 120 } catch (IOException e) { 121 LOG.warn("error serializing object" , e); 122 } finally { 123 try { 124 if (baos != null) { 125 try { 126 baos.close(); 127 } catch (IOException e) { 128 LOG.warn("error closing stream" , e); 129 } 130 } 131 } finally { 132 if (out != null) { 133 try { 134 out.close(); 135 } catch (IOException e) { 136 LOG.warn("error closing stream" , e); 137 } 138 } 139 } 140 } 141 142 return false; 143 } 144}