/** class taken from Apache JCI */ public final class ResourceStoreClassLoader extends ClassLoader { private static final Logger LOG = LoggerFactory.getLogger(ResourceStoreClassLoader.class); private final ResourceStore[] stores; public ResourceStoreClassLoader(final ClassLoader pParent, final ResourceStore[] pStores) { super(pParent); stores = new ResourceStore[pStores.length]; System.arraycopy(pStores, 0, stores, 0, stores.length); } private Class fastFindClass(final String name) { if (stores != null) { String fileName = name.replace('.', '/') + ".class"; for (final ResourceStore store : stores) { final byte[] clazzBytes = store.read(fileName); if (clazzBytes != null) { return defineClass(name, clazzBytes, 0, clazzBytes.length); } } } return null; } protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class clazz = findLoadedClass(name); if (clazz == null) { clazz = fastFindClass(name); if (clazz == null) { final ClassLoader parent = getParent(); if (parent != null) { clazz = parent.loadClass(name); } else { throw new ClassNotFoundException(name); } } } if (resolve) { resolveClass(clazz); } return clazz; } protected Class findClass(final String name) throws ClassNotFoundException { final Class clazz = fastFindClass(name); if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; } }
/** 生成基于自定义的附件文件对象的显示和下载链接 示例:<s2:file value="r2File" label="关联附件"/> */ public class S2FileTag extends ComponentTagSupport { private static final Logger LOG = LoggerFactory.getLogger(S2FileTag.class); /** Bootstrap样式label属性定义 */ private String label; /** 返回附件对象实例 @see File.FileDef */ private String value; public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new File(stack); } protected void populateParams() { super.populateParams(); File uiBean = ((File) component); uiBean.setValue(value); } public int doEndTag() throws JspException { JspWriter writer = pageContext.getOut(); if (label != null) { try { writer.write("<div class='control-group'><label class='control-label'>"); writer.write(label); writer.write("</label><div class='controls'>"); } catch (IOException e) { if (LOG.isInfoEnabled()) { LOG.info("Could not print out value '" + label + "'", e); } } } component.end(writer, getBody()); if (label != null) { try { writer.write("</div></div>"); } catch (IOException e) { if (LOG.isInfoEnabled()) { LOG.info("Could not print out value '" + label + "'", e); } } } component = null; return EVAL_PAGE; } public void setLabel(String label) { this.label = label; } public void setValue(String value) { this.value = value; } }
/** Read resources from a jar file */ public class JarResourceStore implements ResourceStore { private static final Logger LOG = LoggerFactory.getLogger(JarResourceStore.class); private final File file; public JarResourceStore(File file) { this.file = file; } public void write(String pResourceName, byte[] pResourceData) {} public byte[] read(String pResourceName) { InputStream in = null; try { ZipFile jarFile = new ZipFile(file); ZipEntry entry = jarFile.getEntry(pResourceName); // read into byte array ByteArrayOutputStream out = new ByteArrayOutputStream(); in = jarFile.getInputStream(entry); copy(in, out); return out.toByteArray(); } catch (Exception e) { if (LOG.isDebugEnabled()) LOG.debug("Unable to read file [#0] from [#1]", e, pResourceName, file.getName()); return null; } finally { closeQuietly(in); } } public static long copy(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[1024 * 4]; long count = 0; int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } private void closeQuietly(InputStream is) { try { if (is != null) is.close(); } catch (IOException e) { if (LOG.isErrorEnabled()) LOG.error("Unable to close input stream", e); } } }
private void initLogging(HostConfig filterConfig) { String factoryName = filterConfig.getInitParameter("loggerFactory"); if (factoryName != null) { try { Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass()); LoggerFactory fac = (LoggerFactory) cls.newInstance(); LoggerFactory.setLoggerFactory(fac); } catch (InstantiationException e) { System.err.println( "Unable to instantiate logger factory: " + factoryName + ", using default"); e.printStackTrace(); } catch (IllegalAccessException e) { System.err.println("Unable to access logger factory: " + factoryName + ", using default"); e.printStackTrace(); } catch (ClassNotFoundException e) { System.err.println( "Unable to locate logger factory class: " + factoryName + ", using default"); e.printStackTrace(); } } log = LoggerFactory.getLogger(FilterDispatcher.class); }
public class ChatAuthenticationInterceptor implements Interceptor { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(ChatAuthenticationInterceptor.class); public static final String USER_SESSION_KEY = "chatUserSessionKey"; public void destroy() {} public void init() {} public String intercept(ActionInvocation invocation) throws Exception { LOG.debug("Authenticating chat user"); SessionMap session = (SessionMap) ActionContext.getContext().get(ActionContext.SESSION); User user = (User) session.get(USER_SESSION_KEY); if (user == null) { return Action.LOGIN; } return invocation.invoke(); } }
/** * Serializes an object into JavaScript Object Notation (JSON). If cyclic references are detected * they will be nulled out. */ public class JSONWriter { private static final Logger LOG = LoggerFactory.getLogger(JSONWriter.class); /** By default, enums are serialised as name=value pairs */ public static final boolean ENUM_AS_BEAN_DEFAULT = false; private static char[] hex = "0123456789ABCDEF".toCharArray(); private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE_IGNORE_HIERARCHY = new ConcurrentHashMap<Class<?>, BeanInfo>(); private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE = new ConcurrentHashMap<Class<?>, BeanInfo>(); private StringBuilder buf = new StringBuilder(); private Stack<Object> stack = new Stack<Object>(); private boolean ignoreHierarchy = true; private Object root; private boolean buildExpr = true; private String exprStack = ""; private Collection<Pattern> excludeProperties; private Collection<Pattern> includeProperties; private DateFormat formatter; private boolean enumAsBean = ENUM_AS_BEAN_DEFAULT; private boolean excludeNullProperties; /** * @param object Object to be serialized into JSON * @return JSON string for object * @throws JSONException */ public String write(Object object) throws JSONException { return this.write(object, null, null, false); } /** * @param object Object to be serialized into JSON * @return JSON string for object * @throws JSONException */ public String write( Object object, Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties, boolean excludeNullProperties) throws JSONException { this.excludeNullProperties = excludeNullProperties; this.buf.setLength(0); this.root = object; this.exprStack = ""; this.buildExpr = ((excludeProperties != null) && !excludeProperties.isEmpty()) || ((includeProperties != null) && !includeProperties.isEmpty()); this.excludeProperties = excludeProperties; this.includeProperties = includeProperties; this.value(object, null); return this.buf.toString(); } /** Detect cyclic references */ protected void value(Object object, Method method) throws JSONException { if (object == null) { this.add("null"); return; } if (this.stack.contains(object)) { Class clazz = object.getClass(); // cyclic reference if (clazz.isPrimitive() || clazz.equals(String.class)) { this.process(object, method); } else { if (LOG.isDebugEnabled()) { LOG.debug("Cyclic reference detected on " + object); } this.add("null"); } return; } this.process(object, method); } /** Serialize object into json */ protected void process(Object object, Method method) throws JSONException { this.stack.push(object); if (object instanceof Class) { this.string(object); } else if (object instanceof Boolean) { this.bool((Boolean) object); } else if (object instanceof Number) { this.add(object); } else if (object instanceof String) { this.string(object); } else if (object instanceof Character) { this.string(object); } else if (object instanceof Map) { this.map((Map) object, method); } else if (object.getClass().isArray()) { this.array(object, method); } else if (object instanceof Iterable) { this.array(((Iterable) object).iterator(), method); } else if (object instanceof Date) { this.date((Date) object, method); } else if (object instanceof Calendar) { this.date(((Calendar) object).getTime(), method); } else if (object instanceof Locale) { this.string(object); } else if (object instanceof Enum) { this.enumeration((Enum) object); } else { processCustom(object, method); } this.stack.pop(); } /** Serialize custom object into json */ protected void processCustom(Object object, Method method) throws JSONException { this.bean(object); } /** Instrospect bean and serialize its properties */ protected void bean(Object object) throws JSONException { this.add("{"); BeanInfo info; try { Class clazz = object.getClass(); info = ((object == this.root) && this.ignoreHierarchy) ? getBeanInfoIgnoreHierarchy(clazz) : getBeanInfo(clazz); PropertyDescriptor[] props = info.getPropertyDescriptors(); boolean hasData = false; for (PropertyDescriptor prop : props) { String name = prop.getName(); Method accessor = prop.getReadMethod(); Method baseAccessor = findBaseAccessor(clazz, accessor); if (baseAccessor != null) { if (baseAccessor.isAnnotationPresent(JSON.class)) { JSONAnnotationFinder jsonFinder = new JSONAnnotationFinder(baseAccessor).invoke(); if (!jsonFinder.shouldSerialize()) continue; if (jsonFinder.getName() != null) { name = jsonFinder.getName(); } } // ignore "class" and others if (this.shouldExcludeProperty(prop)) { continue; } String expr = null; if (this.buildExpr) { expr = this.expandExpr(name); if (this.shouldExcludeProperty(expr)) { continue; } expr = this.setExprStack(expr); } Object value = accessor.invoke(object); if (baseAccessor.isAnnotationPresent(JSONFieldBridge.class)) { value = getBridgedValue(baseAccessor, value); } boolean propertyPrinted = this.add(name, value, accessor, hasData); hasData = hasData || propertyPrinted; if (this.buildExpr) { this.setExprStack(expr); } } } // special-case handling for an Enumeration - include the name() as // a property */ if (object instanceof Enum) { Object value = ((Enum) object).name(); this.add("_name", value, object.getClass().getMethod("name"), hasData); } } catch (Exception e) { throw new JSONException(e); } this.add("}"); } protected BeanInfo getBeanInfoIgnoreHierarchy(final Class<?> clazz) throws IntrospectionException { BeanInfo beanInfo = BEAN_INFO_CACHE_IGNORE_HIERARCHY.get(clazz); if (beanInfo != null) { return beanInfo; } beanInfo = Introspector.getBeanInfo(clazz, clazz.getSuperclass()); BEAN_INFO_CACHE_IGNORE_HIERARCHY.put(clazz, beanInfo); return beanInfo; } protected BeanInfo getBeanInfo(final Class<?> clazz) throws IntrospectionException { BeanInfo beanInfo = BEAN_INFO_CACHE.get(clazz); if (beanInfo != null) { return beanInfo; } beanInfo = Introspector.getBeanInfo(clazz); BEAN_INFO_CACHE.put(clazz, beanInfo); return beanInfo; } protected Object getBridgedValue(Method baseAccessor, Object value) throws InstantiationException, IllegalAccessException { JSONFieldBridge fieldBridgeAnn = baseAccessor.getAnnotation(JSONFieldBridge.class); if (fieldBridgeAnn != null) { Class impl = fieldBridgeAnn.impl(); FieldBridge instance = (FieldBridge) impl.newInstance(); if (fieldBridgeAnn.params().length > 0 && ParameterizedBridge.class.isAssignableFrom(impl)) { Map<String, String> params = new HashMap<String, String>(fieldBridgeAnn.params().length); for (JSONParameter param : fieldBridgeAnn.params()) { params.put(param.name(), param.value()); } ((ParameterizedBridge) instance).setParameterValues(params); } value = instance.objectToString(value); } return value; } protected Method findBaseAccessor(Class clazz, Method accessor) { Method baseAccessor = null; if (clazz.getName().contains("$$EnhancerByCGLIB$$")) { try { baseAccessor = Thread.currentThread() .getContextClassLoader() .loadClass(clazz.getName().substring(0, clazz.getName().indexOf("$$"))) .getMethod(accessor.getName(), accessor.getParameterTypes()); } catch (Exception ex) { LOG.debug(ex.getMessage(), ex); } } else if (clazz.getName().contains("$$_javassist")) { try { baseAccessor = Class.forName(clazz.getName().substring(0, clazz.getName().indexOf("_$$"))) .getMethod(accessor.getName(), accessor.getParameterTypes()); } catch (Exception ex) { LOG.debug(ex.getMessage(), ex); } // in hibernate4.3.7,because javassist3.18.1's class name generate rule is '_$$_jvst'+... } else if (clazz.getName().contains("$$_jvst")) { try { baseAccessor = Class.forName(clazz.getName().substring(0, clazz.getName().indexOf("_$$"))) .getMethod(accessor.getName(), accessor.getParameterTypes()); } catch (Exception ex) { LOG.debug(ex.getMessage(), ex); } } else { return accessor; } return baseAccessor; } /** * Instrospect an Enum and serialize it as a name/value pair or as a bean including all its own * properties */ protected void enumeration(Enum enumeration) throws JSONException { if (enumAsBean) { this.bean(enumeration); } else { this.string(enumeration.name()); } } protected boolean shouldExcludeProperty(PropertyDescriptor prop) throws SecurityException, NoSuchFieldException { String name = prop.getName(); return name.equals("class") || name.equals("declaringClass") || name.equals("cachedSuperClass") || name.equals("metaClass"); } protected String expandExpr(int i) { return this.exprStack + "[" + i + "]"; } protected String expandExpr(String property) { if (this.exprStack.length() == 0) { return property; } return this.exprStack + "." + property; } protected String setExprStack(String expr) { String s = this.exprStack; this.exprStack = expr; return s; } protected boolean shouldExcludeProperty(String expr) { if (this.excludeProperties != null) { for (Pattern pattern : this.excludeProperties) { if (pattern.matcher(expr).matches()) { if (LOG.isDebugEnabled()) { LOG.debug("Ignoring property because of exclude rule: " + expr); } return true; } } } if (this.includeProperties != null) { for (Pattern pattern : this.includeProperties) { if (pattern.matcher(expr).matches()) { return false; } } if (LOG.isDebugEnabled()) { LOG.debug("Ignoring property because of include rule: " + expr); } return true; } return false; } /** Add name/value pair to buffer */ protected boolean add(String name, Object value, Method method, boolean hasData) throws JSONException { if (excludeNullProperties && value == null) { return false; } if (hasData) { this.add(','); } this.add('"'); this.add(name); this.add("\":"); this.value(value, method); return true; } /** Add map to buffer */ protected void map(Map map, Method method) throws JSONException { this.add("{"); Iterator it = map.entrySet().iterator(); boolean warnedNonString = false; // one report per map boolean hasData = false; while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); if (excludeNullProperties && entry.getValue() == null) { continue; } Object key = entry.getKey(); if (key == null) { LOG.error("Cannot build expression for null key in #0", exprStack); continue; } String expr = null; if (this.buildExpr) { expr = this.expandExpr(key.toString()); if (this.shouldExcludeProperty(expr)) { continue; } expr = this.setExprStack(expr); } if (hasData) { this.add(','); } hasData = true; if (!warnedNonString && !(key instanceof String)) { if (LOG.isWarnEnabled()) { LOG.warn( "JavaScript doesn't support non-String keys, using toString() on #0", key.getClass().getName()); } warnedNonString = true; } this.value(key.toString(), method); this.add(":"); this.value(entry.getValue(), method); if (this.buildExpr) { this.setExprStack(expr); } } this.add("}"); } /** Add date to buffer */ protected void date(Date date, Method method) { JSON json = null; if (method != null) json = method.getAnnotation(JSON.class); if (this.formatter == null) this.formatter = new SimpleDateFormat(JSONUtil.RFC3339_FORMAT); DateFormat formatter = (json != null) && (json.format().length() > 0) ? new SimpleDateFormat(json.format()) : this.formatter; this.string(formatter.format(date)); } /** Add array to buffer */ protected void array(Iterator it, Method method) throws JSONException { this.add("["); boolean hasData = false; for (int i = 0; it.hasNext(); i++) { String expr = null; if (this.buildExpr) { expr = this.expandExpr(i); if (this.shouldExcludeProperty(expr)) { it.next(); continue; } expr = this.setExprStack(expr); } if (hasData) { this.add(','); } hasData = true; this.value(it.next(), method); if (this.buildExpr) { this.setExprStack(expr); } } this.add("]"); } /** Add array to buffer */ protected void array(Object object, Method method) throws JSONException { this.add("["); int length = Array.getLength(object); boolean hasData = false; for (int i = 0; i < length; ++i) { String expr = null; if (this.buildExpr) { expr = this.expandExpr(i); if (this.shouldExcludeProperty(expr)) { continue; } expr = this.setExprStack(expr); } if (hasData) { this.add(','); } hasData = true; this.value(Array.get(object, i), method); if (this.buildExpr) { this.setExprStack(expr); } } this.add("]"); } /** Add boolean to buffer */ protected void bool(boolean b) { this.add(b ? "true" : "false"); } /** escape characters */ protected void string(Object obj) { this.add('"'); CharacterIterator it = new StringCharacterIterator(obj.toString()); for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { if (c == '"') { this.add("\\\""); } else if (c == '\\') { this.add("\\\\"); } else if (c == '/') { this.add("\\/"); } else if (c == '\b') { this.add("\\b"); } else if (c == '\f') { this.add("\\f"); } else if (c == '\n') { this.add("\\n"); } else if (c == '\r') { this.add("\\r"); } else if (c == '\t') { this.add("\\t"); } else if (Character.isISOControl(c)) { this.unicode(c); } else { this.add(c); } } this.add('"'); } /** Add object to buffer */ protected void add(Object obj) { this.buf.append(obj); } /** Add char to buffer */ protected void add(char c) { this.buf.append(c); } /** * Represent as unicode * * @param c character to be encoded */ protected void unicode(char c) { this.add("\\u"); int n = c; for (int i = 0; i < 4; ++i) { int digit = (n & 0xf000) >> 12; this.add(hex[digit]); n <<= 4; } } public void setIgnoreHierarchy(boolean ignoreHierarchy) { this.ignoreHierarchy = ignoreHierarchy; } /** * If true, an Enum is serialized as a bean with a special property _name=name() as all as all * other properties defined within the enum.<br> * If false, an Enum is serialized as a name=value pair (name=name()) * * @param enumAsBean true to serialize an enum as a bean instead of as a name=value pair * (default=false) */ public void setEnumAsBean(boolean enumAsBean) { this.enumAsBean = enumAsBean; } public void setDateFormatter(String defaultDateFormat) { if (defaultDateFormat != null) { this.formatter = new SimpleDateFormat(defaultDateFormat); } } protected static class JSONAnnotationFinder { private boolean serialize = true; private Method accessor; private String name; public JSONAnnotationFinder(Method accessor) { this.accessor = accessor; } public boolean shouldSerialize() { return serialize; } public String getName() { return name; } public JSONAnnotationFinder invoke() { JSON json = accessor.getAnnotation(JSON.class); serialize = json.serialize(); if (serialize && json.name().length() > 0) { name = json.name(); } return this; } } }
/** * <code>ServletContextListener</code> that initializes and finalizes the persistent storage of User * and Subscription information for the Struts Demonstration Application, using an in-memory * database backed by an XML file. * * <p> * * <p><strong>IMPLEMENTATION WARNING</strong> - If this web application is run from a WAR file, or * in another environment where reading and writing of the web application resource is impossible, * the initial contents will be copied to a file in the web application temporary directory provided * by the container. This is for demonstration purposes only - you should <strong>NOT</strong> * assume that files written here will survive a restart of your servlet container. * * <p> * * <p>This class was borrowed from the Shale Mailreader. Changes were: * * <p> * * <ul> * <p> * <li>Path to database.xml (under classes here). * <p> * <li>Class to store protocol list (an array here). * <p> * </ul> * * <p>DEVELOPMENT NOTE - Another approach would be to instantiate the database via Spring. */ public final class ApplicationListener implements ServletContextListener { // ------------------------------------------------------ Manifest Constants /** Appication scope attribute key under which the in-memory version of our database is stored. */ public static final String DATABASE_KEY = "database"; /** * Application scope attribute key under which the valid selection items for the protocol property * is stored. */ public static final String PROTOCOLS_KEY = "protocols"; // ------------------------------------------------------ Instance Variables /** The <code>ServletContext</code> for this web application. */ private ServletContext context = null; /** The {@link MemoryUserDatabase} object we construct and make available. */ private MemoryUserDatabase database = null; /** Logging output for this plug in instance. */ private Logger log = LoggerFactory.getLogger(this.getClass()); // ------------------------------------------------------------- Properties /** The web application resource path of our persistent database storage file. */ private String pathname = "/WEB-INF/database.xml"; /** * Return the application resource path to the database. * * @return application resource path path to the database */ public String getPathname() { return (this.pathname); } /** * Set the application resource path to the database. * * @param pathname to the database */ public void setPathname(String pathname) { this.pathname = pathname; } // ------------------------------------------ ServletContextListener Methods /** * Gracefully shut down this database, releasing any resources that were allocated at * initialization. * * @param event ServletContextEvent to process */ public void contextDestroyed(ServletContextEvent event) { log.info("Finalizing memory database plug in"); if (database != null) { try { database.close(); } catch (Exception e) { log.error("Closing memory database", e); } } context.removeAttribute(DATABASE_KEY); context.removeAttribute(PROTOCOLS_KEY); database = null; context = null; } /** * Initialize and load our initial database from persistent storage. * * @param event The context initialization event */ public void contextInitialized(ServletContextEvent event) { log.info("Initializing memory database plug in from '" + pathname + "'"); // Remember our associated ServletContext this.context = event.getServletContext(); // Construct a new database and make it available database = new MemoryUserDatabase(); try { String path = calculatePath(); if (log.isDebugEnabled()) { log.debug(" Loading database from '" + path + "'"); } database.setPathname(path); database.open(); } catch (Exception e) { log.error("Opening memory database", e); throw new IllegalStateException("Cannot load database from '" + pathname + "': " + e); } context.setAttribute(DATABASE_KEY, database); } // -------------------------------------------------------- Private Methods /** * Calculate and return an absolute pathname to the XML file to contain our persistent storage * information. * * @throws Exception if an input/output error occurs */ private String calculatePath() throws Exception { // Can we access the database via file I/O? String path = context.getRealPath(pathname); if (path != null) { return (path); } // Does a copy of this file already exist in our temporary directory File dir = (File) context.getAttribute("javax.servlet.context.tempdir"); File file = new File(dir, "struts-example-database.xml"); if (file.exists()) { return (file.getAbsolutePath()); } // Copy the static resource to a temporary file and return its path InputStream is = context.getResourceAsStream(pathname); BufferedInputStream bis = new BufferedInputStream(is, 1024); FileOutputStream os = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(os, 1024); byte buffer[] = new byte[1024]; while (true) { int n = bis.read(buffer); if (n <= 0) { break; } bos.write(buffer, 0, n); } bos.close(); bis.close(); return (file.getAbsolutePath()); } }
public class DiskFileItemMultiPartRequest implements MultiPartRequest { static final Logger LOG = LoggerFactory.getLogger(MultiPartRequest.class); // maps parameter name -> List of FileItem objects protected Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>(); // maps parameter name -> List of param values protected Map<String, List<String>> params = new HashMap<String, List<String>>(); // any errors while processing this request protected List<String> errors = new ArrayList<String>(); protected long maxSize; protected long sizeThreshold; @Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE) public void setMaxSize(String maxSize) { this.maxSize = Long.parseLong(maxSize); } @Inject("struts.multipart.sizeThreshold") public void setSizeThreshold(long sizeThreshold) { this.sizeThreshold = sizeThreshold; } /** * Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's * multipart classes (see class description). * * @param saveDir the directory to save off the file * @param request the request containing the multipart * @throws java.io.IOException is thrown if encoding fails. */ public void parse(HttpServletRequest request, String saveDir) throws IOException { try { processUpload(request, saveDir); } catch (FileUploadException e) { if (LOG.isWarnEnabled()) { LOG.warn("Unable to parse request", e); } errors.add(e.getMessage()); } } private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException { for (FileItem item : parseRequest(request, saveDir)) { if (LOG.isDebugEnabled()) { LOG.debug("Found item " + item.getFieldName()); } if (item.isFormField()) { processNormalFormField(item, request.getCharacterEncoding()); } else { processFileField(item); } } } private void processFileField(FileItem item) { if (LOG.isDebugEnabled()) { LOG.debug("Item is a file upload"); } // Skip file uploads that don't have a file name - meaning that no file // was selected. if (item.getName() == null || item.getName().trim().length() < 1) { LOG.debug("No file has been uploaded for the field: " + item.getFieldName()); return; } List<FileItem> values; if (files.get(item.getFieldName()) != null) { values = files.get(item.getFieldName()); } else { values = new ArrayList<FileItem>(); } values.add(item); files.put(item.getFieldName(), values); } private void processNormalFormField(FileItem item, String charset) throws UnsupportedEncodingException { if (LOG.isDebugEnabled()) { LOG.debug("Item is a normal form field"); } List<String> values; if (params.get(item.getFieldName()) != null) { values = params.get(item.getFieldName()); } else { values = new ArrayList<String>(); } // note: see http://jira.opensymphony.com/browse/WW-633 // basically, in some cases the charset may be null, so // we're just going to try to "other" method (no idea if this // will work) if (charset != null) { values.add(item.getString(charset)); } else { values.add(item.getString()); } params.put(item.getFieldName(), values); } private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException { DiskFileItemFactory fac = createDiskFileItemFactory(saveDir); ServletFileUpload upload = new ServletFileUpload(fac); upload.setSizeMax(maxSize); return upload.parseRequest(createRequestContext(servletRequest)); } private DiskFileItemFactory createDiskFileItemFactory(String saveDir) { DiskFileItemFactory fac = new DiskFileItemFactory(); // Make sure that the data is written to file // fac.setSizeThreshold(10240); if (saveDir != null) { fac.setRepository(new File(saveDir)); } return fac; } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames() */ public Enumeration<String> getFileParameterNames() { return Collections.enumeration(files.keySet()); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String) */ public String[] getContentType(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> contentTypes = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { contentTypes.add(fileItem.getContentType()); } return contentTypes.toArray(new String[contentTypes.size()]); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String) */ public File[] getFile(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<File> fileList = new ArrayList<File>(items.size()); for (FileItem fileItem : items) { File storeLocation = ((DiskFileItem) fileItem).getStoreLocation(); if (fileItem.isInMemory() && storeLocation != null && !storeLocation.exists()) { try { storeLocation.createNewFile(); } catch (IOException e) { if (LOG.isErrorEnabled()) { LOG.error( "Cannot write uploaded empty file to disk: " + storeLocation.getAbsolutePath(), e); } } } fileList.add(storeLocation); } return fileList.toArray(new File[fileList.size()]); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String) */ public String[] getFileNames(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> fileNames = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { fileNames.add(getCanonicalName(fileItem.getName())); } return fileNames.toArray(new String[fileNames.size()]); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String) */ public String[] getFilesystemName(String fieldName) { List<FileItem> items = files.get(fieldName); if (items == null) { return null; } List<String> fileNames = new ArrayList<String>(items.size()); for (FileItem fileItem : items) { fileNames.add(((DiskFileItem) fileItem).getStoreLocation().getName()); } return fileNames.toArray(new String[fileNames.size()]); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String) */ public String getParameter(String name) { List<String> v = params.get(name); if (v != null && v.size() > 0) { return v.get(0); } return null; } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames() */ public Enumeration<String> getParameterNames() { return Collections.enumeration(params.keySet()); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String) */ public String[] getParameterValues(String name) { List<String> v = params.get(name); if (v != null && v.size() > 0) { return v.toArray(new String[v.size()]); } return null; } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors() */ public List getErrors() { return errors; } /** * Returns the canonical name of the given file. * * @param filename the given file * @return the canonical name of the given file */ private String getCanonicalName(String filename) { int forwardSlash = filename.lastIndexOf("/"); int backwardSlash = filename.lastIndexOf("\\"); if (forwardSlash != -1 && forwardSlash > backwardSlash) { filename = filename.substring(forwardSlash + 1, filename.length()); } else if (backwardSlash != -1 && backwardSlash >= forwardSlash) { filename = filename.substring(backwardSlash + 1, filename.length()); } return filename; } /** * Creates a RequestContext needed by Jakarta Commons Upload. * * @param req the request. * @return a new request context. */ private RequestContext createRequestContext(final HttpServletRequest req) { return new RequestContext() { public String getCharacterEncoding() { return req.getCharacterEncoding(); } public String getContentType() { return req.getContentType(); } public int getContentLength() { return req.getContentLength(); } public InputStream getInputStream() throws IOException { InputStream in = req.getInputStream(); if (in == null) { throw new IOException("Missing content in the request"); } return req.getInputStream(); } }; } }
/** * Base Action for MailreaderSupport application. * * <p> * * <p>Note that this class does NOT implement model driven because of the way the pre-existing model * is designed. The MailReader DAO includes immutable fields that can only be set on construction, * and some objects do not have a default construction. One approach would be to mirror all the DAO * properties on the Actions. As an alternative, this implementations uses the DAO properties where * possible, and uses local Action properties only as needed. To create new objects, a blank * temporary object is constructed, and the page uses a mix of local Action properties and DAO * properties. When the new object is to be saved, the local Action properties are used to create * the object using the DAO factory methods, the input values are copied from the temporary object, * and the new object is saved. It's kludge, but it avoids creating unnecessary local properties. * Pick your poison. */ public class MailreaderSupport extends ActionSupport implements SessionAware, ApplicationAware { /** * Return CANCEL so apropriate result can be selected. * * @return "cancel" so apropriate result can be selected. */ public String cancel() { return Constants.CANCEL; } /** Convenience method to copy User properties. */ protected void copyUser(User source, User target) { if ((source == null) || (target == null)) return; target.setFromAddress(source.getFromAddress()); target.setFullName(source.getFullName()); target.setPassword(source.getPassword()); target.setReplyToAddress(source.getReplyToAddress()); } /** Convenience method to copy Subscription properties. */ protected void copySubscription(Subscription source, Subscription target) { if ((source == null) || (target == null)) return; target.setAutoConnect(source.getAutoConnect()); target.setPassword(source.getPassword()); target.setType(source.getType()); target.setUsername(source.getUsername()); } // ---- ApplicationAware ---- /** * Field to store application context or its proxy. * * <p> * * <p>The application context lasts for the life of the application. A reference to the database * is stored in the application context at startup. */ private Map application; /** * Store a new application context. * * @param value A Map representing application state */ public void setApplication(Map value) { application = value; } /** Provide application context. */ public Map getApplication() { return application; } // ---- SessionAware ---- /** Field to store session context, or its proxy. */ private Map session; /** * Store a new session context. * * @param value A Map representing session state */ public void setSession(Map value) { session = value; } /** * Provide session context. * * @return session context */ public Map getSession() { return session; } // ---- Task property (utilized by UI) ---- /** * Field to store workflow task. * * <p> * * <p>The Task is used to track the state of the CRUD workflows. It can be set to Constant.CREATE, * Constant.EDIT, or Constant.DELETE as needed. */ private String task = null; /** * Provide worklow task. * * @return Returns the task. */ public String getTask() { return task; } /** * Store new workflow task. * * @param value The task to set. */ public void setTask(String value) { task = value; } // ---- Token property (utilized by UI) ---- /** Field to store double-submit guard. */ private String token = null; /** * Provide Token. * * @return Returns the token. */ public String getToken() { return token; } /** * Store new Token. * * @param value The token to set. */ public void setToken(String value) { token = value; } // ---- Host property ---- /** * Field to store Subscription host. * * <p> * * <p>The host is an immutable property of the Subscrtion DAP object, so we need to store it * locally until we are ready to create the Subscription. */ private String host; /** * Provide tSubscription host. * * @return host property */ public String getHost() { return host; } /** * Store new Subscription host. * * @param value */ public void setHost(String value) { host = value; } // ---- Password property ---- /** * Field to store User password property. * * <p> * * <p>The User DAO object password proerty is immutable, so we store it locally until we are ready * to create the object. */ private String password = null; /** * Provide User password * * @return Returns the password. */ public String getPassword() { return password; } /** * Store new User Password * * @param value The password to set. */ public void setPassword(String value) { password = value; } // ---- Password2 property (confirmation) ---- /** * Field to store the User password confirmation. * * <p> * * <p>When a User object is created, we ask the client to enter the password twice, to help ensure * the password is being typed correctly. */ private String password2 = null; /** * Provide the User password confirmation. * * @return Returns the confirmationpassword. */ public String getPassword2() { return password2; } /** * Store a new User password confirmation. * * @param value The confirmation password to set. */ public void setPassword2(String value) { password2 = value; } // ---- Username property ---- /** * Field to store User username. * * <p> * * <p>The User DAO object password proerty is immutable, so we store it locally until we are ready * to create the object. */ private String username = null; /** * Provide User username. * * @return Returns the User username. */ public String getUsername() { return username; } /** * Store new User username * * @param value The username to set. */ public void setUsername(String value) { username = value; } // ---- Database property ---- /** * Provide reference to UserDatabase, or null if the database is not available. * * @return a reference to the UserDatabase or null if the database is not available */ public UserDatabase getDatabase() { Object db = getApplication().get(Constants.DATABASE_KEY); if (db == null) { this.addActionError(getText("error.database.missing")); } return (UserDatabase) db; } /** * Store a new reference to UserDatabase * * @param database */ public void setDatabase(UserDatabase database) { getApplication().put(Constants.DATABASE_KEY, database); } // ---- User property ---- /** * Provide reference to User object for authenticated user. * * @return User object for authenticated user. */ public User getUser() { return (User) getSession().get(Constants.USER_KEY); } /** * Store new reference to User Object. * * @param user User object for authenticated user */ public void setUser(User user) { getSession().put(Constants.USER_KEY, user); } /** * Obtain User object from database, or return null if the credentials are not found or invalid. * * @param username User username * @param password User password * @return User object or null if not found * @throws ExpiredPasswordException */ public User findUser(String username, String password) throws ExpiredPasswordException { // FIXME: Stupid testing hack to compensate for inadequate DAO layer if (Constants.EXPIRED_PASSWORD_EXCEPTION.equals(username)) { throw new ExpiredPasswordException(Constants.EXPIRED_PASSWORD_EXCEPTION); } User user = getDatabase().findUser(username); if ((user != null) && !user.getPassword().equals(password)) { user = null; } if (user == null) { this.addFieldError(Constants.PASSWORD_MISMATCH_FIELD, getText("error.password.mismatch")); } return user; } /** <code>Log</code> instance for this application. */ protected Logger log = LoggerFactory.getLogger(Constants.PACKAGE); /** * Persist the User object, including subscriptions, to the database. * * @throws java.lang.Exception on database error */ public void saveUser() throws Exception { try { getDatabase().save(); } catch (Exception e) { String message = Constants.LOG_DATABASE_SAVE_ERROR + getUser().getUsername(); log.error(message, e); throw new Exception(message, e); } } public void createInputUser() { User user = new MemoryUser(null, null); setUser(user); } /** * Verify input for creating a new user, create the user, and process the login. * * @return A new User and empty Errors if create succeeds, or null and Errors if create fails */ public User createUser(String username, String password) { UserDatabase database = getDatabase(); User user; try { user = database.findUser(username); } catch (ExpiredPasswordException e) { user = getUser(); // Just so that it is not null } if (user != null) { this.addFieldError("username", "error.username.unique"); return null; } return database.createUser(username); } // Since user.username is immutable, we have to use some local properties /** * Use the current User object to create a new User object, and make the new User object the * authenticated user. * * <p> * * <p>The "current" User object is usually a temporary object being used to capture input. * * @param _username User username * @param _password User password */ public void copyUser(String _username, String _password) { User input = getUser(); input.setPassword(_password); User user = createUser(_username, _password); if (null != user) { copyUser(input, user); setUser(user); } } // ---- Subscription property ---- /** * Obtain the cached Subscription object, if any. * * @return Cached Subscription object or null */ public Subscription getSubscription() { return (Subscription) getSession().get(Constants.SUBSCRIPTION_KEY); } /** * Store new User Subscription. * * @param subscription */ public void setSubscription(Subscription subscription) { getSession().put(Constants.SUBSCRIPTION_KEY, subscription); } /** * Obtain User Subscription object for the given host, or return null if not found. * * <p>It would be possible for this code to throw a NullPointerException, but the ExceptionHandler * in the xwork.xml will catch that for us. * * @return The matching Subscription or null */ public Subscription findSubscription(String host) { Subscription subscription; subscription = getUser().findSubscription(host); return subscription; } /** * Obtain uSER Subscription for the local Host property. * * <p> * * <p>Usually, the host property will be set from the client request, because it was embedded in a * link to the Subcription action. * * @return Subscription or null if not found */ public Subscription findSubscription() { return findSubscription(getHost()); } /** Provide a "temporary" User Subscription object that can be used to capture input values. */ public void createInputSubscription() { Subscription sub = new MemorySubscription(getUser(), null); setSubscription(sub); setHost(sub.getHost()); } /** * Provide new User Subscription object for the given host, or null if the host is not unique. * * @param host * @return New User Subscription object or null */ public Subscription createSubscription(String host) { Subscription sub; sub = findSubscription(host); if (null != sub) { // FIXME - localization - "error.host.unique") addFieldError(Constants.HOST, "That hostname is already defined"); return null; } return getUser().createSubscription(host); } /** * Create a new Subscription from the current Subscription object, making the new Subscription the * current Subscription. * * <p> * * <p>Usually, the "current" Subscription is a temporary object being used to capture input * values. * * @param host */ public void copySubscription(String host) { Subscription input = getSubscription(); Subscription sub = createSubscription(host); if (null != sub) { copySubscription(input, sub); setSubscription(sub); setHost(sub.getHost()); } } /** Delete the current Subscription object from the database. */ public void removeSubscription() { getUser().removeSubscription(getSubscription()); getSession().remove(Constants.SUBSCRIPTION_KEY); } /** * Provide MailServer Host for current User Subscription. * * @return MailServer Host for current User Subscription */ public String getSubscriptionHost() { Subscription sub = getSubscription(); if (null == sub) { return null; } return sub.getHost(); } }
/** * UIBean is the standard superclass of all Struts UI components. It defines common Struts and html * properties all UI components should present for usage. * <!-- START SNIPPET: templateRelatedAttributes --> * * <table border="1"> * <thead> * <tr> * <td>Attribute</td> * <td>Theme</td> * <td>Data Types</td> * <td>Description</td> * </tr> * </thead> * <tbody> * <tr> * <td>templateDir</td> * <td>n/a</td> * <td>String</td> * <td>define the template directory</td> * </td> * <tr> * <td>theme</td> * <td>n/a</td> * <td>String</td> * <td>define the theme name</td> * </td> * <tr> * <td>template</td> * <td>n/a</td> * <td>String</td> * <td>define the template name</td> * </td> * <tr> * <td>themeExpansionToken</td> * <td>n/a</td> * <td>String</td> * <td>special token (defined with struts.ui.theme.expansion.token) used to search for template in parent theme * (don't use it separately!)</td> * </td> * <tr> * <td>expandTheme</td> * <td>n/a</td> * <td>String</td> * <td>concatenation of themeExpansionToken and theme which tells internal template loader mechanism * to try load template from current theme and then from parent theme (and parent theme, and so on) * when used with <#include/> directive</td> * </td> * </tbody> * </table> * * <!-- END SNIPPET: templateRelatedAttributes --> * * <p> * <!-- START SNIPPET: generalAttributes --> * * <table border="1"> * <thead> * <tr> * <td>Attribute</td> * <td>Theme</td> * <td>Data Types</td> * <td>Description</td> * </tr> * </thead> * <tbody> * <tr> * <td>cssClass</td> * <td>simple</td> * <td>String</td> * <td>define html class attribute</td> * </tr> * <tr> * <td>cssStyle</td> * <td>simple</td> * <td>String</td> * <td>define html style attribute</td> * </tr> * <tr> * <td>cssClass</td> * <td>simple</td> * <td>String</td> * <td>error class attribute</td> * </tr> * <tr> * <td>cssStyle</td> * <td>simple</td> * <td>String</td> * <td>error style attribute</td> * </tr> * <tr> * <td>title</td> * <td>simple</td> * <td>String</td> * <td>define html title attribute</td> * </tr> * <tr> * <td>disabled</td> * <td>simple</td> * <td>String</td> * <td>define html disabled attribute</td> * </tr> * <tr> * <td>label</td> * <td>xhtml</td> * <td>String</td> * <td>define label of form element</td> * </tr> * <tr> * <td>labelPosition</td> * <td>xhtml</td> * <td>String</td> * <td>define label position of form element (top/left), default to left</td> * </tr> * <tr> * <td>requiredPosition</td> * <td>xhtml</td> * <td>String</td> * <td>define required label position of form element (left/right), default to right</td> * </tr> * <tr> * <td>errorPosition</td> * <td>xhtml</td> * <td>String</td> * <td>define error position of form element (top|bottom), default to top</td> * </tr> * <tr> * <td>name</td> * <td>simple</td> * <td>String</td> * <td>Form Element's field name mapping</td> * </tr> * <tr> * <td>required</td> * <td>xhtml</td> * <td>Boolean</td> * <td>add * to label (true to add false otherwise)</td> * </tr> * <tr> * <td>tabIndex</td> * <td>simple</td> * <td>String</td> * <td>define html tabindex attribute</td> * </tr> * <tr> * <td>value</td> * <td>simple</td> * <td>Object</td> * <td>define value of form element</td> * </tr> * </tbody> * </table> * * <!-- END SNIPPET: generalAttributes --> * * <p> * <!-- START SNIPPET: javascriptRelatedAttributes --> * * <table border="1"> * <thead> * <tr> * <td>Attribute</td> * <td>Theme</td> * <td>Data Types</td> * <td>Description</td> * </tr> * </thead> * <tbody> * <tr> * <td>onclick</td> * <td>simple</td> * <td>String</td> * <td>html javascript onclick attribute</td> * </tr> * <tr> * <td>ondblclick</td> * <td>simple</td> * <td>String</td> * <td>html javascript ondbclick attribute</td> * </tr> * <tr> * <td>onmousedown</td> * <td>simple</td> * <td>String</td> * <td>html javascript onmousedown attribute</td> * </tr> * <tr> * <td>onmouseup</td> * <td>simple</td> * <td>String</td> * <td>html javascript onmouseup attribute</td> * </tr> * <tr> * <td>onmouseover</td> * <td>simple</td> * <td>String</td> * <td>html javascript onmouseover attribute</td> * </tr> * <tr> * <td>onmouseout</td> * <td>simple</td> * <td>String</td> * <td>html javascript onmouseout attribute</td> * </tr> * <tr> * <td>onfocus</td> * <td>simple</td> * <td>String</td> * <td>html javascript onfocus attribute</td> * </tr> * <tr> * <td>onblur</td> * <td>simple</td> * <td>String</td> * <td>html javascript onblur attribute</td> * </tr> * <tr> * <td>onkeypress</td> * <td>simple</td> * <td>String</td> * <td>html javascript onkeypress attribute</td> * </tr> * <tr> * <td>onkeyup</td> * <td>simple</td> * <td>String</td> * <td>html javascript onkeyup attribute</td> * </tr> * <tr> * <td>onkeydown</td> * <td>simple</td> * <td>String</td> * <td>html javascript onkeydown attribute</td> * </tr> * <tr> * <td>onselect</td> * <td>simple</td> * <td>String</td> * <td>html javascript onselect attribute</td> * </tr> * <tr> * <td>onchange</td> * <td>simple</td> * <td>String</td> * <td>html javascript onchange attribute</td> * </tr> * </tbody> * </table> * * <!-- END SNIPPET: javascriptRelatedAttributes --> * * <p> * <!-- START SNIPPET: tooltipattributes --> * * <table border="1"> * <tr> * <td>Attribute</td> * <td>Data Type</td> * <td>Default</td> * <td>Description</td> * </tr> * <tr> * <td>tooltip</td> * <td>String</td> * <td>none</td> * <td>Set the tooltip of this particular component</td> * </tr> * <tr> * <td>jsTooltipEnabled</td> * <td>String</td> * <td>false</td> * <td>Enable js tooltip rendering</td> * </tr> * <tr> * <td>tooltipIcon</td> * <td>String</td> * <td>/struts/static/tooltip/tooltip.gif</td> * <td>The url to the tooltip icon</td> * <tr> * <td>tooltipDelay</td> * <td>String</td> * <td>500</td> * <td>Tooltip shows up after the specified timeout (miliseconds). A behavior similar to that of OS based tooltips.</td> * </tr> * <tr> * <td>key</td> * <td>simple</td> * <td>String</td> * <td>The name of the property this input field represents. This will auto populate the name, label, and value</td> * </tr> * </table> * * <!-- END SNIPPET: tooltipattributes --> * <!-- START SNIPPET: tooltipdescription --> * <b>tooltipConfig is deprecated, use individual tooltip configuration attributes instead </b> * * <p>Every Form UI component (in xhtml / css_xhtml or any other that extends them) can have * tooltips assigned to them. The Form component's tooltip related attribute, once defined, will be * applied to all form UI components that are created under it unless explicitly overriden by having * the Form UI component itself defined with their own tooltip attribute. * * <p>In Example 1, the textfield will inherit the tooltipDelay and tooltipIconPath attribte from * its containing form. In other words, although it doesn't define a tooltipIconPath attribute, it * will have that attribute inherited from its containing form. * * <p>In Example 2, the textfield will inherite both the tooltipDelay and tooltipIconPath attribute * from its containing form, but the tooltipDelay attribute is overriden at the textfield itself. * Hence, the textfield actually will have its tooltipIcon defined as /myImages/myIcon.gif, * inherited from its containing form, and tooltipDelay defined as 5000. * * <p>Example 3, 4 and 5 show different ways of setting the tooltip configuration attribute.<br> * <b>Example 3:</b> Set tooltip config through the body of the param tag<br> * <b>Example 4:</b> Set tooltip config through the value attribute of the param tag<br> * <b>Example 5:</b> Set tooltip config through the tooltip attributes of the component tag<br> * <!-- END SNIPPET: tooltipdescription --> * * <pre> * <!-- START SNIPPET: tooltipexample --> * * <!-- Example 1: --> * <s:form * tooltipDelay="500" * tooltipIconPath="/myImages/myIcon.gif" .... > * .... * <s:textfield label="Customer Name" tooltip="Enter the customer name" .... /> * .... * </s:form> * * <!-- Example 2: --> * <s:form * tooltipDelay="500" * tooltipIconPath="/myImages/myIcon.gif" .... > * .... * <s:textfield label="Address" * tooltip="Enter your address" * tooltipDelay="5000" /> * .... * </s:form> * * * <-- Example 3: --> * <s:textfield * label="Customer Name" * tooltip="One of our customer Details"> * <s:param name="tooltipDelay"> * 500 * </s:param> * <s:param name="tooltipIconPath"> * /myImages/myIcon.gif * </s:param> * </s:textfield> * * * <-- Example 4: --> * <s:textfield * label="Customer Address" * tooltip="Enter The Customer Address" > * <s:param * name="tooltipDelay" * value="500" /> * </s:textfield> * * * <-- Example 5: --> * <s:textfield * label="Customer Telephone Number" * tooltip="Enter customer Telephone Number" * tooltipDelay="500" * tooltipIconPath="/myImages/myIcon.gif" /> * * <!-- END SNIPPET: tooltipexample --> * </pre> */ public abstract class UIBean extends Component { private static final Logger LOG = LoggerFactory.getLogger(UIBean.class); protected HttpServletRequest request; protected HttpServletResponse response; public UIBean(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { super(stack); this.request = request; this.response = response; this.templateSuffix = ContextUtil.getTemplateSuffix(stack.getContext()); } // The templateSuffic to use, overrides the default one if not null. protected String templateSuffix; // The template to use, overrides the default one. protected String template; // templateDir and theme attributes protected String templateDir; protected String theme; // shortcut, sets label, name, and value protected String key; protected String id; protected String cssClass; protected String cssStyle; protected String cssErrorClass; protected String cssErrorStyle; protected String disabled; protected String label; protected String labelPosition; protected String labelSeparator; protected String requiredPosition; protected String errorPosition; protected String name; protected String requiredLabel; protected String tabindex; protected String value; protected String title; // HTML scripting events attributes protected String onclick; protected String ondblclick; protected String onmousedown; protected String onmouseup; protected String onmouseover; protected String onmousemove; protected String onmouseout; protected String onfocus; protected String onblur; protected String onkeypress; protected String onkeydown; protected String onkeyup; protected String onselect; protected String onchange; // common html attributes protected String accesskey; // javascript tooltip attribute protected String tooltip; protected String tooltipConfig; protected String javascriptTooltip; protected String tooltipDelay; protected String tooltipCssClass; protected String tooltipIconPath; // dynamic attributes protected Map<String, Object> dynamicAttributes = new HashMap<String, Object>(); protected String defaultTemplateDir; protected String defaultUITheme; protected String uiThemeExpansionToken; protected TemplateEngineManager templateEngineManager; // dynamic attributes support for tags used with FreeMarker templates protected static ConcurrentMap<Class, Set<String>> standardAttributesMap = new ConcurrentHashMap<Class, Set<String>>(); @Inject(StrutsConstants.STRUTS_UI_TEMPLATEDIR) public void setDefaultTemplateDir(String dir) { this.defaultTemplateDir = dir; } @Inject(StrutsConstants.STRUTS_UI_THEME) public void setDefaultUITheme(String theme) { this.defaultUITheme = theme; } @Inject(StrutsConstants.STRUTS_UI_THEME_EXPANSION_TOKEN) public void setUIThemeExpansionToken(String uiThemeExpansionToken) { this.uiThemeExpansionToken = uiThemeExpansionToken; } @Inject public void setTemplateEngineManager(TemplateEngineManager mgr) { this.templateEngineManager = mgr; } public boolean end(Writer writer, String body) { evaluateParams(); try { super.end(writer, body, false); mergeTemplate(writer, buildTemplateName(template, getDefaultTemplate())); } catch (Exception e) { throw new StrutsException(e); } finally { popComponentStack(); } return false; } /** * A contract that requires each concrete UI Tag to specify which template should be used as a * default. For example, the CheckboxTab might return "checkbox.vm" while the RadioTag might * return "radio.vm". This value <strong>not</strong> begin with a '/' unless you intend to make * the path absolute rather than relative to the current theme. * * @return The name of the template to be used as the default. */ protected abstract String getDefaultTemplate(); protected Template buildTemplateName(String myTemplate, String myDefaultTemplate) { String template = myDefaultTemplate; if (myTemplate != null) { template = findString(myTemplate); } String templateDir = getTemplateDir(); String theme = getTheme(); return new Template(templateDir, theme, template); } protected void mergeTemplate(Writer writer, Template template) throws Exception { final TemplateEngine engine = templateEngineManager.getTemplateEngine(template, templateSuffix); if (engine == null) { throw new ConfigurationException("Unable to find a TemplateEngine for template " + template); } if (LOG.isDebugEnabled()) { LOG.debug("Rendering template " + template); } final TemplateRenderingContext context = new TemplateRenderingContext(template, writer, getStack(), getParameters(), this); engine.renderTemplate(context); } public String getTemplateDir() { String templateDir = null; if (this.templateDir != null) { templateDir = findString(this.templateDir); } // If templateDir is not explicitly given, // try to find attribute which states the dir set to use if ((templateDir == null) || (templateDir.equals(""))) { templateDir = stack.findString("#attr.templateDir"); } // Default template set if ((templateDir == null) || (templateDir.equals(""))) { templateDir = defaultTemplateDir; } // Defaults to 'template' if ((templateDir == null) || (templateDir.equals(""))) { templateDir = "template"; } return templateDir; } public String getTheme() { String theme = null; if (this.theme != null) { theme = findString(this.theme); } if (theme == null || theme.equals("")) { Form form = (Form) findAncestor(Form.class); if (form != null) { theme = form.getTheme(); } } // If theme set is not explicitly given, // try to find attribute which states the theme set to use if ((theme == null) || (theme.equals(""))) { theme = stack.findString("#attr.theme"); } // Default theme set if ((theme == null) || (theme.equals(""))) { theme = defaultUITheme; } return theme; } public void evaluateParams() { String templateDir = getTemplateDir(); String theme = getTheme(); addParameter("templateDir", templateDir); addParameter("theme", theme); addParameter("template", template != null ? findString(template) : getDefaultTemplate()); addParameter("dynamicAttributes", dynamicAttributes); addParameter("themeExpansionToken", uiThemeExpansionToken); addParameter("expandTheme", uiThemeExpansionToken + theme); String name = null; String providedLabel = null; if (this.key != null) { if (this.name == null) { this.name = key; } if (this.label == null) { // lookup the label from a TextProvider (default value is the key) providedLabel = TextProviderHelper.getText(key, key, stack); } } if (this.name != null) { name = findString(this.name); addParameter("name", name); } if (label != null) { addParameter("label", findString(label)); } else { if (providedLabel != null) { // label found via a TextProvider addParameter("label", providedLabel); } } if (labelSeparator != null) { addParameter("labelseparator", findString(labelSeparator)); } if (labelPosition != null) { addParameter("labelposition", findString(labelPosition)); } if (requiredPosition != null) { addParameter("requiredPosition", findString(requiredPosition)); } if (errorPosition != null) { addParameter("errorposition", findString(errorPosition)); } if (requiredLabel != null) { addParameter("required", findValue(requiredLabel, Boolean.class)); } if (disabled != null) { addParameter("disabled", findValue(disabled, Boolean.class)); } if (tabindex != null) { addParameter("tabindex", findString(tabindex)); } if (onclick != null) { addParameter("onclick", findString(onclick)); } if (ondblclick != null) { addParameter("ondblclick", findString(ondblclick)); } if (onmousedown != null) { addParameter("onmousedown", findString(onmousedown)); } if (onmouseup != null) { addParameter("onmouseup", findString(onmouseup)); } if (onmouseover != null) { addParameter("onmouseover", findString(onmouseover)); } if (onmousemove != null) { addParameter("onmousemove", findString(onmousemove)); } if (onmouseout != null) { addParameter("onmouseout", findString(onmouseout)); } if (onfocus != null) { addParameter("onfocus", findString(onfocus)); } if (onblur != null) { addParameter("onblur", findString(onblur)); } if (onkeypress != null) { addParameter("onkeypress", findString(onkeypress)); } if (onkeydown != null) { addParameter("onkeydown", findString(onkeydown)); } if (onkeyup != null) { addParameter("onkeyup", findString(onkeyup)); } if (onselect != null) { addParameter("onselect", findString(onselect)); } if (onchange != null) { addParameter("onchange", findString(onchange)); } if (accesskey != null) { addParameter("accesskey", findString(accesskey)); } if (cssClass != null) { addParameter("cssClass", findString(cssClass)); } if (cssStyle != null) { addParameter("cssStyle", findString(cssStyle)); } if (cssErrorClass != null) { addParameter("cssErrorClass", findString(cssErrorClass)); } if (cssErrorStyle != null) { addParameter("cssErrorStyle", findString(cssErrorStyle)); } if (title != null) { addParameter("title", findString(title)); } // see if the value was specified as a parameter already if (parameters.containsKey("value")) { parameters.put("nameValue", parameters.get("value")); } else { if (evaluateNameValue()) { final Class valueClazz = getValueClassType(); if (valueClazz != null) { if (value != null) { addParameter("nameValue", findValue(value, valueClazz)); } else if (name != null) { String expr = completeExpressionIfAltSyntax(name); addParameter("nameValue", findValue(expr, valueClazz)); } } else { if (value != null) { addParameter("nameValue", findValue(value)); } else if (name != null) { addParameter("nameValue", findValue(name)); } } } } final Form form = (Form) findAncestor(Form.class); // create HTML id element populateComponentHtmlId(form); if (form != null) { addParameter("form", form.getParameters()); if (name != null) { // list should have been created by the form component List<String> tags = (List<String>) form.getParameters().get("tagNames"); tags.add(name); } } // tooltip & tooltipConfig if (tooltipConfig != null) { addParameter("tooltipConfig", findValue(tooltipConfig)); } if (tooltip != null) { addParameter("tooltip", findString(tooltip)); Map tooltipConfigMap = getTooltipConfig(this); if (form != null) { // inform the containing form that we need tooltip javascript included form.addParameter("hasTooltip", Boolean.TRUE); // tooltipConfig defined in component itseilf will take precedence // over those defined in the containing form Map overallTooltipConfigMap = getTooltipConfig(form); overallTooltipConfigMap.putAll(tooltipConfigMap); // override parent form's tooltip config for (Object o : overallTooltipConfigMap.entrySet()) { Map.Entry entry = (Map.Entry) o; addParameter((String) entry.getKey(), entry.getValue()); } } else { if (LOG.isWarnEnabled()) { LOG.warn( "No ancestor Form found, javascript based tooltip will not work, however standard HTML tooltip using alt and title attribute will still work "); } } // TODO: this is to keep backward compatibility, remove once when tooltipConfig is dropped String jsTooltipEnabled = (String) getParameters().get("jsTooltipEnabled"); if (jsTooltipEnabled != null) this.javascriptTooltip = jsTooltipEnabled; // TODO: this is to keep backward compatibility, remove once when tooltipConfig is dropped String tooltipIcon = (String) getParameters().get("tooltipIcon"); if (tooltipIcon != null) this.addParameter("tooltipIconPath", tooltipIcon); if (this.tooltipIconPath != null) this.addParameter("tooltipIconPath", findString(this.tooltipIconPath)); // TODO: this is to keep backward compatibility, remove once when tooltipConfig is dropped String tooltipDelayParam = (String) getParameters().get("tooltipDelay"); if (tooltipDelayParam != null) this.addParameter("tooltipDelay", tooltipDelayParam); if (this.tooltipDelay != null) this.addParameter("tooltipDelay", findString(this.tooltipDelay)); if (this.javascriptTooltip != null) { Boolean jsTooltips = (Boolean) findValue(this.javascriptTooltip, Boolean.class); // TODO use a Boolean model when tooltipConfig is dropped this.addParameter("jsTooltipEnabled", jsTooltips.toString()); if (form != null) form.addParameter("hasTooltip", jsTooltips); if (this.tooltipCssClass != null) this.addParameter("tooltipCssClass", findString(this.tooltipCssClass)); } } evaluateExtraParams(); } protected String escape(String name) { // escape any possible values that can make the ID painful to work with in JavaScript if (name != null) { return name.replaceAll("[\\/\\.\\[\\]]", "_"); } else { return null; } } /** * Ensures an unescaped attribute value cannot be vulnerable to XSS attacks * * @param val The value to check * @return The escaped value */ protected String ensureAttributeSafelyNotEscaped(String val) { if (val != null) { return val.replaceAll("\"", """); } else { return null; } } protected void evaluateExtraParams() {} protected boolean evaluateNameValue() { return true; } protected Class getValueClassType() { return String.class; } public void addFormParameter(String key, Object value) { Form form = (Form) findAncestor(Form.class); if (form != null) { form.addParameter(key, value); } } protected void enableAncestorFormCustomOnsubmit() { Form form = (Form) findAncestor(Form.class); if (form != null) { form.addParameter("customOnsubmitEnabled", Boolean.TRUE); } else { if (LOG.isWarnEnabled()) { LOG.warn("Cannot find an Ancestor form, custom onsubmit is NOT enabled"); } } } protected Map getTooltipConfig(UIBean component) { Object tooltipConfigObj = component.getParameters().get("tooltipConfig"); Map<String, String> tooltipConfig = new LinkedHashMap<String, String>(); if (tooltipConfigObj instanceof Map) { // we get this if its configured using // 1] UI component's tooltipConfig attribute OR // 2] <param name="tooltip" value="" /> param tag value attribute tooltipConfig = new LinkedHashMap<String, String>((Map) tooltipConfigObj); } else if (tooltipConfigObj instanceof String) { // we get this if its configured using // <param name="tooltipConfig"> ... </param> tag's body String tooltipConfigStr = (String) tooltipConfigObj; String[] tooltipConfigArray = tooltipConfigStr.split("\\|"); for (String aTooltipConfigArray : tooltipConfigArray) { String[] configEntry = aTooltipConfigArray.trim().split("="); String key = configEntry[0].trim(); String value; if (configEntry.length > 1) { value = configEntry[1].trim(); tooltipConfig.put(key, value); } else { if (LOG.isWarnEnabled()) { LOG.warn( "component " + component + " tooltip config param " + key + " has no value defined, skipped"); } } } } if (component.javascriptTooltip != null) tooltipConfig.put("jsTooltipEnabled", component.javascriptTooltip); if (component.tooltipIconPath != null) tooltipConfig.put("tooltipIcon", component.tooltipIconPath); if (component.tooltipDelay != null) tooltipConfig.put("tooltipDelay", component.tooltipDelay); return tooltipConfig; } /** * Create HTML id element for the component and populate this component parameter map. * Additionally, a parameter named escapedId is populated which contains the found id value * filtered by {@link #escape(String)}, needed eg. for naming Javascript identifiers based on the * id value. * * <p>The order is as follows :- * * <ol> * <li>This component id attribute * <li>[containing_form_id]_[this_component_name] * <li>[this_component_name] * </ol> * * @param form enclosing form tag */ protected void populateComponentHtmlId(Form form) { String tryId; String generatedId; if (id != null) { // this check is needed for backwards compatibility with 2.1.x tryId = findStringIfAltSyntax(id); } else if (null == (generatedId = escape(name != null ? findString(name) : null))) { if (LOG.isDebugEnabled()) { LOG.debug( "Cannot determine id attribute for [#0], consider defining id, name or key attribute!", this); } tryId = null; } else if (form != null) { tryId = form.getParameters().get("id") + "_" + generatedId; } else { tryId = generatedId; } addParameter("id", tryId); addParameter("escapedId", escape(tryId)); } /** * Get's the id for referencing element. * * @return the id for referencing element. */ public String getId() { return id; } @StrutsTagAttribute(description = "HTML id attribute") public void setId(String id) { if (id != null) { this.id = findString(id); } } @StrutsTagAttribute(description = "The template directory.") public void setTemplateDir(String templateDir) { this.templateDir = templateDir; } @StrutsTagAttribute( description = "The theme (other than default) to use for rendering the element") public void setTheme(String theme) { this.theme = theme; } public String getTemplate() { return template; } @StrutsTagAttribute( description = "The template (other than default) to use for rendering the element") public void setTemplate(String template) { this.template = template; } @StrutsTagAttribute(description = "The css class to use for element") public void setCssClass(String cssClass) { this.cssClass = cssClass; } @StrutsTagAttribute(description = "The css style definitions for element to use") public void setCssStyle(String cssStyle) { this.cssStyle = cssStyle; } @StrutsTagAttribute(description = "The css error class to use for element") public void setCssErrorClass(String cssErrorClass) { this.cssErrorClass = cssErrorClass; } @StrutsTagAttribute(description = "The css error style definitions for element to use") public void setCssErrorStyle(String cssErrorStyle) { this.cssErrorStyle = cssErrorStyle; } @StrutsTagAttribute(description = "Set the html title attribute on rendered html element") public void setTitle(String title) { this.title = title; } @StrutsTagAttribute(description = "Set the html disabled attribute on rendered html element") public void setDisabled(String disabled) { this.disabled = disabled; } @StrutsTagAttribute(description = "Label expression used for rendering an element specific label") public void setLabel(String label) { this.label = label; } @StrutsTagAttribute(description = "String that will be appended to the label", defaultValue = ":") public void setLabelSeparator(String labelseparator) { this.labelSeparator = labelseparator; } @StrutsTagAttribute(description = "Define label position of form element (top/left)") public void setLabelposition(String labelPosition) { this.labelPosition = labelPosition; } @StrutsTagAttribute( description = "Define required position of required form element (left|right)") public void setRequiredPosition(String requiredPosition) { this.requiredPosition = requiredPosition; } @StrutsTagAttribute(description = "Define error position of form element (top|bottom)") public void setErrorPosition(String errorPosition) { this.errorPosition = errorPosition; } @StrutsTagAttribute(description = "The name to set for element") public void setName(String name) { this.name = name; } @StrutsTagAttribute( description = "If set to true, the rendered element will indicate that input is required", type = "Boolean", defaultValue = "false") public void setRequiredLabel(String requiredLabel) { this.requiredLabel = requiredLabel; } @StrutsTagAttribute(description = "Set the html tabindex attribute on rendered html element") public void setTabindex(String tabindex) { this.tabindex = tabindex; } @StrutsTagAttribute(description = "Preset the value of input element.") public void setValue(String value) { this.value = value; } @StrutsTagAttribute(description = "Set the html onclick attribute on rendered html element") public void setOnclick(String onclick) { this.onclick = onclick; } @StrutsTagAttribute(description = "Set the html ondblclick attribute on rendered html element") public void setOndblclick(String ondblclick) { this.ondblclick = ondblclick; } @StrutsTagAttribute(description = "Set the html onmousedown attribute on rendered html element") public void setOnmousedown(String onmousedown) { this.onmousedown = onmousedown; } @StrutsTagAttribute(description = "Set the html onmouseup attribute on rendered html element") public void setOnmouseup(String onmouseup) { this.onmouseup = onmouseup; } @StrutsTagAttribute(description = "Set the html onmouseover attribute on rendered html element") public void setOnmouseover(String onmouseover) { this.onmouseover = onmouseover; } @StrutsTagAttribute(description = "Set the html onmousemove attribute on rendered html element") public void setOnmousemove(String onmousemove) { this.onmousemove = onmousemove; } @StrutsTagAttribute(description = "Set the html onmouseout attribute on rendered html element") public void setOnmouseout(String onmouseout) { this.onmouseout = onmouseout; } @StrutsTagAttribute(description = "Set the html onfocus attribute on rendered html element") public void setOnfocus(String onfocus) { this.onfocus = onfocus; } @StrutsTagAttribute(description = " Set the html onblur attribute on rendered html element") public void setOnblur(String onblur) { this.onblur = onblur; } @StrutsTagAttribute(description = "Set the html onkeypress attribute on rendered html element") public void setOnkeypress(String onkeypress) { this.onkeypress = onkeypress; } @StrutsTagAttribute(description = "Set the html onkeydown attribute on rendered html element") public void setOnkeydown(String onkeydown) { this.onkeydown = onkeydown; } @StrutsTagAttribute(description = "Set the html onkeyup attribute on rendered html element") public void setOnkeyup(String onkeyup) { this.onkeyup = onkeyup; } @StrutsTagAttribute(description = "Set the html onselect attribute on rendered html element") public void setOnselect(String onselect) { this.onselect = onselect; } @StrutsTagAttribute(description = "Set the html onchange attribute on rendered html element") public void setOnchange(String onchange) { this.onchange = onchange; } @StrutsTagAttribute(description = "Set the html accesskey attribute on rendered html element") public void setAccesskey(String accesskey) { this.accesskey = accesskey; } @StrutsTagAttribute(description = "Set the tooltip of this particular component") public void setTooltip(String tooltip) { this.tooltip = tooltip; } @StrutsTagAttribute( description = "Deprecated. Use individual tooltip configuration attributes instead.") public void setTooltipConfig(String tooltipConfig) { this.tooltipConfig = tooltipConfig; } @StrutsTagAttribute( description = "Set the key (name, value, label) for this particular component") public void setKey(String key) { this.key = key; } @StrutsTagAttribute( description = "Use JavaScript to generate tooltips", type = "Boolean", defaultValue = "false") public void setJavascriptTooltip(String javascriptTooltip) { this.javascriptTooltip = javascriptTooltip; } @StrutsTagAttribute( description = "CSS class applied to JavaScrip tooltips", defaultValue = "StrutsTTClassic") public void setTooltipCssClass(String tooltipCssClass) { this.tooltipCssClass = tooltipCssClass; } @StrutsTagAttribute( description = "Delay in milliseconds, before showing JavaScript tooltips ", defaultValue = "Classic") public void setTooltipDelay(String tooltipDelay) { this.tooltipDelay = tooltipDelay; } @StrutsTagAttribute(description = "Icon path used for image that will have the tooltip") public void setTooltipIconPath(String tooltipIconPath) { this.tooltipIconPath = tooltipIconPath; } public void setDynamicAttributes(Map<String, Object> dynamicAttributes) { this.dynamicAttributes.putAll(dynamicAttributes); } @Override /** * supports dynamic attributes for freemarker ui tags * * @see https://issues.apache.org/jira/browse/WW-3174 */ public void copyParams(Map params) { super.copyParams(params); Set<String> standardAttributes = getStandardAttributes(); for (Object o : params.entrySet()) { Map.Entry entry = (Map.Entry) o; String key = (String) entry.getKey(); if (!key.equals("dynamicAttributes") && !standardAttributes.contains(key)) { dynamicAttributes.put(key, entry.getValue()); } } } protected Set<String> getStandardAttributes() { Class clz = getClass(); Set<String> standardAttributes = standardAttributesMap.get(clz); if (standardAttributes == null) { standardAttributes = new HashSet<String>(); while (clz != null) { for (Field f : clz.getDeclaredFields()) { if (Modifier.isProtected(f.getModifiers()) && (f.getType().equals(String.class) || clz.equals(ListUIBean.class) && f.getName().equals("list"))) standardAttributes.add(f.getName()); } if (clz.equals(UIBean.class)) { break; } else { clz = clz.getSuperclass(); } } standardAttributesMap.putIfAbsent(clz, standardAttributes); } return standardAttributes; } }
public class VelocityLayoutResult extends VelocityResult { public static final String PROPERTY_DEFAULT_LAYOUT = "tools.view.servlet.layout.default.template"; public static final String PROPERTY_LAYOUT_DIR = "tools.view.servlet.layout.directory"; public static final String PROPERTY_INPUT_ENCODING = "input.encoding"; public static final String PROPERTY_OUTPUT_ENCODING = "output.encoding"; public static final String PROPERTY_CONTENT_TYPE = "default.contentType"; private static final long serialVersionUID = 6020934292083047099L; private static final Logger logger = LoggerFactory.getLogger(VelocityLayoutResult.class); public static String KEY_SCREEN_CONTENT = "screen_content"; public static String KEY_LAYOUT = "layout"; protected VelocityManager velocityManager; protected String defaultLayout; protected String layoutDir; protected String inputEncoding; protected String outputEncoding; protected String contentType; @Override public void setVelocityManager(VelocityManager mgr) { this.velocityManager = mgr; super.setVelocityManager(mgr); } @Override public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { ValueStack stack = ActionContext.getContext().getValueStack(); HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); JspFactory jspFactory = null; ServletContext servletContext = ServletActionContext.getServletContext(); Servlet servlet = JspSupportServlet.jspSupportServlet; velocityManager.init(servletContext); boolean usedJspFactory = false; PageContext pageContext = (PageContext) ActionContext.getContext().get(ServletActionContext.PAGE_CONTEXT); if (pageContext == null && servlet != null) { jspFactory = JspFactory.getDefaultFactory(); pageContext = jspFactory.getPageContext(servlet, request, response, null, true, 8192, true); ActionContext.getContext().put(ServletActionContext.PAGE_CONTEXT, pageContext); usedJspFactory = true; } try { String encoding = getEncoding(finalLocation); String contentType = getContentType(finalLocation); if (encoding != null) { contentType = contentType + ";charset=" + encoding; } Template t = getTemplate( stack, velocityManager.getVelocityEngine(), invocation, finalLocation, encoding); Context context = createContext(velocityManager, stack, request, response, finalLocation); StringWriter stringWriter = new StringWriter(); t.merge(context, stringWriter); context.put(KEY_SCREEN_CONTENT, stringWriter.toString()); Object obj = context.get(KEY_LAYOUT); String layout = (obj == null) ? null : obj.toString(); defaultLayout = (String) velocityManager.getVelocityEngine().getProperty(PROPERTY_DEFAULT_LAYOUT); layoutDir = (String) velocityManager.getVelocityEngine().getProperty(PROPERTY_LAYOUT_DIR); if (!layoutDir.endsWith("/")) { layoutDir += '/'; } if (!layoutDir.startsWith("/")) { layoutDir = "/" + layoutDir; } defaultLayout = layoutDir + defaultLayout; if (layout == null) { layout = defaultLayout; } else { layout = layoutDir + layout; } Template layoutVm = null; try { inputEncoding = (String) velocityManager.getVelocityEngine().getProperty(PROPERTY_INPUT_ENCODING); layoutVm = getTemplate( stack, velocityManager.getVelocityEngine(), invocation, layout, inputEncoding); } catch (Exception e) { logger.error("VelocityLayoutResult: Can't load layout \"" + layout + "\": " + e); if (!layout.equals(defaultLayout)) { layoutVm = getTemplate( stack, velocityManager.getVelocityEngine(), invocation, defaultLayout, inputEncoding); } } outputEncoding = (String) velocityManager.getVelocityEngine().getProperty(PROPERTY_OUTPUT_ENCODING); Writer writer = new OutputStreamWriter(response.getOutputStream(), outputEncoding); response.setContentType(contentType); layoutVm.merge(context, writer); writer.flush(); } catch (Exception e) { logger.error("Unable to render Velocity Template, '" + finalLocation + "'", e); throw e; } finally { if (usedJspFactory) { jspFactory.releasePageContext(pageContext); } } return; } }
/** * A utility class for invoking prefixed methods in action class. * * <p>Interceptors that made use of this class are: * * <ul> * <li>DefaultWorkflowInterceptor * <li>PrepareInterceptor * </ul> * * <p> * <!-- START SNIPPET: javadocDefaultWorkflowInterceptor --> * <b>In DefaultWorkflowInterceptor</b> * * <p>applies only when action implements {@link com.opensymphony.xwork2.Validateable} * * <ol> * <li>if the action class have validate{MethodName}(), it will be invoked * <li>else if the action class have validateDo{MethodName}(), it will be invoked * <li>no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is * "true" (which is by default "true"), validate() will be invoked. * </ol> * * <!-- END SNIPPET: javadocDefaultWorkflowInterceptor --> * <!-- START SNIPPET: javadocPrepareInterceptor --> * <b>In PrepareInterceptor</b> * * <p>Applies only when action implements Preparable * * <ol> * <li>if the action class have prepare{MethodName}(), it will be invoked * <li>else if the action class have prepareDo(MethodName()}(), it will be invoked * <li>no matter if 1] or 2] is performed, if alwaysinvokePrepare property of the interceptor is * "true" (which is by default "true"), prepare() will be invoked. * </ol> * * <!-- END SNIPPET: javadocPrepareInterceptor --> * * @author Philip Luppens * @author tm_jee */ public class PrefixMethodInvocationUtil { private static final Logger LOG = LoggerFactory.getLogger(PrefixMethodInvocationUtil.class); private static final String DEFAULT_INVOCATION_METHODNAME = "execute"; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; /** * This method will prefix <code>actionInvocation</code>'s <code>ActionProxy</code>'s <code>method * </code> with <code>prefixes</code> before invoking the prefixed method. Order of the <code> * prefixes</code> is important, as this method will return once a prefixed method is found in the * action class. * * <p>For example, with * * <pre> * invokePrefixMethod(actionInvocation, new String[] { "prepare", "prepareDo" }); * </pre> * * Assuming <code>actionInvocation.getProxy(),getMethod()</code> returns "submit", the order of * invocation would be as follows:- * * <ol> * <li>prepareSubmit() * <li>prepareDoSubmit() * </ol> * * If <code>prepareSubmit()</code> exists, it will be invoked and this method will return, <code> * prepareDoSubmit()</code> will NOT be invoked. * * <p>On the other hand, if <code>prepareDoSubmit()</code> does not exists, and <code> * prepareDoSubmit()</code> exists, it will be invoked. * * <p>If none of those two methods exists, nothing will be invoked. * * @param actionInvocation the action invocation * @param prefixes prefixes for method names * @throws InvocationTargetException is thrown if invocation of a method failed. * @throws IllegalAccessException is thrown if invocation of a method failed. */ public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException { Object action = actionInvocation.getAction(); String methodName = actionInvocation.getProxy().getMethod(); if (methodName == null) { // if null returns (possible according to the docs), use the default execute methodName = DEFAULT_INVOCATION_METHODNAME; } Method method = getPrefixedMethod(prefixes, methodName, action); if (method != null) { method.invoke(action, new Object[0]); } } /** * This method returns a {@link Method} in <code>action</code>. The method returned is found by * searching for method in <code>action</code> whose method name is equals to the result of * appending each <code>prefixes</code> to <code>methodName</code>. Only the first method found * will be returned, hence the order of <code>prefixes</code> is important. If none is found this * method will return null. * * @param prefixes the prefixes to prefix the <code>methodName</code> * @param methodName the method name to be prefixed with <code>prefixes</code> * @param action the action class of which the prefixed method is to be search for. * @return a {@link Method} if one is found, else <tt>null</tt>. */ public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) { assert (prefixes != null); String capitalizedMethodName = capitalizeMethodName(methodName); for (String prefixe : prefixes) { String prefixedMethodName = prefixe + capitalizedMethodName; try { return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { // hmm -- OK, try next prefix if (LOG.isDebugEnabled()) { LOG.debug("cannot find method [" + prefixedMethodName + "] in action [" + action + "]"); } } } return null; } /** * This method capitalized the first character of <code>methodName</code>. <br> * eg. <code>capitalizeMethodName("someMethod");</code> will return <code>"SomeMethod"</code>. * * @param methodName the method name * @return capitalized method name */ public static String capitalizeMethodName(String methodName) { assert (methodName != null); return methodName.substring(0, 1).toUpperCase() + methodName.substring(1); } }
/** * * <!-- START SNIPPET: javadoc --> * <b>NOTE: JSP-TAG</b> * * <p>Generate an iterator based on the val attribute supplied. <b>NOTE:</b> The generated iterator * will <b>ALWAYS</b> be pushed into the top of the stack, and poped at the end of the tag. * <!-- END SNIPPET: javadoc --> * <!-- START SNIPPET: params --> * * <ul> * <li>val* (Object) - the source to be parsed into an iterator * <li>count (Object) - the max number (Integer, Float, Double, Long, String) entries to be in the * iterator * <li>separator (String) - the separator to be used in separating the <i>val</i> into entries of * the iterator * <li>var (String) - the name to store the resultant iterator into page context, if such name is * supplied * <li>converter (Object) - the converter (must extends off IteratorGenerator.Converter interface) * to convert the String entry parsed from <i>val</i> into an object * </ul> * * <!-- END SNIPPET: params --> * <!-- START SNIPPET: example --> * Example One: * * <pre> * Generate a simple iterator * <s:generator val="%{'aaa,bbb,ccc,ddd,eee'}"> * <s:iterator> * <s:property /><br/> * </s:iterator> * </s:generator> * </pre> * * This generates an iterator and print it out using the iterator tag. * * <p>Example Two: * * <pre> * Generate an iterator with count attribute * <s:generator val="%{'aaa,bbb,ccc,ddd,eee'}" count="3"> * <s:iterator> * <s:property /><br/> * </s:iterator> * </s:generator> * </pre> * * This generates an iterator, but only 3 entries will be available in the iterator generated, * namely aaa, bbb and ccc respectively because count attribute is set to 3 * * <p>Example Three: * * <pre> * Generate an iterator with var attribute * <s:generator val="%{'aaa,bbb,ccc,ddd,eee'}" count="4" separator="," var="myAtt" /> * <% * Iterator i = (Iterator) pageContext.getAttribute("myAtt"); * while(i.hasNext()) { * String s = (String) i.next(); %> * <%=s%> <br/> * <% } * %> * </pre> * * This generates an iterator and put it in the PageContext under the key as specified by the var * attribute. * * <p>Example Four: * * <pre> * Generate an iterator with comparator attribute * <s:generator val="%{'aaa,bbb,ccc,ddd,eee'}" converter="%{myConverter}"> * <s:iterator> * <s:property /><br/> * </s:iterator> * </s:generator> * * * public class GeneratorTagAction extends ActionSupport { * * .... * * public Converter getMyConverter() { * return new Converter() { * public Object convert(String value) throws Exception { * return "converter-"+value; * } * }; * } * * ... * * } * </pre> * * This will generate an iterator with each entries decided by the converter supplied. With this * converter, it simply add "converter-" to each entries. * <!-- END SNIPPET: example --> * * @see org.apache.struts2.util.IteratorGenerator */ @StrutsTag( name = "generator", tldTagClass = "org.apache.struts2.views.jsp.iterator.IteratorGeneratorTag", description = "Generate an iterator for a iterable source.") public class IteratorGeneratorTag extends StrutsBodyTagSupport { private static final long serialVersionUID = 2968037295463973936L; public static final String DEFAULT_SEPARATOR = ","; private static final Logger LOG = LoggerFactory.getLogger(IteratorGeneratorTag.class); String countAttr; String separatorAttr; String valueAttr; String converterAttr; String var; IteratorGenerator iteratorGenerator = null; @StrutsTagAttribute( type = "Integer", description = "The max number entries to be in the iterator") public void setCount(String count) { countAttr = count; } /** * @s.tagattribute required="true" type="String" description="the separator to be used in * separating the <i>val</i> into entries of the iterator" */ @StrutsTagAttribute( required = true, description = "The separator to be used in separating the <i>val</i> into entries of the iterator") public void setSeparator(String separator) { separatorAttr = separator; } /** @s.tagattribute required="true" description="the source to be parsed into an iterator" */ @StrutsTagAttribute(required = true, description = "The source to be parsed into an iterator") public void setVal(String val) { valueAttr = val; } @StrutsTagAttribute( type = "org.apache.struts2.util.IteratorGenerator.Converter", description = "The converter to convert the String entry parsed from <i>val</i> into an object") public void setConverter(String aConverter) { converterAttr = aConverter; } @StrutsTagAttribute(description = "Deprecated. Use 'var' instead") public void setId(String string) { setVar(string); } @StrutsTagAttribute( description = "The name to store the resultant iterator into page context, if such name is supplied") public void setVar(String var) { this.var = var; } public int doStartTag() throws JspException { // value Object value = findValue(valueAttr); // separator String separator = DEFAULT_SEPARATOR; if (separatorAttr != null && separatorAttr.length() > 0) { separator = findString(separatorAttr); } // TODO: maybe this could be put into an Util class, or there is already one? // count int count = 0; if (countAttr != null && countAttr.length() > 0) { Object countObj = findValue(countAttr); if (countObj instanceof Number) { count = ((Number) countObj).intValue(); } else if (countObj instanceof String) { try { count = Integer.parseInt((String) countObj); } catch (NumberFormatException e) { LOG.warn( "unable to convert count attribute [" + countObj + "] to number, ignore count attribute", e); } } } // converter Converter converter = null; if (converterAttr != null && converterAttr.length() > 0) { converter = (Converter) findValue(converterAttr); } iteratorGenerator = new IteratorGenerator(); iteratorGenerator.setValues(value); iteratorGenerator.setCount(count); iteratorGenerator.setSeparator(separator); iteratorGenerator.setConverter(converter); iteratorGenerator.execute(); // Push resulting iterator on stack and put into // stack context if we have a "var" specified. getStack().push(iteratorGenerator); if (var != null && var.length() > 0) { getStack().getContext().put(var, iteratorGenerator); } return EVAL_BODY_INCLUDE; } public int doEndTag() throws JspException { // pop resulting iterator from stack at end tag getStack().pop(); iteratorGenerator = null; // clean up return EVAL_PAGE; } }
/** * Serializes an object into JavaScript Object Notation (JSON). If cyclic references are detected * they will be nulled out. */ class JSONWriter { private static final Logger LOG = LoggerFactory.getLogger(JSONWriter.class); /** By default, enums are serialzied as name=value pairs */ public static final boolean ENUM_AS_BEAN_DEFAULT = false; static char[] hex = "0123456789ABCDEF".toCharArray(); private StringBuilder buf = new StringBuilder(); private Stack stack = new Stack(); private boolean ignoreHierarchy = true; private Object root; private boolean buildExpr = true; private String exprStack = ""; private Collection<Pattern> excludeProperties; private Collection<Pattern> includeProperties; private DateFormat formatter; private boolean enumAsBean = ENUM_AS_BEAN_DEFAULT; private boolean excludeNullProperties; /** * @param object Object to be serialized into JSON * @return JSON string for object * @throws JSONException */ public String write(Object object) throws JSONException { return this.write(object, null, null, false); } /** * @param object Object to be serialized into JSON * @return JSON string for object * @throws JSONException */ public String write( Object object, Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties, boolean excludeNullProperties) throws JSONException { this.excludeNullProperties = excludeNullProperties; this.buf.setLength(0); this.root = object; this.exprStack = ""; this.buildExpr = ((excludeProperties != null) && !excludeProperties.isEmpty()) || ((includeProperties != null) && !includeProperties.isEmpty()); this.excludeProperties = excludeProperties; this.includeProperties = includeProperties; this.value(object, null); return this.buf.toString(); } /** Detect cyclic references */ private void value(Object object, Method method) throws JSONException { if (object == null) { this.add("null"); return; } if (this.stack.contains(object)) { Class clazz = object.getClass(); // cyclic reference if (clazz.isPrimitive() || clazz.equals(String.class)) { this.process(object, method); } else { if (LOG.isDebugEnabled()) { LOG.debug("Cyclic reference detected on " + object); } this.add("null"); } return; } this.process(object, method); } /** Serialize object into json */ private void process(Object object, Method method) throws JSONException { this.stack.push(object); DecimalFormat df = new DecimalFormat("#.##"); if (object instanceof Class) { this.string(object); } else if (object instanceof Boolean) { this.bool(((Boolean) object).booleanValue()); } else if (object instanceof Number) { this.add(df.format(object)); } else if (object instanceof String) { this.string(object); } else if (object instanceof Character) { this.string(object); } else if (object instanceof Map) { this.map((Map) object, method); } else if (object.getClass().isArray()) { this.array(object, method); } else if (object instanceof Iterable) { this.array(((Iterable) object).iterator(), method); } else if (object instanceof Date) { this.date((Date) object, method); } else if (object instanceof Calendar) { this.date(((Calendar) object).getTime(), method); } else if (object instanceof Locale) { this.string(object); } else if (object instanceof Enum) { this.enumeration((Enum) object); } else { this.bean(object); } this.stack.pop(); } /** Instrospect bean and serialize its properties */ private void bean(Object object) throws JSONException { this.add("{"); BeanInfo info; try { Class clazz = object.getClass(); info = ((object == this.root) && this.ignoreHierarchy) ? Introspector.getBeanInfo(clazz, clazz.getSuperclass()) : Introspector.getBeanInfo(clazz); PropertyDescriptor[] props = info.getPropertyDescriptors(); boolean hasData = false; for (int i = 0; i < props.length; ++i) { PropertyDescriptor prop = props[i]; String name = prop.getName(); Method accessor = prop.getReadMethod(); Method baseAccessor = null; if (clazz.getName().indexOf("$$EnhancerByCGLIB$$") > -1) { try { baseAccessor = Class.forName(clazz.getName().substring(0, clazz.getName().indexOf("$$"))) .getMethod(accessor.getName(), accessor.getParameterTypes()); } catch (Exception ex) { LOG.debug(ex.getMessage(), ex); } } else baseAccessor = accessor; if (baseAccessor != null) { JSON json = baseAccessor.getAnnotation(JSON.class); if (json != null) { if (!json.serialize()) continue; else if (json.name().length() > 0) name = json.name(); } // ignore "class" and others if (this.shouldExcludeProperty(clazz, prop)) { continue; } String expr = null; if (this.buildExpr) { expr = this.expandExpr(name); if (this.shouldExcludeProperty(expr)) { continue; } expr = this.setExprStack(expr); } Object value = accessor.invoke(object, new Object[0]); boolean propertyPrinted = this.add(name, value, accessor, hasData); hasData = hasData || propertyPrinted; if (this.buildExpr) { this.setExprStack(expr); } } } // special-case handling for an Enumeration - include the name() as // a property */ if (object instanceof Enum) { Object value = ((Enum) object).name(); this.add("_name", value, object.getClass().getMethod("name"), hasData); } } catch (Exception e) { throw new JSONException(e); } this.add("}"); } /** * Instrospect an Enum and serialize it as a name/value pair or as a bean including all its own * properties */ private void enumeration(Enum enumeration) throws JSONException { if (enumAsBean) { this.bean(enumeration); } else { this.string(enumeration.name()); } } /** Ignore "class" field */ private boolean shouldExcludeProperty(Class clazz, PropertyDescriptor prop) throws SecurityException, NoSuchFieldException { String name = prop.getName(); if (name.equals("class") || name.equals("declaringClass") || name.equals("cachedSuperClass") || name.equals("metaClass")) { return true; } return false; } private String expandExpr(int i) { return this.exprStack + "[" + i + "]"; } private String expandExpr(String property) { if (this.exprStack.length() == 0) return property; return this.exprStack + "." + property; } private String setExprStack(String expr) { String s = this.exprStack; this.exprStack = expr; return s; } private boolean shouldExcludeProperty(String expr) { if (this.excludeProperties != null) { for (Pattern pattern : this.excludeProperties) { if (pattern.matcher(expr).matches()) { if (LOG.isDebugEnabled()) LOG.debug("Ignoring property because of exclude rule: " + expr); return true; } } } if (this.includeProperties != null) { for (Pattern pattern : this.includeProperties) { if (pattern.matcher(expr).matches()) { return false; } } if (LOG.isDebugEnabled()) LOG.debug("Ignoring property because of include rule: " + expr); return true; } return false; } /** Add name/value pair to buffer */ private boolean add(String name, Object value, Method method, boolean hasData) throws JSONException { if (!excludeNullProperties || (value != null)) { if (hasData) { this.add(','); } this.add('"'); this.add(name); this.add("\":"); this.value(value, method); return true; } return false; } /** Add map to buffer */ private void map(Map map, Method method) throws JSONException { this.add("{"); Iterator it = map.entrySet().iterator(); boolean warnedNonString = false; // one report per map boolean hasData = false; while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); Object key = entry.getKey(); String expr = null; if (this.buildExpr) { if (key == null) { LOG.error("Cannot build expression for null key in " + this.exprStack); continue; } else { expr = this.expandExpr(key.toString()); if (this.shouldExcludeProperty(expr)) { continue; } expr = this.setExprStack(expr); } } if (hasData) { this.add(','); } hasData = true; if (!warnedNonString && !(key instanceof String)) { LOG.warn( "JavaScript doesn't support non-String keys, using toString() on " + key.getClass().getName()); warnedNonString = true; } this.value(key.toString(), method); this.add(":"); this.value(entry.getValue(), method); if (this.buildExpr) { this.setExprStack(expr); } } this.add("}"); } /** Add date to buffer */ private void date(Date date, Method method) { JSON json = null; if (method != null) json = method.getAnnotation(JSON.class); if (this.formatter == null) this.formatter = new SimpleDateFormat(JSONUtil.RFC3339_FORMAT); DateFormat formatter = (json != null) && (json.format().length() > 0) ? new SimpleDateFormat(json.format()) : this.formatter; this.string(formatter.format(date)); } /** Add array to buffer */ private void array(Iterator it, Method method) throws JSONException { this.add("["); boolean hasData = false; for (int i = 0; it.hasNext(); i++) { String expr = null; if (this.buildExpr) { expr = this.expandExpr(i); if (this.shouldExcludeProperty(expr)) { it.next(); continue; } expr = this.setExprStack(expr); } if (hasData) { this.add(','); } hasData = true; this.value(it.next(), method); if (this.buildExpr) { this.setExprStack(expr); } } this.add("]"); } /** Add array to buffer */ private void array(Object object, Method method) throws JSONException { this.add("["); int length = Array.getLength(object); boolean hasData = false; for (int i = 0; i < length; ++i) { String expr = null; if (this.buildExpr) { expr = this.expandExpr(i); if (this.shouldExcludeProperty(expr)) { continue; } expr = this.setExprStack(expr); } if (hasData) { this.add(','); } hasData = true; this.value(Array.get(object, i), method); if (this.buildExpr) { this.setExprStack(expr); } } this.add("]"); } /** Add boolean to buffer */ private void bool(boolean b) { this.add(b ? "true" : "false"); } /** escape characters */ private void string(Object obj) { this.add('"'); CharacterIterator it = new StringCharacterIterator(obj.toString()); for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { if (c == '"') { this.add("\\\""); } else if (c == '\\') { this.add("\\\\"); } else if (c == '/') { this.add("\\/"); } else if (c == '\b') { this.add("\\b"); } else if (c == '\f') { this.add("\\f"); } else if (c == '\n') { this.add("\\n"); } else if (c == '\r') { this.add("\\r"); } else if (c == '\t') { this.add("\\t"); } else if (Character.isISOControl(c)) { this.unicode(c); } else { this.add(c); } } this.add('"'); } /** Add object to buffer */ private void add(Object obj) { this.buf.append(obj); } /** Add char to buffer */ private void add(char c) { this.buf.append(c); } /** * Represent as unicode * * @param c character to be encoded */ private void unicode(char c) { this.add("\\u"); int n = c; for (int i = 0; i < 4; ++i) { int digit = (n & 0xf000) >> 12; this.add(hex[digit]); n <<= 4; } } public void setIgnoreHierarchy(boolean ignoreHierarchy) { this.ignoreHierarchy = ignoreHierarchy; } /** * If true, an Enum is serialized as a bean with a special property _name=name() as all as all * other properties defined within the enum.<br> * If false, an Enum is serialized as a name=value pair (name=name()) * * @param enumAsBean true to serialize an enum as a bean instead of as a name=value pair * (default=false) */ public void setEnumAsBean(boolean enumAsBean) { this.enumAsBean = enumAsBean; } }
/** * Contains preparation operations for a request before execution * * <p>做一些预处理工作,如: * <li>创建action context,并设置thread local, {@link #createActionContext(HttpServletRequest, * HttpServletResponse)} * <li>设置请求编码, {@link #setEncodingAndLocale(HttpServletRequest, HttpServletResponse)} * <li>包装request , {@link #wrapRequest(HttpServletRequest)} * <li>释放request资源(如由MultiPartRequestWrapper创建的文件); 设置 ActionContext.setContext(null); * Dispatcher.setInstance(null); , 详情见 {@link #cleanupRequest(HttpServletRequest)} * <li>其他等等... */ public class PrepareOperations { private static final Logger LOG = LoggerFactory.getLogger(PrepareOperations.class); private ServletContext servletContext; private Dispatcher dispatcher; private static final String STRUTS_ACTION_MAPPING_KEY = "struts.actionMapping"; public static final String CLEANUP_RECURSION_COUNTER = "__cleanup_recursion_counter"; private Logger log = LoggerFactory.getLogger(PrepareOperations.class); public PrepareOperations(ServletContext servletContext, Dispatcher dispatcher) { this.dispatcher = dispatcher; this.servletContext = servletContext; } /** Creates the action context and initializes the thread local */ public ActionContext createActionContext( HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack .getContext() .putAll(dispatcher.createContextMap(request, response, null, servletContext)); // 利用ValueStack的context属性实例化ActionContext ctx = new ActionContext(stack.getContext()); } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; } /** Cleans up a request of thread locals */ public void cleanupRequest(HttpServletRequest request) { Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (counterVal != null) { counterVal -= 1; request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal); if (counterVal > 0) { if (log.isDebugEnabled()) { log.debug("skipping cleanup counter=" + counterVal); } return; } } // always clean up the thread request, even if an action hasn't been executed try { dispatcher.cleanUpRequest(request); } catch (IOException e) { if (LOG.isWarnEnabled()) { LOG.warn( "Cannot clean up the request, some files can still remain in #0 after upload!", e, StrutsConstants.STRUTS_MULTIPART_SAVEDIR); } } finally { ActionContext.setContext(null); Dispatcher.setInstance(null); } } /** Assigns the dispatcher to the dispatcher thread local */ public void assignDispatcherToThread() { Dispatcher.setInstance(dispatcher); } /** Sets the request encoding and locale on the response */ public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { dispatcher.prepare(request, response); } /** * Wraps the request with the Struts wrapper that handles multipart requests better * * @return The new request, if there is one * @throws ServletException */ public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { HttpServletRequest request = oldRequest; try { // Wrap request first, just in case it is multipart/form-data // parameters might not be accessible through before encoding (ww-1278) request = dispatcher.wrapRequest(request, servletContext); } catch (IOException e) { String message = "Could not wrap servlet request with MultipartRequestWrapper!"; throw new ServletException(message, e); } return request; } /** * Finds and optionally creates an {@link ActionMapping}. It first looks in the current request to * see if one has already been found, otherwise, it creates it and stores it in the request. No * mapping will be created in the case of static resource requests or unidentifiable requests for * other servlets, for example. */ public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response) { return findActionMapping(request, response, false); } /** * * <li>forceLookup 为false时,优先返回(ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY) * <li>forceLookup 为true时,每次都去获取ActionMapper实例 <br> * <br> * Finds and optionally creates an {@link ActionMapping}. if forceLookup is false, it first * looks in the current request to see if one has already been found, otherwise, it creates it * and stores it in the request. No mapping will be created in the case of static resource * requests or unidentifiable requests for other servlets, for example. * * @param forceLookup if true, the action mapping will be looked up from the ActionMapper * instance, ignoring if there is one in the request or not */ public ActionMapping findActionMapping( HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { // ActionMapper默认实例是{ @link org.apache.struts2.dispatcher.mapper.DefaultActionMapper} mapping = dispatcher .getContainer() .getInstance(ActionMapper.class) .getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { // 设置mapping到request属性, request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { dispatcher.sendError( request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } return mapping; } /** Cleans up the dispatcher instance */ public void cleanupDispatcher() { if (dispatcher == null) { throw new StrutsException( "Something is seriously wrong, Dispatcher is not initialized (null) "); } else { try { dispatcher.cleanup(); } finally { ActionContext.setContext(null); } } } /** * Check whether the request matches a list of exclude patterns. * * @param request The request to check patterns against * @param excludedPatterns list of patterns for exclusion * @return <tt>true</tt> if the request URI matches one of the given patterns */ public boolean isUrlExcluded(HttpServletRequest request, List<Pattern> excludedPatterns) { if (excludedPatterns != null) { String uri = getUri(request); for (Pattern pattern : excludedPatterns) { if (pattern.matcher(uri).matches()) { return true; } } } return false; } /** * Gets the uri from the request * * @param request The request * @return The uri */ private String getUri(HttpServletRequest request) { // handle http dispatcher includes. String uri = (String) request.getAttribute("javax.servlet.include.servlet_path"); if (uri != null) { return uri; } uri = RequestUtils.getServletPath(request); if (uri != null && !"".equals(uri)) { return uri; } uri = request.getRequestURI(); return uri.substring(request.getContextPath().length()); } }
/** * Base class for range based validators. * * @author Jason Carreira * @author Cameron Braid */ public abstract class AbstractRangeValidator<T extends Comparable> extends FieldValidatorSupport { private static final Logger LOG = LoggerFactory.getLogger(AbstractRangeValidator.class); private final Class<T> type; private T min; private String minExpression; private T max; private String maxExpression; protected AbstractRangeValidator(Class<T> type) { this.type = type; } public void validate(Object object) throws ValidationException { Object obj = getFieldValue(getFieldName(), object); Comparable<T> value = (Comparable<T>) obj; // if there is no value - don't do comparison // if a value is required, a required validator should be added to the field if (value == null) { return; } // only check for a minimum value if the min parameter is set T minComparatorValue = getMin(); if ((minComparatorValue != null) && (value.compareTo(minComparatorValue) < 0)) { addFieldError(getFieldName(), object); } // only check for a maximum value if the max parameter is set T maxComparatorValue = getMax(); if ((maxComparatorValue != null) && (value.compareTo(maxComparatorValue) > 0)) { addFieldError(getFieldName(), object); } } public void setMin(T min) { this.min = min; } public T getMin() { if (min != null) { return min; } else if (StringUtils.isNotEmpty(minExpression)) { return (T) parse(minExpression, type); } else { return null; } } public void setMinExpression(String minExpression) { if (LOG.isDebugEnabled()) { LOG.debug("${minExpression} was defined as [#0]", minExpression); } this.minExpression = minExpression; } public void setMax(T max) { this.max = max; } public T getMax() { if (max != null) { return max; } else if (StringUtils.isNotEmpty(maxExpression)) { return (T) parse(maxExpression, type); } else { return null; } } public void setMaxExpression(String maxExpression) { if (LOG.isDebugEnabled()) { LOG.debug("${maxExpression} was defined as [#0]", maxExpression); } this.maxExpression = maxExpression; } }
/** * * <!-- START SNIPPET: description --> * Improved restful action mapper that adds several ReST-style improvements to action mapping, but * supports fully-customized URL's via XML. The two primary ReST enhancements are: * * <ul> * <li>If the method is not specified (via '!' or 'method:' prefix), the method is "guessed" at * using ReST-style conventions that examine the URL and the HTTP method. * <li>Parameters are extracted from the action name, if parameter name/value pairs are specified * using PARAM_NAME/PARAM_VALUE syntax. * </ul> * * <p>These two improvements allow a GET request for 'category/action/movie/Thrillers' to be mapped * to the action name 'movie' with an id of 'Thrillers' with an extra parameter named 'category' * with a value of 'action'. A single action mapping can then handle all CRUD operations using * wildcards, e.g. * * <pre> * <action name="movie/*" className="app.MovieAction"> * <param name="id">{0}</param> * ... * </action> * </pre> * * <p>This mapper supports the following parameters: * * <ul> * <li><code>struts.mapper.idParameterName</code> - If set, this value will be the name of the * parameter under which the id is stored. The id will then be removed from the action name. * This allows restful actions to not require wildcards. * </ul> * * <p>The following URL's will invoke its methods: * * <ul> * <li><code>GET: /movie/ => method="index"</code> * <li><code>GET: /movie/Thrillers => method="view", id="Thrillers"</code> * <li><code>GET: /movie/Thrillers!edit => method="edit", id="Thrillers"</code> * <li><code>GET: /movie/new => method="editNew"</code> * <li><code>POST: /movie/ => method="create"</code> * <li><code>PUT: /movie/Thrillers => method="update", id="Thrillers"</code> * <li><code>DELETE: /movie/Thrillers => method="remove", id="Thrillers"</code> * </ul> * * <p>To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML, the HTTP * parameter "__http_method" will be used. * * <p>The syntax and design for this feature was inspired by the ReST support in Ruby on Rails. See * <a * href="http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it"> * http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it * </a> * <!-- END SNIPPET: description --> */ public class Restful2ActionMapper extends DefaultActionMapper { protected static final Logger LOG = LoggerFactory.getLogger(Restful2ActionMapper.class); public static final String HTTP_METHOD_PARAM = "__http_method"; private String idParameterName = null; public Restful2ActionMapper() { setSlashesInActionNames("true"); } /* * (non-Javadoc) * * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest) */ public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { if (!isSlashesInActionNames()) { throw new IllegalStateException( "This action mapper requires the setting 'slashesInActionNames' to be set to 'true'"); } ActionMapping mapping = super.getMapping(request, configManager); if (mapping == null) { return null; } String actionName = mapping.getName(); String id = null; // Only try something if the action name is specified if (actionName != null && actionName.length() > 0) { int lastSlashPos = actionName.lastIndexOf('/'); if (lastSlashPos > -1) { id = actionName.substring(lastSlashPos + 1); } // If a method hasn't been explicitly named, try to guess using ReST-style patterns if (mapping.getMethod() == null) { if (lastSlashPos == actionName.length() - 1) { // Index e.g. foo/ if (isGet(request)) { mapping.setMethod("index"); // Creating a new entry on POST e.g. foo/ } else if (isPost(request)) { mapping.setMethod("create"); } } else if (lastSlashPos > -1) { // Viewing the form to create a new item e.g. foo/new if (isGet(request) && "new".equals(id)) { mapping.setMethod("editNew"); // Viewing an item e.g. foo/1 } else if (isGet(request)) { mapping.setMethod("view"); // Removing an item e.g. foo/1 } else if (isDelete(request)) { mapping.setMethod("remove"); // Updating an item e.g. foo/1 } else if (isPut(request)) { mapping.setMethod("update"); } } if (idParameterName != null && lastSlashPos > -1) { actionName = actionName.substring(0, lastSlashPos); } } if (idParameterName != null && id != null) { if (mapping.getParams() == null) { mapping.setParams(new HashMap<String, Object>()); } mapping.getParams().put(idParameterName, id); } // Try to determine parameters from the url before the action name int actionSlashPos = actionName.lastIndexOf('/', lastSlashPos - 1); if (actionSlashPos > 0 && actionSlashPos < lastSlashPos) { String params = actionName.substring(0, actionSlashPos); HashMap<String, String> parameters = new HashMap<String, String>(); try { StringTokenizer st = new StringTokenizer(params, "/"); boolean isNameTok = true; String paramName = null; String paramValue; while (st.hasMoreTokens()) { if (isNameTok) { paramName = URLDecoder.decode(st.nextToken(), "UTF-8"); isNameTok = false; } else { paramValue = URLDecoder.decode(st.nextToken(), "UTF-8"); if ((paramName != null) && (paramName.length() > 0)) { parameters.put(paramName, paramValue); } isNameTok = true; } } if (parameters.size() > 0) { if (mapping.getParams() == null) { mapping.setParams(new HashMap<String, Object>()); } mapping.getParams().putAll(parameters); } } catch (Exception e) { if (LOG.isWarnEnabled()) { LOG.warn("Unable to determine parameters from the url", e); } } mapping.setName(actionName.substring(actionSlashPos + 1)); } } return mapping; } protected boolean isGet(HttpServletRequest request) { return "get".equalsIgnoreCase(request.getMethod()); } protected boolean isPost(HttpServletRequest request) { return "post".equalsIgnoreCase(request.getMethod()); } protected boolean isPut(HttpServletRequest request) { if ("put".equalsIgnoreCase(request.getMethod())) { return true; } else { return isPost(request) && "put".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM)); } } protected boolean isDelete(HttpServletRequest request) { if ("delete".equalsIgnoreCase(request.getMethod())) { return true; } else { return isPost(request) && "delete".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM)); } } public String getIdParameterName() { return idParameterName; } @Inject(required = false, value = StrutsConstants.STRUTS_ID_PARAMETER_NAME) public void setIdParameterName(String idParameterName) { this.idParameterName = idParameterName; } }
/** * * <!-- START SNIPPET: description --> * * <p>Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically * applied for any request that includes a file. It adds the following parameters, where [File Name] * is the name given to the file uploaded by the HTML form: * * <p> * * <ul> * <p> * <li>[File Name] : File - the actual File * <p> * <li>[File Name]ContentType : String - the content type of the file * <p> * <li>[File Name]FileName : String - the actual name of the file uploaded (not the HTML name) * <p> * </ul> * * <p> * * <p>You can get access to these files by merely providing setters in your action that correspond * to any of the three patterns above, such as setDocument(File document), * setDocumentContentType(String contentType), etc. <br> * See the example code section. * * <p> * * <p>This interceptor will add several field errors, assuming that the action implements {@link * ValidationAware}. These error messages are based on several i18n values stored in * struts-messages.properties, a default i18n file processed for all i18n requests. You can override * the text of these messages by providing text for the following keys: * * <p> * * <ul> * <p> * <li>struts.messages.error.uploading - a general error that occurs when the file could not be * uploaded * <p> * <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large * <p> * <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not * match the expected content types specified * <p> * <li>struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not * match the expected file extensions specified * <p> * </ul> * * <p> * <!-- END SNIPPET: description --> * * <p> * * <p><u>Interceptor parameters:</u> * * <p> * <!-- START SNIPPET: parameters --> * * <p> * * <ul> * <p> * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file * reference to be set on the action. Note, this is <b>not</b> related to the various * properties found in struts.properties. Default to approximately 2MB. * <p> * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the * interceptor will allow a file reference to be set on the action. If none is specified allow * all types to be uploaded. * <p> * <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that * the interceptor will allow a file reference to be set on the action. If none is specified * allow all extensions to be uploaded. * </ul> * * <p> * * <p> * <!-- END SNIPPET: parameters --> * * <p> * * <p><u>Extending the interceptor:</u> * * <p> * * <p> * * <p> * <!-- START SNIPPET: extending --> * * <p>You can extend this interceptor and override the acceptFile method to provide more control * over which files are supported and which are not. * * <p> * <!-- END SNIPPET: extending --> * * <p> * * <p><u>Example code:</u> * * <p> * * <pre> * <!-- START SNIPPET: example-configuration --> * <action name="doUpload" class="com.example.UploadAction"> * <interceptor-ref name="fileUpload"/> * <interceptor-ref name="basicStack"/> * <result name="success">good_result.jsp</result> * </action> * <!-- END SNIPPET: example-configuration --> * </pre> * * <p> * <!-- START SNIPPET: multipart-note --> * * <p>You must set the encoding to <code>multipart/form-data</code> in the form where the user * selects the file to upload. * * <p> * <!-- END SNIPPET: multipart-note --> * * <p> * * <pre> * <!-- START SNIPPET: example-form --> * <s:form action="doUpload" method="post" enctype="multipart/form-data"> * <s:file name="upload" label="File"/> * <s:submit/> * </s:form> * <!-- END SNIPPET: example-form --> * </pre> * * <p>And then in your action code you'll have access to the File object if you provide setters * according to the naming convention documented in the start. * * <p> * * <pre> * <!-- START SNIPPET: example-action --> * package com.example; * * import java.io.File; * import com.opensymphony.xwork2.ActionSupport; * * public UploadAction extends ActionSupport { * private File file; * private String contentType; * private String filename; * * public void setUpload(File file) { * this.file = file; * } * * public void setUploadContentType(String contentType) { * this.contentType = contentType; * } * * public void setUploadFileName(String filename) { * this.filename = filename; * } * * public String execute() { * //... * return SUCCESS; * } * } * <!-- END SNIPPET: example-action --> * </pre> */ public class FileUploadInterceptor extends AbstractInterceptor { private static final long serialVersionUID = -4764627478894962478L; protected static final Logger LOG = LoggerFactory.getLogger(FileUploadInterceptor.class); protected Long maximumSize; protected Set<String> allowedTypesSet = Collections.emptySet(); protected Set<String> allowedExtensionsSet = Collections.emptySet(); private PatternMatcher matcher; private Container container; @Inject public void setMatcher(PatternMatcher matcher) { this.matcher = matcher; } @Inject public void setContainer(Container container) { this.container = container; } /** * Sets the allowed extensions * * @param allowedExtensions A comma-delimited list of extensions */ public void setAllowedExtensions(String allowedExtensions) { allowedExtensionsSet = TextParseUtil.commaDelimitedStringToSet(allowedExtensions); } /** * Sets the allowed mimetypes * * @param allowedTypes A comma-delimited list of types */ public void setAllowedTypes(String allowedTypes) { allowedTypesSet = TextParseUtil.commaDelimitedStringToSet(allowedTypes); } /** * Sets the maximum size of an uploaded file * * @param maximumSize The maximum size in bytes */ public void setMaximumSize(Long maximumSize) { this.maximumSize = maximumSize; } /* (non-Javadoc) * @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation) */ public String intercept(ActionInvocation invocation) throws Exception { ActionContext ac = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST); if (!(request instanceof MultiPartRequestWrapper)) { if (LOG.isDebugEnabled()) { ActionProxy proxy = invocation.getProxy(); LOG.debug( getTextMessage( "struts.messages.bypass.request", new String[] {proxy.getNamespace(), proxy.getActionName()})); } return invocation.invoke(); } ValidationAware validation = null; Object action = invocation.getAction(); if (action instanceof ValidationAware) { validation = (ValidationAware) action; } MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request; if (multiWrapper.hasErrors()) { for (String error : multiWrapper.getErrors()) { if (validation != null) { validation.addActionError(error); } if (LOG.isWarnEnabled()) { LOG.warn(error); } } } // bind allowed Files Enumeration fileParameterNames = multiWrapper.getFileParameterNames(); while (fileParameterNames != null && fileParameterNames.hasMoreElements()) { // get the value of this input tag String inputName = (String) fileParameterNames.nextElement(); // get the content type String[] contentType = multiWrapper.getContentTypes(inputName); if (isNonEmpty(contentType)) { // get the name of the file from the input tag String[] fileName = multiWrapper.getFileNames(inputName); if (isNonEmpty(fileName)) { // get a File object for the uploaded File File[] files = multiWrapper.getFiles(inputName); if (files != null && files.length > 0) { List<File> acceptedFiles = new ArrayList<File>(files.length); List<String> acceptedContentTypes = new ArrayList<String>(files.length); List<String> acceptedFileNames = new ArrayList<String>(files.length); String contentTypeName = inputName + "ContentType"; String fileNameName = inputName + "FileName"; for (int index = 0; index < files.length; index++) { if (acceptFile( action, files[index], fileName[index], contentType[index], inputName, validation)) { acceptedFiles.add(files[index]); acceptedContentTypes.add(contentType[index]); acceptedFileNames.add(fileName[index]); } } if (!acceptedFiles.isEmpty()) { Map<String, Object> params = ac.getParameters(); params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()])); params.put( contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()])); params.put( fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()])); } } } else { if (LOG.isWarnEnabled()) { LOG.warn( getTextMessage(action, "struts.messages.invalid.file", new String[] {inputName})); } } } else { if (LOG.isWarnEnabled()) { LOG.warn( getTextMessage( action, "struts.messages.invalid.content.type", new String[] {inputName})); } } } // invoke action return invocation.invoke(); } /** * Override for added functionality. Checks if the proposed file is acceptable based on * contentType and size. * * @param action - uploading action for message retrieval. * @param file - proposed upload file. * @param contentType - contentType of the file. * @param inputName - inputName of the file. * @param validation - Non-null ValidationAware if the action implements ValidationAware, allowing * for better logging. * @return true if the proposed file is acceptable by contentType and size. */ protected boolean acceptFile( Object action, File file, String filename, String contentType, String inputName, ValidationAware validation) { boolean fileIsAcceptable = false; // If it's null the upload failed if (file == null) { String errMsg = getTextMessage(action, "struts.messages.error.uploading", new String[] {inputName}); if (validation != null) { validation.addFieldError(inputName, errMsg); } if (LOG.isWarnEnabled()) { LOG.warn(errMsg); } } else if (maximumSize != null && maximumSize < file.length()) { String errMsg = getTextMessage( action, "struts.messages.error.file.too.large", new String[] {inputName, filename, file.getName(), "" + file.length()}); if (validation != null) { validation.addFieldError(inputName, errMsg); } if (LOG.isWarnEnabled()) { LOG.warn(errMsg); } } else if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) { String errMsg = getTextMessage( action, "struts.messages.error.content.type.not.allowed", new String[] {inputName, filename, file.getName(), contentType}); if (validation != null) { validation.addFieldError(inputName, errMsg); } if (LOG.isWarnEnabled()) { LOG.warn(errMsg); } } else if ((!allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, filename))) { String errMsg = getTextMessage( action, "struts.messages.error.file.extension.not.allowed", new String[] {inputName, filename, file.getName(), contentType}); if (validation != null) { validation.addFieldError(inputName, errMsg); } if (LOG.isWarnEnabled()) { LOG.warn(errMsg); } } else { fileIsAcceptable = true; } return fileIsAcceptable; } /** * @param extensionCollection - Collection of extensions (all lowercase). * @param filename - filename to check. * @return true if the filename has an allowed extension, false otherwise. */ private boolean hasAllowedExtension(Collection<String> extensionCollection, String filename) { if (filename == null) { return false; } String lowercaseFilename = filename.toLowerCase(); for (String extension : extensionCollection) { if (lowercaseFilename.endsWith(extension)) { return true; } } return false; } /** * @param itemCollection - Collection of string items (all lowercase). * @param item - Item to search for. * @return true if itemCollection contains the item, false otherwise. */ private boolean containsItem(Collection<String> itemCollection, String item) { for (String pattern : itemCollection) if (matchesWildcard(pattern, item)) return true; return false; } private boolean matchesWildcard(String pattern, String text) { Object o = matcher.compilePattern(pattern); return matcher.match(new HashMap<String, String>(), text, o); } private boolean isNonEmpty(Object[] objArray) { boolean result = false; for (int index = 0; index < objArray.length && !result; index++) { if (objArray[index] != null) { result = true; } } return result; } protected String getTextMessage(String messageKey, String[] args) { return getTextMessage(this, messageKey, args); } protected String getTextMessage(Object action, String messageKey, String[] args) { if (action instanceof TextProvider) { return ((TextProvider) action).getText(messageKey, args); } return getTextProvider(action).getText(messageKey, args); } private TextProvider getTextProvider(Object action) { TextProviderFactory tpf = new TextProviderFactory(); if (container != null) { container.inject(tpf); } LocaleProvider localeProvider = getLocaleProvider(action); return tpf.createInstance(action.getClass(), localeProvider); } private LocaleProvider getLocaleProvider(Object action) { LocaleProvider localeProvider; if (action instanceof LocaleProvider) { localeProvider = (LocaleProvider) action; } else { localeProvider = container.getInstance(LocaleProvider.class); } return localeProvider; } }
/** * * <!-- START SNIPPET: description --> * This interceptor ensures that the action will only be executed if the user has the correct role. * <!-- END SNIPPET: description --> * * <p><u>Interceptor parameters:</u> * <!-- START SNIPPET: parameters --> * * <ul> * <li>allowedRoles - a comma-separated list of roles to allow * <li>disallowedRoles - a comma-separated list of roles to disallow * </ul> * * <p>When both allowedRoles and disallowedRoles are configured, then disallowedRoles takes * precedence, applying the following logic: (if ((inRole(role1) || inRole(role2) || ... * inRole(roleN)) && !inRole(roleA) && !inRole(roleB) && ... !inRole(roleZ)) { //permit ... * <!-- END SNIPPET: parameters --> * <!-- START SNIPPET: extending --> * There are three extensions to the existing interceptor: * * <ul> * <li>isAllowed(HttpServletRequest,Object) - whether or not to allow the passed action execution * with this request * <li>handleRejection(ActionInvocation) - handles an unauthorized request. * <li>areRolesValid(List<String> roles) - allows subclasses to lookup roles to ensure they are * valid. If not valid, RolesInterceptor will log the error and cease to function. This helps * prevent security misconfiguration flaws. * </ul> * * <!-- END SNIPPET: extending --> * * <pre> * <!-- START SNIPPET: example --> * <!-- only allows the admin and member roles --> * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="completeStack"/> * <interceptor-ref name="roles"> * <param name="allowedRoles">admin,member</param> * </interceptor-ref> * <result name="success">good_result.ftl</result> * </action> * <!-- END SNIPPET: example --> * </pre> */ public class RolesInterceptor extends AbstractInterceptor { private static final Logger LOG = LoggerFactory.getLogger(RolesInterceptor.class); private boolean isProperlyConfigured = true; protected List<String> allowedRoles = Collections.emptyList(); protected List<String> disallowedRoles = Collections.emptyList(); public void setAllowedRoles(String roles) { allowedRoles = stringToList(roles); checkRoles(allowedRoles); } public void setDisallowedRoles(String roles) { disallowedRoles = stringToList(roles); checkRoles(disallowedRoles); } private void checkRoles(List<String> roles) { if (!areRolesValid(roles)) { LOG.fatal("An unknown Role was configured: #0", roles.toString()); isProperlyConfigured = false; throw new IllegalArgumentException("An unknown role was configured: " + roles); } } public String intercept(ActionInvocation invocation) throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); if (!isProperlyConfigured) { throw new IllegalArgumentException( "RolesInterceptor is misconfigured, check logs for erroneous configuration!"); } if (!isAllowed(request, invocation.getAction())) { return handleRejection(invocation, response); } else { return invocation.invoke(); } } /** Splits a string into a List */ protected List<String> stringToList(String val) { if (val != null) { String[] list = val.split("[ ]*,[ ]*"); return Arrays.asList(list); } else { return Collections.emptyList(); } } /** * Determines if the request should be allowed for the action * * @param request The request * @param action The action object * @return True if allowed, false otherwise */ protected boolean isAllowed(HttpServletRequest request, Object action) { for (String role : disallowedRoles) { if (request.isUserInRole(role)) { return false; } } if (allowedRoles.isEmpty()) { return true; } for (String role : allowedRoles) { if (request.isUserInRole(role)) { return true; } } return false; } /** * Handles a rejection by sending a 403 HTTP error * * @param invocation The invocation * @return The result code * @throws Exception */ protected String handleRejection(ActionInvocation invocation, HttpServletResponse response) throws Exception { response.sendError(HttpServletResponse.SC_FORBIDDEN); return null; } /** * Extension point for sub-classes to test if configured roles are known valid roles. * Implementations are encouraged to implement this method to prevent misconfigured roles. If this * method returns false, the RolesInterceptor will be disabled and block all requests. * * @param roles allowed and disallowed roles * @return whether the roles are valid or not (always true for the default implementation) */ protected boolean areRolesValid(List<String> roles) { return true; } }
/** * * <!-- START SNIPPET: description --> * The aim of this Interceptor is to alias a named parameter to a different named parameter. By * acting as the glue between actions sharing similiar parameters (but with different names), it can * help greatly with action chaining. * * <p>Action's alias expressions should be in the form of <code> * #{ "name1" : "alias1", "name2" : "alias2" }</code>. This means that assuming an action (or * something else in the stack) has a value for the expression named <i>name1</i> and the action * this interceptor is applied to has a setter named <i>alias1</i>, <i>alias1</i> will be set with * the value from <i>name1</i>. * <!-- END SNIPPET: description --> * * <p><u>Interceptor parameters:</u> * <!-- START SNIPPET: parameters --> * * <ul> * <li>aliasesKey (optional) - the name of the action parameter to look for the alias map (by * default this is <i>aliases</i>). * </ul> * * <!-- END SNIPPET: parameters --> * * <p><u>Extending the interceptor:</u> * * <p> * <!-- START SNIPPET: extending --> * This interceptor does not have any known extension points. * <!-- END SNIPPET: extending --> * * <p><u>Example code:</u> * * <pre> * <!-- START SNIPPET: example --> * <action name="someAction" class="com.examples.SomeAction"> * <!-- The value for the foo parameter will be applied as if it were named bar --> * <param name="aliases">#{ 'foo' : 'bar' }</param> * * <interceptor-ref name="alias"/> * <interceptor-ref name="basicStack"/> * <result name="success">good_result.ftl</result> * </action> * <!-- END SNIPPET: example --> * </pre> * * @author Matthew Payne */ public class AliasInterceptor extends AbstractInterceptor { private static final Logger LOG = LoggerFactory.getLogger(AliasInterceptor.class); private static final String DEFAULT_ALIAS_KEY = "aliases"; protected String aliasesKey = DEFAULT_ALIAS_KEY; protected ValueStackFactory valueStackFactory; static boolean devMode = false; @Inject("devMode") public static void setDevMode(String mode) { devMode = "true".equals(mode); } @Inject public void setValueStackFactory(ValueStackFactory valueStackFactory) { this.valueStackFactory = valueStackFactory; } /** * Sets the name of the action parameter to look for the alias map. * * <p>Default is <code>aliases</code>. * * @param aliasesKey the name of the action parameter */ public void setAliasesKey(String aliasesKey) { this.aliasesKey = aliasesKey; } @Override public String intercept(ActionInvocation invocation) throws Exception { ActionConfig config = invocation.getProxy().getConfig(); ActionContext ac = invocation.getInvocationContext(); Object action = invocation.getAction(); // get the action's parameters final Map<String, String> parameters = config.getParams(); if (parameters.containsKey(aliasesKey)) { String aliasExpression = parameters.get(aliasesKey); ValueStack stack = ac.getValueStack(); Object obj = stack.findValue(aliasExpression); if (obj != null && obj instanceof Map) { // get secure stack ValueStack newStack = valueStackFactory.createValueStack(stack); boolean clearableStack = newStack instanceof ClearableValueStack; if (clearableStack) { // if the stack's context can be cleared, do that to prevent OGNL // from having access to objects in the stack, see XW-641 ((ClearableValueStack) newStack).clearContextValues(); Map<String, Object> context = newStack.getContext(); ReflectionContextState.setCreatingNullObjects(context, true); ReflectionContextState.setDenyMethodExecution(context, true); ReflectionContextState.setReportingConversionErrors(context, true); // keep locale from original context context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE)); } // override Map aliases = (Map) obj; for (Object o : aliases.entrySet()) { Map.Entry entry = (Map.Entry) o; String name = entry.getKey().toString(); String alias = (String) entry.getValue(); Object value = stack.findValue(name); if (null == value) { // workaround Map<String, Object> contextParameters = ActionContext.getContext().getParameters(); if (null != contextParameters) { value = contextParameters.get(name); } } if (null != value) { try { newStack.setValue(alias, value); } catch (RuntimeException e) { if (devMode) { String developerNotification = LocalizedTextUtil.findText( ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[] { "Unexpected Exception caught setting '" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage() }); LOG.error(developerNotification); if (action instanceof ValidationAware) { ((ValidationAware) action).addActionMessage(developerNotification); } } } } } if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null)) stack .getContext() .put( ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS)); } else { LOG.debug("invalid alias expression:" + aliasesKey); } } return invocation.invoke(); } }
/** * A utility class the actual dispatcher delegates most of its tasks to. Each instance of the * primary dispatcher holds an instance of this dispatcher to be shared for all requests. * * @see org.apache.struts2.dispatcher.FilterDispatcher */ public class Dispatcher { /** Provide a logging instance. */ private static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class); /** Provide a thread local instance. */ private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>(); /** Store list of DispatcherListeners. */ private static List<DispatcherListener> dispatcherListeners = new CopyOnWriteArrayList<DispatcherListener>(); /** Store ConfigurationManager instance, set on init. */ private ConfigurationManager configurationManager; /** Store state of StrutsConstants.STRUTS_DEVMODE setting. */ private boolean devMode; /** Store state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting. */ private boolean disableRequestAttributeValueStackLookup; /** Store state of StrutsConstants.STRUTS_I18N_ENCODING setting. */ private String defaultEncoding; /** Store state of StrutsConstants.STRUTS_LOCALE setting. */ private String defaultLocale; /** Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting. */ private String multipartSaveDir; /** Stores the value of StrutsConstants.STRUTS_MULTIPART_HANDLER setting */ private String multipartHandlerName; /** Provide list of default configuration files. */ private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml"; /** * Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND. * * <p>The workaround is for WebLogic. We try to autodect WebLogic on Dispatcher init. The * workaround can also be enabled manually. */ private boolean paramsWorkaroundEnabled = false; /** * Indicates if Dispatcher should handle exception and call sendError() Introduced to allow * integration with other frameworks like Spring Security */ private boolean handleException; /** * Provide the dispatcher instance for the current thread. * * @return The dispatcher instance */ public static Dispatcher getInstance() { return instance.get(); } /** * Store the dispatcher instance for this thread. * * @param instance The instance */ public static void setInstance(Dispatcher instance) { Dispatcher.instance.set(instance); } /** * Add a dispatcher lifecycle listener. * * @param listener The listener to add */ public static void addDispatcherListener(DispatcherListener listener) { dispatcherListeners.add(listener); } /** * Remove a specific dispatcher lifecycle listener. * * @param listener The listener */ public static void removeDispatcherListener(DispatcherListener listener) { dispatcherListeners.remove(listener); } private ServletContext servletContext; private Map<String, String> initParams; private ValueStackFactory valueStackFactory; /** * Create the Dispatcher instance for a given ServletContext and set of initialization parameters. * * @param servletContext Our servlet context * @param initParams The set of initialization parameters */ public Dispatcher(ServletContext servletContext, Map<String, String> initParams) { this.servletContext = servletContext; this.initParams = initParams; } /** * Modify state of StrutsConstants.STRUTS_DEVMODE setting. * * @param mode New setting */ @Inject(StrutsConstants.STRUTS_DEVMODE) public void setDevMode(String mode) { devMode = "true".equals(mode); } /** * Modify state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting. * * @param disableRequestAttributeValueStackLookup New setting */ @Inject( value = StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, required = false) public void setDisableRequestAttributeValueStackLookup( String disableRequestAttributeValueStackLookup) { this.disableRequestAttributeValueStackLookup = "true".equalsIgnoreCase(disableRequestAttributeValueStackLookup); } /** * Modify state of StrutsConstants.STRUTS_LOCALE setting. * * @param val New setting */ @Inject(value = StrutsConstants.STRUTS_LOCALE, required = false) public void setDefaultLocale(String val) { defaultLocale = val; } /** * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting. * * @param val New setting */ @Inject(StrutsConstants.STRUTS_I18N_ENCODING) public void setDefaultEncoding(String val) { defaultEncoding = val; } /** * Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting. * * @param val New setting */ @Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR) public void setMultipartSaveDir(String val) { multipartSaveDir = val; } @Inject(StrutsConstants.STRUTS_MULTIPART_PARSER) public void setMultipartHandler(String val) { multipartHandlerName = val; } @Inject public void setValueStackFactory(ValueStackFactory valueStackFactory) { this.valueStackFactory = valueStackFactory; } @Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION) public void setHandleException(String handleException) { this.handleException = Boolean.parseBoolean(handleException); } /** Releases all instances bound to this dispatcher instance. */ public void cleanup() { // clean up ObjectFactory ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class); if (objectFactory == null) { if (LOG.isWarnEnabled()) { LOG.warn( "Object Factory is null, something is seriously wrong, no clean up will be performed"); } } if (objectFactory instanceof ObjectFactoryDestroyable) { try { ((ObjectFactoryDestroyable) objectFactory).destroy(); } catch (Exception e) { // catch any exception that may occurred during destroy() and log it LOG.error( "exception occurred while destroying ObjectFactory [#0]", e, objectFactory.toString()); } } // clean up Dispatcher itself for this thread instance.set(null); // clean up DispatcherListeners if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherDestroyed(this); } } // clean up all interceptors by calling their destroy() method Set<Interceptor> interceptors = new HashSet<Interceptor>(); Collection<PackageConfig> packageConfigs = configurationManager.getConfiguration().getPackageConfigs().values(); for (PackageConfig packageConfig : packageConfigs) { for (Object config : packageConfig.getAllInterceptorConfigs().values()) { if (config instanceof InterceptorStackConfig) { for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) { interceptors.add(interceptorMapping.getInterceptor()); } } } } for (Interceptor interceptor : interceptors) { interceptor.destroy(); } // Clear container holder when application is unloaded / server shutdown ContainerHolder.clear(); // cleanup action context ActionContext.setContext(null); // clean up configuration configurationManager.destroyConfiguration(); configurationManager = null; } private void init_FileManager() throws ClassNotFoundException { if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER)) { final String fileManagerClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER); final Class<FileManager> fileManagerClass = (Class<FileManager>) Class.forName(fileManagerClassName); if (LOG.isInfoEnabled()) { LOG.info("Custom FileManager specified: #0", fileManagerClassName); } configurationManager.addContainerProvider( new FileManagerProvider(fileManagerClass, fileManagerClass.getSimpleName())); } else { // add any other Struts 2 provided implementations of FileManager configurationManager.addContainerProvider( new FileManagerProvider(JBossFileManager.class, "jboss")); } if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY)) { final String fileManagerFactoryClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY); final Class<FileManagerFactory> fileManagerFactoryClass = (Class<FileManagerFactory>) Class.forName(fileManagerFactoryClassName); if (LOG.isInfoEnabled()) { LOG.info("Custom FileManagerFactory specified: #0", fileManagerFactoryClassName); } configurationManager.addContainerProvider( new FileManagerFactoryProvider(fileManagerFactoryClass)); } } private void init_DefaultProperties() { configurationManager.addContainerProvider(new DefaultPropertiesProvider()); } private void init_LegacyStrutsProperties() { configurationManager.addContainerProvider(new LegacyPropertiesConfigurationProvider()); } private void init_TraditionalXmlConfigurations() { String configPaths = initParams.get("config"); if (configPaths == null) { configPaths = DEFAULT_CONFIGURATION_PATHS; } String[] files = configPaths.split("\\s*[,]\\s*"); for (String file : files) { if (file.endsWith(".xml")) { if ("xwork.xml".equals(file)) { configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false)); } else { configurationManager.addContainerProvider( createStrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException("Invalid configuration file name"); } } } protected XmlConfigurationProvider createXmlConfigurationProvider( String filename, boolean errorIfMissing) { return new XmlConfigurationProvider(filename, errorIfMissing); } protected XmlConfigurationProvider createStrutsXmlConfigurationProvider( String filename, boolean errorIfMissing, ServletContext ctx) { return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx); } private void init_CustomConfigurationProviders() { String configProvs = initParams.get("configProviders"); if (configProvs != null) { String[] classes = configProvs.split("\\s*[,]\\s*"); for (String cname : classes) { try { Class cls = ClassLoaderUtil.loadClass(cname, this.getClass()); ConfigurationProvider prov = (ConfigurationProvider) cls.newInstance(); configurationManager.addContainerProvider(prov); } catch (InstantiationException e) { throw new ConfigurationException("Unable to instantiate provider: " + cname, e); } catch (IllegalAccessException e) { throw new ConfigurationException("Unable to access provider: " + cname, e); } catch (ClassNotFoundException e) { throw new ConfigurationException("Unable to locate provider class: " + cname, e); } } } } private void init_FilterInitParameters() { configurationManager.addContainerProvider( new ConfigurationProvider() { public void destroy() {} public void init(Configuration configuration) throws ConfigurationException {} public void loadPackages() throws ConfigurationException {} public boolean needsReload() { return false; } public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { props.putAll(initParams); } }); } private void init_AliasStandardObjects() { configurationManager.addContainerProvider(new BeanSelectionProvider()); } private Container init_PreloadConfiguration() { Configuration config = configurationManager.getConfiguration(); Container container = config.getContainer(); boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); LocalizedTextUtil.setReloadBundles(reloadi18n); ContainerHolder.store(container); return container; } private void init_CheckWebLogicWorkaround(Container container) { // test whether param-access workaround needs to be enabled if (servletContext != null && servletContext.getServerInfo() != null && servletContext.getServerInfo().contains("WebLogic")) { if (LOG.isInfoEnabled()) { LOG.info("WebLogic server detected. Enabling Struts parameter access work-around."); } paramsWorkaroundEnabled = true; } else { paramsWorkaroundEnabled = "true" .equals( container.getInstance( String.class, StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)); } } /** * Load configurations, including both XML and zero-configuration strategies, and update optional * settings, including whether to reload configurations and resource files. */ public void init() { if (configurationManager == null) { configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } try { init_FileManager(); init_DefaultProperties(); // [1] init_TraditionalXmlConfigurations(); // [2] init_LegacyStrutsProperties(); // [3] init_CustomConfigurationProviders(); // [5] init_FilterInitParameters(); // [6] init_AliasStandardObjects(); // [7] Container container = init_PreloadConfiguration(); container.inject(this); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } catch (Exception ex) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } } protected ConfigurationManager createConfigurationManager(String name) { return new ConfigurationManager(name); } /** * Load Action class for mapping and invoke the appropriate Action method, or go directly to the * Result. * * <p>This method first creates the action context from the given parameters, and then loads an * <tt>ActionProxy</tt> from the given action name and namespace. After that, the Action method is * executed and output channels through the response object. Actions not found are sent back to * the user via the {@link Dispatcher#sendError} method, using the 404 return code. All other * errors are reported by throwing a ServletException. * * @param request the HttpServletRequest object * @param response the HttpServletResponse object * @param mapping the action mapping object * @throws ServletException when an unknown error occurs (not a 404, but typically something that * would end up as a 5xx by the servlet container) * @param context Our ServletContext object */ public void serviceAction( HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap(request, response, mapping, context); // If there was a previous value stack, then create a new copy and pass it in to be used by the // new Action ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); ActionProxy proxy = config .getContainer() .getInstance(ActionProxyFactory.class) .createActionProxy(namespace, name, method, extraContext, true, false); request.setAttribute( ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { // WW-2874 Only log error if in devMode if (devMode) { String reqStr = request.getRequestURI(); if (request.getQueryString() != null) { reqStr = reqStr + "?" + request.getQueryString(); } LOG.error("Could not find action or result\n" + reqStr, e); } else { if (LOG.isWarnEnabled()) { LOG.warn("Could not find action or result", e); } } sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { if (handleException || devMode) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } else { throw new ServletException(e); } } finally { UtilTimerStack.pop(timerKey); } } /** * Create a context map containing all the wrapped request objects * * @param request The servlet request * @param response The servlet response * @param mapping The action mapping * @param context The servlet context * @return A map of context objects */ public Map<String, Object> createContextMap( HttpServletRequest request, HttpServletResponse response, ActionMapping mapping, ServletContext context) { // request map wrapping the http request objects Map requestMap = new RequestMap(request); // parameters map wrapping the http parameters. ActionMapping parameters are now handled and // applied separately Map params = new HashMap(request.getParameterMap()); // session map wrapping the http session Map session = new SessionMap(request); // application map wrapping the ServletContext Map application = new ApplicationMap(context); Map<String, Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context); if (mapping != null) { extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); } return extraContext; } /** * Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the * entire <tt>Action</tt> context. * * @param requestMap a Map of all request attributes. * @param parameterMap a Map of all request parameters. * @param sessionMap a Map of all session attributes. * @param applicationMap a Map of all servlet context attributes. * @param request the HttpServletRequest object. * @param response the HttpServletResponse object. * @param servletContext the ServletContextmapping object. * @return a HashMap representing the <tt>Action</tt> context. */ public HashMap<String, Object> createContextMap( Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { HashMap<String, Object> extraContext = new HashMap<String, Object>(); extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap)); extraContext.put(ActionContext.SESSION, sessionMap); extraContext.put(ActionContext.APPLICATION, applicationMap); Locale locale; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } else { locale = request.getLocale(); } extraContext.put(ActionContext.LOCALE, locale); // extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode)); extraContext.put(StrutsStatics.HTTP_REQUEST, request); extraContext.put(StrutsStatics.HTTP_RESPONSE, response); extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext); // helpers to get access to request/session/application scope extraContext.put("request", requestMap); extraContext.put("session", sessionMap); extraContext.put("application", applicationMap); extraContext.put("parameters", parameterMap); AttributeMap attrMap = new AttributeMap(extraContext); extraContext.put("attr", attrMap); return extraContext; } /** * Return the path to save uploaded files to (this is configurable). * * @return the path to save uploaded files to * @param servletContext Our ServletContext */ private String getSaveDir(ServletContext servletContext) { String saveDir = multipartSaveDir.trim(); if (saveDir.equals("")) { File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir"); if (LOG.isInfoEnabled()) { LOG.info( "Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir"); } if (tempdir != null) { saveDir = tempdir.toString(); setMultipartSaveDir(saveDir); } } else { File multipartSaveDir = new File(saveDir); if (!multipartSaveDir.exists()) { if (!multipartSaveDir.mkdirs()) { String logMessage; try { logMessage = "Could not find create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'."; } catch (IOException e) { logMessage = "Could not find create multipart save directory '" + multipartSaveDir.toString() + "'."; } if (devMode) { LOG.error(logMessage); } else { if (LOG.isWarnEnabled()) { LOG.warn(logMessage); } } } } } if (LOG.isDebugEnabled()) { LOG.debug("saveDir=" + saveDir); } return saveDir; } /** * Prepare a request, including setting the encoding and locale. * * @param request The request * @param response The response */ public void prepare(HttpServletRequest request, HttpServletResponse response) { String encoding = null; if (defaultEncoding != null) { encoding = defaultEncoding; } // check for Ajax request to use UTF-8 encoding strictly // http://www.w3.org/TR/XMLHttpRequest/#the-send-method if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { encoding = "UTF-8"; } Locale locale = null; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } if (encoding != null) { applyEncoding(request, encoding); } if (locale != null) { response.setLocale(locale); } if (paramsWorkaroundEnabled) { request.getParameter( "foo"); // simply read any parameter (existing or not) to "prime" the request } } private void applyEncoding(HttpServletRequest request, String encoding) { try { if (!encoding.equals(request.getCharacterEncoding())) { // if the encoding is already correctly set and the parameters have been already read // do not try to set encoding because it is useless and will cause an error request.setCharacterEncoding(encoding); } } catch (Exception e) { LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e); } } /** * Wrap and return the given request or return the original request object. This method * transparently handles multipart data as a wrapped class around the given request. Override this * method to handle multipart requests in a special way or to handle other types of requests. * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is flexible - * look first to that object before overriding this method to handle multipart data. * * @param request the HttpServletRequest object. * @param servletContext Our ServletContext object * @return a wrapped request or original request. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper * @throws java.io.IOException on any error. */ public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); if (content_type != null && content_type.contains("multipart/form-data")) { MultiPartRequest mpr = getMultiPartRequest(); LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider); } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); } return request; } /** * On each request it must return a new instance as implementation could be not thread safe and * thus ensure of resource clean up * * @return */ protected MultiPartRequest getMultiPartRequest() { MultiPartRequest mpr = null; // check for alternate implementations of MultiPartRequest Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); for (String multiName : multiNames) { if (multiName.equals(multipartHandlerName)) { mpr = getContainer().getInstance(MultiPartRequest.class, multiName); } } if (mpr == null) { mpr = getContainer().getInstance(MultiPartRequest.class); } return mpr; } /** * Removes all the files created by MultiPartRequestWrapper. * * @param request the HttpServletRequest object. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper */ public void cleanUpRequest(HttpServletRequest request) { ContainerHolder.clear(); if (!(request instanceof MultiPartRequestWrapper)) { return; } MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request; multiWrapper.cleanUp(); } /** * Send an HTTP error response code. * * @param request the HttpServletRequest object. * @param response the HttpServletResponse object. * @param code the HttpServletResponse error code (see {@link * javax.servlet.http.HttpServletResponse} for possible error codes). * @param e the Exception that is reported. * @param ctx the ServletContext object. */ public void sendError( HttpServletRequest request, HttpServletResponse response, ServletContext ctx, int code, Exception e) { Boolean devModeOverride = FilterDispatcher.getDevModeOverride(); if (devModeOverride != null ? devModeOverride : devMode) { if (LOG.isDebugEnabled()) { LOG.debug("Exception occurred during processing request: #0", e, e.getMessage()); } try { FreemarkerManager mgr = getContainer().getInstance(FreemarkerManager.class); freemarker.template.Configuration config = mgr.getConfiguration(ctx); Template template = config.getTemplate("/org/apache/struts2/dispatcher/error.ftl"); List<Throwable> chain = new ArrayList<Throwable>(); Throwable cur = e; chain.add(cur); while ((cur = cur.getCause()) != null) { chain.add(cur); } HashMap<String, Object> data = new HashMap<String, Object>(); data.put("exception", e); data.put("unknown", Location.UNKNOWN); data.put("chain", chain); data.put("locator", new Locator()); Writer writer = new StringWriter(); template.process(data, writer); response.setContentType("text/html"); response.getWriter().write(writer.toString()); response.getWriter().close(); } catch (Exception exp) { try { if (LOG.isDebugEnabled()) { LOG.debug("Cannot show problem report!", exp); } response.sendError( code, "Unable to show problem report:\n" + exp + "\n\n" + LocationUtils.getLocation(exp)); } catch (IOException ex) { // we're already sending an error, not much else we can do if more stuff breaks } } } else { try { if (LOG.isErrorEnabled()) { LOG.error("Exception occurred during processing request: #0", e, e.getMessage()); } // WW-1977: Only put errors in the request when code is a 500 error if (code == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // send a http error response to use the servlet defined error handler // make the exception availible to the web.xml defined error page request.setAttribute("javax.servlet.error.exception", e); // for compatibility request.setAttribute("javax.servlet.jsp.jspException", e); } // send the error response response.sendError(code, e.getMessage()); } catch (IOException e1) { // we're already sending an error, not much else we can do if more stuff breaks } } } /** Cleanup any resources used to initialise Dispatcher */ public void cleanUpAfterInit() { if (LOG.isDebugEnabled()) { LOG.debug("Cleaning up resources used to init Dispatcher"); } ContainerHolder.clear(); } /** Provide an accessor class for static XWork utility. */ public static class Locator { public Location getLocation(Object obj) { Location loc = LocationUtils.getLocation(obj); if (loc == null) { return Location.UNKNOWN; } return loc; } } /** * Expose the ConfigurationManager instance. * * @return The instance */ public ConfigurationManager getConfigurationManager() { return configurationManager; } /** * Modify the ConfigurationManager instance * * @param mgr The configuration manager * @deprecated should be removed as is used only in tests */ public void setConfigurationManager(ConfigurationManager mgr) { ContainerHolder.clear(); this.configurationManager = mgr; } /** * Expose the dependency injection container. * * @return Our dependency injection container */ public Container getContainer() { if (ContainerHolder.get() != null) { return ContainerHolder.get(); } ConfigurationManager mgr = getConfigurationManager(); if (mgr == null) { throw new IllegalStateException("The configuration manager shouldn't be null"); } else { Configuration config = mgr.getConfiguration(); if (config == null) { throw new IllegalStateException("Unable to load configuration"); } else { Container container = config.getContainer(); ContainerHolder.store(container); return container; } } } }
/** * Action基础类 * * @author Wizard */ public class BaseAction extends ActionSupport implements SessionAware, ServletRequestAware, ServletResponseAware { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(BaseAction.class); /** */ public static String GLOBAL_ERROR = "globalError"; public static final String RESP_OK = "ok"; private HttpServletRequest request; private HttpServletResponse response; private Map<String, Object> session; protected String tokenid; protected String extoken; private static final Random RANDOM = new Random(); /** token名称 */ protected static final String TOKEN_ID = "tokenid"; protected static final String EX_TOKEN = "extoken"; // 操作标识 /** 初始标识 */ public static final String FLAG_IN = "-1"; /** 新增标识 */ public static final String FLAG_ADD = "0"; /** 修改标识 */ public static final String FLAG_UPD = "1"; /** 删除标识 */ public static final String FLAG_DET = "2"; /** 拷贝标识 */ public static final String FLAG_COPY = "3"; /** 个人资料管理 table标识 */ public static final String TABFLAG_INFO = "0"; // 个人资料 public static final String TABFLAG_PWD = "1"; // 修改密码 public static final String TABFLAG_EMAIL = "2"; // 绑定邮箱 public static final String TABFLAG_MOBILE = "3"; // 绑定手机 public static final String TABFLAG_NOBIND = "4"; // 没绑定 public static final String MMSMODE_Temp = "1"; // 模版模式 public static final String MMSMODE_REAL = "2"; // 实时模式 private static final Logger LOG = LoggerFactory.getLogger(TokenHelper.class); public SessionContext getSessionContext() { return SessionContext.getContext(session); } public HttpServletRequest getRequest() { return request; } public void setRequest(HttpServletRequest request) { this.request = request; } public HttpServletResponse getResponse() { return response; } public void setResponse(HttpServletResponse response) { this.response = response; } public Map<String, Object> getSession() { return session; } public void setSession(Map<String, Object> session) { this.session = session; } public void setServletRequest(HttpServletRequest request) { this.request = request; } public void setServletResponse(HttpServletResponse response) { this.response = response; } public String getTokenid() { try { this.tokenid = new BigInteger(165, RANDOM).toString(36).toUpperCase(); getSession().put("tokenid", this.tokenid); } catch (IllegalStateException e) { // explain to user what the problem is String msg = "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: " + e.getMessage(); LOG.error(msg, e); throw new IllegalArgumentException(msg); } return this.tokenid; } public void setTokenid(String tokenid) { this.tokenid = tokenid; } /** * 判断是否合法的提交操作 * * @param tokenName 为页面对应的name * @return */ public boolean validToken(String tokenName) { String sessToken = (String) session.get(tokenName); String tokenValue = null; if (TOKEN_ID.equals(tokenName)) { tokenValue = tokenid; } else if (EX_TOKEN.equals(tokenName)) { tokenValue = extoken; } else { LOG.warn("the tokenName is not required "); } // 解决重启服务器,刷新报空指针问题 if (tokenValue == null) { LOG.warn("the tokenValue is null."); return false; } if (!tokenValue.equals(sessToken)) { LOG.warn("Form" + tokenName + "does not match the session" + tokenName + "."); return false; } // remove the token so it won't be used again session.remove(tokenName); return true; } public String getExtoken() { try { this.extoken = new BigInteger(165, RANDOM).toString(36).toUpperCase(); getSession().put("extoken", this.extoken); } catch (IllegalStateException e) { // explain to user what the problem is String msg = "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: " + e.getMessage(); LOG.error(msg, e); throw new IllegalArgumentException(msg); } return extoken; } public void setExtoken(String extoken) { this.extoken = extoken; } public void print(String content, String contentType) { HttpServletResponse response = ServletActionContext.getResponse(); response.setContentType(contentType); response.setCharacterEncoding("utf-8"); response.setHeader("cache-control", "no-cache"); PrintWriter out = null; try { out = response.getWriter(); out.print(content); } catch (IOException e) { logger.error("输出流时发生错误。流内容:\"" + content + "\",输出类型:" + contentType, e); } finally { if (out != null) { out.flush(); out.close(); } } } }
/** * * <!-- START SNIPPET: javadoc --> * Used to get the property of a <i>value</i>, which will default to the top of the stack if none is * specified. * <!-- END SNIPPET: javadoc --> * * <p> * <!-- START SNIPPET: params --> * * <ul> * <li>default (String) - The default value to be used if <u>value</u> attribute is null * <li>escape (Boolean) - Escape HTML. Default to true * <li>value (Object) - value to be displayed * </ul> * * <!-- END SNIPPET: params --> * * <pre> * <!-- START SNIPPET: example --> * * <s:push value="myBean"> * <!-- Example 1: --> * <s:property value="myBeanProperty" /> * * <!-- Example 2: -->TextUtils * <s:property value="myBeanProperty" default="a default value" /> * </s:push> * * <!-- END SNIPPET: example --> * </pre> * * <pre> * <!-- START SNIPPET: exampledescription --> * * Example 1 prints the result of myBean's getMyBeanProperty() method. * Example 2 prints the result of myBean's getMyBeanProperty() method and if it is null, print 'a default value' instead. * * <!-- END SNIPPET: exampledescription --> * </pre> * * <pre> * <!-- START SNIPPET: i18nExample --> * * <s:property value="getText('some.key')" /> * * <!-- END SNIPPET: i18nExample --> * </pre> */ @StrutsTag( name = "property", tldBodyContent = "empty", tldTagClass = "org.apache.struts2.views.jsp.PropertyTag", description = "Print out expression which evaluates against the stack") public class Property extends Component { private static final Logger LOG = LoggerFactory.getLogger(Property.class); public Property(ValueStack stack) { super(stack); } private String defaultValue; private String value; private boolean escape = true; private boolean escapeJavaScript = false; @StrutsTagAttribute( description = "The default value to be used if <u>value</u> attribute is null") public void setDefault(String defaultValue) { this.defaultValue = defaultValue; } @StrutsTagAttribute( description = " Whether to escape HTML", type = "Boolean", defaultValue = "true") public void setEscape(boolean escape) { this.escape = escape; } @StrutsTagAttribute( description = "Whether to escape Javascript", type = "Boolean", defaultValue = "false") public void setEscapeJavaScript(boolean escapeJavaScript) { this.escapeJavaScript = escapeJavaScript; } @StrutsTagAttribute( description = "Value to be displayed", type = "Object", defaultValue = "<top of stack>") public void setValue(String value) { this.value = value; } public boolean start(Writer writer) { boolean result = super.start(writer); String actualValue = null; if (value == null) { value = "top"; } else { value = stripExpressionIfAltSyntax(value); } // exception: don't call findString(), since we don't want the // expression parsed in this one case. it really // doesn't make sense, in fact. actualValue = (String) getStack().findValue(value, String.class, throwExceptionOnELFailure); try { if (actualValue != null) { writer.write(prepare(actualValue)); } else if (defaultValue != null) { writer.write(prepare(defaultValue)); } } catch (IOException e) { LOG.info("Could not print out value '" + value + "'", e); } return result; } private String prepare(String value) { String result = value; if (escape) { result = StringEscapeUtils.escapeHtml(result); } if (escapeJavaScript) { result = StringEscapeUtils.escapeJavaScript(result); } return result; } }
/** * This class implements the ResultMapBuilder and traverses the web application content directory * looking for reasonably named JSPs and other result types as well as annotations. This naming is * in this form: * * <pre> * /resultPath/namespace/action-<result>.jsp * </pre> * * <p>If there are any files in these locations than a result is created for each one and the result * names is the last portion of the file name up to the . (dot). * * <p>When results are found, new ResultConfig instances are created. The result config that is * created has a number of thing to be aware of: * * <ul> * <li>The result config contains the location parameter, which is required by most result classes * to figure out where to find the result. In addition, the config has all the parameters from * the default result-type configuration. * </ul> * * <p>After loading the files in the web application, this class will then use any annotations on * the action class to override what was found in the web application files. These annotations are * the {@link Result} and {@link Results} annotations. These two annotations allow an action to * supply different or non-forward based results for specific return values of an action method. * * <p>The result path used by this class for locating JSPs and other such result files can be set * using the Struts2 constant named <strong>struts.convention.result.path</strong> or using the * {@link org.apache.struts2.convention.annotation.ResultPath} annotation. * * <p>This class will also locate and configure Results in the classpath, including velocity and * FreeMarker templates inside the classpath. * * <p>All results that are conigured from resources are given a type corresponding to the resources * extension. The extensions and types are given in the table below: * * <table> * <tr><th>Extension</th><th>Type</th></tr> * <tr><td>.jsp</td><td>dispatcher</td</tr> * <tr><td>.jspx</td><td>dispatcher</td</tr> * <tr><td>.html</td><td>dispatcher</td</tr> * <tr><td>.htm</td><td>dispatcher</td</tr> * <tr><td>.vm</td><td>velocity</td</tr> * <tr><td>.ftl</td><td>freemarker</td</tr> * </table> */ public class StuResultMapBuilder implements ResultMapBuilder { private static final Logger LOG = LoggerFactory.getLogger(StuResultMapBuilder.class); private final ServletContext servletContext; private Set<String> relativeResultTypes; private ConventionsService conventionsService; private boolean flatResultLayout = true; /** * Constructs the SimpleResultMapBuilder using the given result location. * * @param servletContext The ServletContext for finding the resources of the web application. * @param container The Xwork container * @param relativeResultTypes The list of result types that can have locations that are relative * and the result location (which is the resultPath plus the namespace) prepended to them. */ @Inject public StuResultMapBuilder( ServletContext servletContext, Container container, @Inject("struts.convention.relative.result.types") String relativeResultTypes) { this.servletContext = servletContext; this.relativeResultTypes = new HashSet<String>(Arrays.asList(relativeResultTypes.split("\\s*[,]\\s*"))); this.conventionsService = container.getInstance( ConventionsService.class, container.getInstance( String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE)); } /** * @param flatResultLayout If 'true' result resources will be expected to be in the form * ${namespace}/${actionName}-${result}.${extension}, otherwise in the form * ${namespace}/${actionName}/${result}.${extension} */ @Inject("struts.convention.result.flatLayout") public void setFlatResultLayout(String flatResultLayout) { this.flatResultLayout = "true".equals(flatResultLayout); } /** {@inheritDoc} */ public Map<String, ResultConfig> build( Class<?> actionClass, org.apache.struts2.convention.annotation.Action annotation, String actionName, PackageConfig packageConfig) { // Get the default result location from the annotation or configuration String defaultResultPath = conventionsService.determineResultPath(actionClass); // Add a slash if (!defaultResultPath.endsWith("/")) { defaultResultPath = defaultResultPath + "/"; } // Check for resources with the action name /* final String namespace = packageConfig.getNamespace(); if (namespace != null && namespace.startsWith("/")) { defaultResultPath = defaultResultPath + namespace.substring(1); } else if (namespace != null) { defaultResultPath = defaultResultPath + namespace; } if (LOG.isTraceEnabled()) { LOG.trace("Using final calculated namespace [#0]", namespace); }*/ // Add that ending slash for concatentation if (!defaultResultPath.endsWith("/")) { defaultResultPath += "/"; } String resultPrefix = defaultResultPath + actionName; // results from files Map<String, ResultConfig> results = new HashMap<String, ResultConfig>(); Map<String, ResultTypeConfig> resultsByExtension = conventionsService.getResultTypesByExtension(packageConfig); createFromResources( actionClass, results, defaultResultPath, resultPrefix, actionName, packageConfig, resultsByExtension); // get inherited @Results and @Result (class level) for (Class<?> clazz : ReflectionTools.getClassHierarchy(actionClass)) { createResultsFromAnnotations( clazz, packageConfig, defaultResultPath, results, resultsByExtension); } // method level if (annotation != null && annotation.results() != null && annotation.results().length > 0) { createFromAnnotations( results, defaultResultPath, packageConfig, annotation.results(), actionClass, resultsByExtension); } return results; } /** * Creates results from @Results and @Result annotations * * @param actionClass class to check for annotations * @param packageConfig packageConfig where the action will be located * @param defaultResultPath default result path * @param results map of results * @param resultsByExtension map of result types keyed by extension */ protected void createResultsFromAnnotations( Class<?> actionClass, PackageConfig packageConfig, String defaultResultPath, Map<String, ResultConfig> results, Map<String, ResultTypeConfig> resultsByExtension) { Results resultsAnn = actionClass.getAnnotation(Results.class); if (resultsAnn != null) { createFromAnnotations( results, defaultResultPath, packageConfig, resultsAnn.value(), actionClass, resultsByExtension); } Result resultAnn = actionClass.getAnnotation(Result.class); if (resultAnn != null) { createFromAnnotations( results, defaultResultPath, packageConfig, new Result[] {resultAnn}, actionClass, resultsByExtension); } } /** * Creates any result types from the resources available in the web application. This scans the * web application resources using the servlet context. * * @param actionClass The action class the results are being built for. * @param results The results map to put the result configs created into. * @param resultPath The calculated path to the resources. * @param resultPrefix The prefix for the result. This is usually <code>/resultPath/actionName * </code>. * @param actionName The action name which is used only for logging in this implementation. * @param packageConfig The package configuration which is passed along in order to determine * @param resultsByExtension The map of extensions to result type configuration instances. */ protected void createFromResources( Class<?> actionClass, Map<String, ResultConfig> results, final String resultPath, final String resultPrefix, final String actionName, PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) { if (LOG.isTraceEnabled()) { LOG.trace( "Searching for results in the Servlet container at [#0]" + " with result prefix of [#1]", resultPath, resultPrefix); } // Build from web application using the ServletContext @SuppressWarnings("unchecked") Set<String> paths = servletContext.getResourcePaths(flatResultLayout ? resultPath : resultPrefix); if (paths != null) { for (String path : paths) { if (LOG.isTraceEnabled()) { LOG.trace("Processing resource path [#0]", path); } String fileName = StringUtils.substringAfterLast(path, "/"); if (StringUtils.isBlank(fileName) || StringUtils.startsWith(fileName, ".")) { if (LOG.isTraceEnabled()) LOG.trace("Ignoring file without name [#0]", path); continue; } else if (fileName.lastIndexOf(".") > 0) { String suffix = fileName.substring(fileName.lastIndexOf(".") + 1); if (conventionsService.getResultTypesByExtension(packageConfig).get(suffix) == null) { if (LOG.isDebugEnabled()) LOG.debug( "No result type defined for file suffix : [#0]. Ignoring file #1", suffix, fileName); continue; } } makeResults(actionClass, path, resultPrefix, results, packageConfig, resultsByExtension); } } // Building from the classpath String classPathLocation = resultPath.startsWith("/") ? resultPath.substring(1, resultPath.length()) : resultPath; if (LOG.isTraceEnabled()) { LOG.trace( "Searching for results in the class path at [#0]" + " with a result prefix of [#1] and action name [#2]", classPathLocation, resultPrefix, actionName); } ResourceFinder finder = new ResourceFinder(classPathLocation, getClassLoaderInterface()); try { Map<String, URL> matches = finder.getResourcesMap(""); if (matches != null) { Test<URL> resourceTest = getResourceTest(resultPath, actionName); for (Map.Entry<String, URL> entry : matches.entrySet()) { if (resourceTest.test(entry.getValue())) { if (LOG.isTraceEnabled()) { LOG.trace("Processing URL [#0]", entry.getKey()); } String urlStr = entry.getValue().toString(); int index = urlStr.lastIndexOf(resultPrefix); String path = urlStr.substring(index); makeResults( actionClass, path, resultPrefix, results, packageConfig, resultsByExtension); } } } } catch (IOException ex) { if (LOG.isErrorEnabled()) LOG.error("Unable to scan directory [#0] for results", ex, classPathLocation); } } protected ClassLoaderInterface getClassLoaderInterface() { /* if there is a ClassLoaderInterface in the context, use it, otherwise default to the default ClassLoaderInterface (a wrapper around the current thread classloader) using this, other plugins (like OSGi) can plugin their own classloader for a while and it will be used by Convention (it cannot be a bean, as Convention is likely to be called multiple times, and it need to use the default ClassLoaderInterface during normal startup) */ ClassLoaderInterface classLoaderInterface = null; ActionContext ctx = ActionContext.getContext(); if (ctx != null) classLoaderInterface = (ClassLoaderInterface) ctx.get(ClassLoaderInterface.CLASS_LOADER_INTERFACE); return (ClassLoaderInterface) ObjectUtils.defaultIfNull( classLoaderInterface, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader())); } private Test<URL> getResourceTest(final String resultPath, final String actionName) { return new Test<URL>() { public boolean test(URL url) { String urlStr = url.toString(); int index = urlStr.lastIndexOf(resultPath); String path = urlStr.substring(index + resultPath.length()); return path.startsWith(actionName); } }; } /** * Makes all the results for the given path. * * @param actionClass The action class the results are being built for. * @param path The path to build the result for. * @param resultPrefix The is the result prefix which is the result location plus the action name. * This is used to determine if the path contains a result code or not. * @param results The Map to place the result(s) * @param packageConfig The package config the results belong to. * @param resultsByExtension The map of extensions to result type configuration instances. */ protected void makeResults( Class<?> actionClass, String path, String resultPrefix, Map<String, ResultConfig> results, PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) { if (path.startsWith(resultPrefix)) { int indexOfDot = path.indexOf('.', resultPrefix.length()); // This case is when the path doesn't contain a result code if (indexOfDot == resultPrefix.length() || !flatResultLayout) { if (LOG.isTraceEnabled()) { LOG.trace( "The result file [#0] has no result code and therefore" + " will be associated with success, input and error by default. This might" + " be overridden by another result file or an annotation.", path); } if (!results.containsKey(Action.SUCCESS)) { ResultConfig success = createResultConfig( actionClass, new ResultInfo(Action.SUCCESS, path, packageConfig, resultsByExtension), packageConfig, null); results.put(Action.SUCCESS, success); } if (!results.containsKey(Action.INPUT)) { ResultConfig input = createResultConfig( actionClass, new ResultInfo(Action.INPUT, path, packageConfig, resultsByExtension), packageConfig, null); results.put(Action.INPUT, input); } if (!results.containsKey(Action.ERROR)) { ResultConfig error = createResultConfig( actionClass, new ResultInfo(Action.ERROR, path, packageConfig, resultsByExtension), packageConfig, null); results.put(Action.ERROR, error); } // This case is when the path contains a result code } else if (indexOfDot > resultPrefix.length()) { if (LOG.isTraceEnabled()) { LOG.trace( "The result file [#0] has a result code and therefore" + " will be associated with only that result code.", path); } String resultCode = path.substring(resultPrefix.length() + 1, indexOfDot); ResultConfig result = createResultConfig( actionClass, new ResultInfo(resultCode, path, packageConfig, resultsByExtension), packageConfig, null); results.put(resultCode, result); } } } protected void createFromAnnotations( Map<String, ResultConfig> resultConfigs, String resultPath, PackageConfig packageConfig, Result[] results, Class<?> actionClass, Map<String, ResultTypeConfig> resultsByExtension) { // Check for multiple results on the class for (Result result : results) { ResultConfig config = createResultConfig( actionClass, new ResultInfo(result, packageConfig, resultPath, actionClass, resultsByExtension), packageConfig, result); if (config != null) { resultConfigs.put(config.getName(), config); } } } /** * Creates the result configuration for the single result annotation. This will use all the * information from the annotation and anything that isn't specified will be fetched from the * PackageConfig defaults (if they exist). * * @param actionClass The action class the results are being built for. * @param info The result info that is used to create the ResultConfig instance. * @param packageConfig The PackageConfig to use to fetch defaults for result and parameters. * @param result (Optional) The result annotation to pull additional information from. * @return The ResultConfig or null if the Result annotation is given and the annotation is * targeted to some other action than this one. */ @SuppressWarnings(value = {"unchecked"}) protected ResultConfig createResultConfig( Class<?> actionClass, ResultInfo info, PackageConfig packageConfig, Result result) { // Look up by the type that was determined from the annotation or by the extension in the // ResultInfo class ResultTypeConfig resultTypeConfig = packageConfig.getAllResultTypeConfigs().get(info.type); if (resultTypeConfig == null) { throw new ConfigurationException( "The Result type [" + info.type + "] which is" + " defined in the Result annotation on the class [" + actionClass + "] or determined" + " by the file extension or is the default result type for the PackageConfig of the" + " action, could not be found as a result-type defined for the Struts/XWork package [" + packageConfig.getName() + "]"); } // Add the default parameters for the result type config (if any) HashMap<String, String> params = new HashMap<String, String>(); if (resultTypeConfig.getParams() != null) { params.putAll(resultTypeConfig.getParams()); } // Handle the annotation if (result != null) { params.putAll(StringTools.createParameterMap(result.params())); } // Map the location to the default param for the result or a param named location if (info.location != null) { String defaultParamName = resultTypeConfig.getDefaultResultParam(); if (!params.containsKey(defaultParamName)) { params.put(defaultParamName, info.location); } } return new ResultConfig.Builder(info.name, resultTypeConfig.getClassName()) .addParams(params) .build(); } protected class ResultInfo { public final String name; public final String location; public final String type; public ResultInfo( String name, String location, PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) { this.name = name; this.location = location; this.type = determineType(location, packageConfig, resultsByExtension); } public ResultInfo( Result result, PackageConfig packageConfig, String resultPath, Class<?> actionClass, Map<String, ResultTypeConfig> resultsByExtension) { this.name = result.name(); if (StringUtils.isNotBlank(result.type())) { this.type = result.type(); } else if (StringUtils.isNotBlank(result.location())) { this.type = determineType(result.location(), packageConfig, resultsByExtension); } else { throw new ConfigurationException( "The action class [" + actionClass + "] contains a " + "result annotation that has no type parameter and no location parameter. One of " + "these must be defined."); } // See if we can handle relative locations or not if (StringUtils.isNotBlank(result.location())) { if (relativeResultTypes.contains(this.type) && !result.location().startsWith("/")) { location = resultPath + result.location(); } else { location = result.location(); } } else { this.location = null; } } String determineType( String location, PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) { int indexOfDot = location.lastIndexOf("."); if (indexOfDot > 0) { String extension = location.substring(indexOfDot + 1); ResultTypeConfig resultTypeConfig = resultsByExtension.get(extension); if (resultTypeConfig != null) { return resultTypeConfig.getName(); } else throw new ConfigurationException( "Unable to find a result type for extension [" + extension + "] " + "in location attribute [" + location + "]."); } else { return packageConfig.getFullDefaultResultType(); } } } }
/** * * <!-- START SNIPPET: description --> * * <p>Generates a JasperReports report using the specified format or PDF if no format is specified. * * <p> * <!-- END SNIPPET: description --> * * <p><b>This result type takes the following parameters:</b> * * <p> * <!-- START SNIPPET: params --> * * <p> * * <ul> * <p> * <li><b>location (default)</b> - the location where the compiled jasper report definition is * (foo.jasper), relative from current URL. * <p> * <li><b>dataSource (required)</b> - the EL expression used to retrieve the datasource from the * value stack (usually a List). * <p> * <li><b>parse</b> - true by default. If set to false, the location param will not be parsed for * EL expressions. * <p> * <li><b>format</b> - the format in which the report should be generated. Valid values can be * found in {@link JasperReportConstants}. If no format is specified, PDF will be used. * <p> * <li><b>contentDisposition</b> - disposition (defaults to "inline", values are typically * <i>filename="document.pdf"</i>). * <p> * <li><b>documentName</b> - name of the document (will generate the http header <code> * Content-disposition = X; filename=X.[format]</code>). * <p> * <li><b>delimiter</b> - the delimiter used when generating CSV reports. By default, the * character used is ",". * <p> * <li><b>imageServletUrl</b> - name of the url that, when prefixed with the context page, can * return report images. * <p> * <li><b>reportParameters</b> - (2.1.2+) OGNL expression used to retrieve a map of report * parameters from the value stack. The parameters may be accessed in the report via the usual * JR mechanism and might include data not part of the dataSource, such as the user name of * the report creator, etc. * <p> * <li><b>exportParameters</b> - (2.1.2+) OGNL expression used to retrieve a map of JR exporter * parameters from the value stack. The export parameters are used to customize the JR export. * For example, a PDF export might enable encryption and set the user password to a string * known to the report creator. * <p> * <li><b>connection</b> - (2.1.7+) JDBC Connection which can be passed to the report instead of * dataSource * <p> * </ul> * * <p> * * <p>This result follows the same rules from {@link StrutsResultSupport}. Specifically, all * parameters will be parsed if the "parse" parameter is not set to false. * <!-- END SNIPPET: params --> * * <p><b>Example:</b> * * <p> * * <pre><!-- START SNIPPET: example1 --> * <result name="success" type="jasper"> * <param name="location">foo.jasper</param> * <param name="dataSource">mySource</param> * <param name="format">CSV</param> * </result> * <!-- END SNIPPET: example1 --></pre> * * or for pdf * * <pre><!-- START SNIPPET: example2 --> * <result name="success" type="jasper"> * <param name="location">foo.jasper</param> * <param name="dataSource">mySource</param> * </result> * <!-- END SNIPPET: example2 --></pre> */ @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) public class JasperReportsResult extends StrutsResultSupport implements JasperReportConstants { private static final long serialVersionUID = -2523174799621182907L; private static final Logger LOG = LoggerFactory.getLogger(JasperReportsResult.class); protected String dataSource; protected String format; protected String documentName; protected String contentDisposition; protected String delimiter; protected String imageServletUrl = "/images/"; protected String timeZone; protected boolean wrapField = true; /** Connection which can be passed to the report instead od dataSource. */ protected String connection; /** * Names a report parameters map stack value, allowing additional report parameters from the * action. */ protected String reportParameters; /** Names an exporter parameters map stack value, allowing the use of custom export parameters. */ protected String exportParameters; /** Default ctor. */ public JasperReportsResult() { super(); } /** * Default ctor with location. * * @param location Result location. */ public JasperReportsResult(String location) { super(location); } public String getImageServletUrl() { return imageServletUrl; } public void setImageServletUrl(final String imageServletUrl) { this.imageServletUrl = imageServletUrl; } public void setDataSource(String dataSource) { this.dataSource = dataSource; } public void setFormat(String format) { this.format = format; } public void setDocumentName(String documentName) { this.documentName = documentName; } public void setContentDisposition(String contentDisposition) { this.contentDisposition = contentDisposition; } public void setDelimiter(String delimiter) { this.delimiter = delimiter; } /** * set time zone id * * @param timeZone */ public void setTimeZone(final String timeZone) { this.timeZone = timeZone; } public void setWrapField(boolean wrapField) { this.wrapField = wrapField; } public String getReportParameters() { return reportParameters; } public void setReportParameters(String reportParameters) { this.reportParameters = reportParameters; } public String getExportParameters() { return exportParameters; } public void setExportParameters(String exportParameters) { this.exportParameters = exportParameters; } public String getConnection() { return connection; } public void setConnection(String connection) { this.connection = connection; } protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { // Will throw a runtime exception if no "datasource" property. initializeProperties(invocation); if (LOG.isDebugEnabled()) { LOG.debug( "Creating JasperReport for dataSource = " + dataSource + ", format = " + format, new Object[0]); } HttpServletRequest request = (HttpServletRequest) invocation.getInvocationContext().get(ServletActionContext.HTTP_REQUEST); HttpServletResponse response = (HttpServletResponse) invocation.getInvocationContext().get(ServletActionContext.HTTP_RESPONSE); // Handle IE special case: it sends a "contype" request first. if ("contype".equals(request.getHeader("User-Agent"))) { try { response.setContentType("application/pdf"); response.setContentLength(0); ServletOutputStream outputStream = response.getOutputStream(); outputStream.close(); } catch (IOException e) { LOG.error("Error writing report output", e); throw new ServletException(e.getMessage(), e); } return; } // Construct the data source for the report. ValueStack stack = invocation.getStack(); ValueStackDataSource stackDataSource = null; Connection conn = (Connection) stack.findValue(connection); if (conn == null) stackDataSource = new ValueStackDataSource(stack, dataSource); // Determine the directory that the report file is in and set the reportDirectory parameter // For WW 2.1.7: // ServletContext servletContext = ((ServletConfig) // invocation.getInvocationContext().get(ServletActionContext.SERVLET_CONFIG)).getServletContext(); ServletContext servletContext = (ServletContext) invocation.getInvocationContext().get(ServletActionContext.SERVLET_CONTEXT); String systemId = servletContext.getRealPath(finalLocation); Map parameters = new ValueStackShadowMap(stack); File directory = new File(systemId.substring(0, systemId.lastIndexOf(File.separator))); parameters.put("reportDirectory", directory); parameters.put(JRParameter.REPORT_LOCALE, invocation.getInvocationContext().getLocale()); // put timezone in jasper report parameter if (timeZone != null) { timeZone = conditionalParse(timeZone, invocation); final TimeZone tz = TimeZone.getTimeZone(timeZone); if (tz != null) { // put the report time zone parameters.put(JRParameter.REPORT_TIME_ZONE, tz); } } // Add any report parameters from action to param map. Map reportParams = (Map) stack.findValue(reportParameters); if (reportParams != null) { if (LOG.isDebugEnabled()) { LOG.debug("Found report parameters; adding to parameters...", new Object[0]); } parameters.putAll(reportParams); } byte[] output; JasperPrint jasperPrint; // Fill the report and produce a print object try { JasperReport jasperReport = (JasperReport) JRLoader.loadObject(new File(systemId)); if (conn == null) jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, stackDataSource); else jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn); } catch (JRException e) { LOG.error("Error building report for uri " + systemId, e); throw new ServletException(e.getMessage(), e); } // Export the print object to the desired output format try { if (contentDisposition != null || documentName != null) { final StringBuffer tmp = new StringBuffer(); tmp.append((contentDisposition == null) ? "inline" : contentDisposition); if (documentName != null) { tmp.append("; filename="); tmp.append(documentName); tmp.append("."); tmp.append(format.toLowerCase()); } response.setHeader("Content-disposition", tmp.toString()); } JRExporter exporter; if (format.equals(FORMAT_PDF)) { response.setContentType("application/pdf"); exporter = new JRPdfExporter(); } else if (format.equals(FORMAT_CSV)) { response.setContentType("text/csv"); exporter = new JRCsvExporter(); } else if (format.equals(FORMAT_HTML)) { response.setContentType("text/html"); // IMAGES_MAPS seems to be only supported as "backward compatible" from JasperReports 1.1.0 Map imagesMap = new HashMap(); request.getSession(true).setAttribute("IMAGES_MAP", imagesMap); exporter = new JRHtmlExporter(); exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, imagesMap); exporter.setParameter( JRHtmlExporterParameter.IMAGES_URI, request.getContextPath() + imageServletUrl); // Needed to support chart images: exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint); request.getSession().setAttribute("net.sf.jasperreports.j2ee.jasper_print", jasperPrint); } else if (format.equals(FORMAT_XLS)) { response.setContentType("application/vnd.ms-excel"); exporter = new JRXlsExporter(); } else if (format.equals(FORMAT_XML)) { response.setContentType("text/xml"); exporter = new JRXmlExporter(); } else if (format.equals(FORMAT_RTF)) { response.setContentType("application/rtf"); exporter = new JRRtfExporter(); } else { throw new ServletException("Unknown report format: " + format); } Map exportParams = (Map) stack.findValue(exportParameters); if (exportParams != null) { if (LOG.isDebugEnabled()) { LOG.debug("Found export parameters; adding to exporter parameters...", new Object[0]); } exporter.getParameters().putAll(exportParams); } output = exportReportToBytes(jasperPrint, exporter); } catch (JRException e) { String message = "Error producing " + format + " report for uri " + systemId; LOG.error(message, e); throw new ServletException(e.getMessage(), e); } response.setContentLength(output.length); // Will throw ServletException on IOException. writeReport(response, output); } /** * Writes report bytes to response output stream. * * @param response Current response. * @param output Report bytes to write. * @throws ServletException on stream IOException. */ private void writeReport(HttpServletResponse response, byte[] output) throws ServletException { ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); outputStream.write(output); outputStream.flush(); } catch (IOException e) { LOG.error("Error writing report output", e); throw new ServletException(e.getMessage(), e); } finally { try { if (outputStream != null) { outputStream.close(); } } catch (IOException e) { LOG.error("Error closing report output stream", e); throw new ServletException(e.getMessage(), e); } } } /** * Sets up result properties, parsing etc. * * @param invocation Current invocation. * @throws Exception on initialization error. */ private void initializeProperties(ActionInvocation invocation) throws Exception { if (dataSource == null && connection == null) { String message = "No dataSource specified..."; LOG.error(message); throw new RuntimeException(message); } if (dataSource != null) dataSource = conditionalParse(dataSource, invocation); format = conditionalParse(format, invocation); if (StringUtils.isEmpty(format)) { format = FORMAT_PDF; } if (contentDisposition != null) { contentDisposition = conditionalParse(contentDisposition, invocation); } if (documentName != null) { documentName = conditionalParse(documentName, invocation); } reportParameters = conditionalParse(reportParameters, invocation); exportParameters = conditionalParse(exportParameters, invocation); } /** * Run a Jasper report to CSV format and put the results in a byte array * * @param jasperPrint The Print object to render as CSV * @param exporter The exporter to use to export the report * @return A CSV formatted report * @throws net.sf.jasperreports.engine.JRException If there is a problem running the report */ private byte[] exportReportToBytes(JasperPrint jasperPrint, JRExporter exporter) throws JRException { byte[] output; ByteArrayOutputStream baos = new ByteArrayOutputStream(); exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint); exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos); if (delimiter != null) { exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, delimiter); } exporter.exportReport(); output = baos.toByteArray(); return output; } }
/** * This class converts the class name into a SEO friendly name by recognizing camel casing and * inserting dashes. This also converts everything to lower case if desired. And this will also * strip off the word <b>Action</b> from the class name. */ public class SEOActionNameBuilder implements ActionNameBuilder { private static final Logger LOG = LoggerFactory.getLogger(SEOActionNameBuilder.class); private String actionSuffix = "Action"; private boolean lowerCase; private String separator; @Inject public SEOActionNameBuilder( @Inject(value = "struts.convention.action.name.lowercase") String lowerCase, @Inject(value = "struts.convention.action.name.separator") String separator) { this.lowerCase = Boolean.parseBoolean(lowerCase); this.separator = separator; } /** * @param actionSuffix (Optional) Classes that end with these value will be mapped as actions * (defaults to "Action") */ @Inject(value = "struts.convention.action.suffix", required = false) public void setActionSuffix(String actionSuffix) { if (StringUtils.isNotBlank(actionSuffix)) { this.actionSuffix = actionSuffix; } } public String build(String className) { String actionName = className; if (actionName.equals(actionSuffix)) throw new IllegalStateException( "The action name cannot be the same as the action suffix [" + actionSuffix + "]"); // Truncate Action suffix if found if (actionName.endsWith(actionSuffix)) { actionName = actionName.substring(0, actionName.length() - actionSuffix.length()); } // Convert to underscores char[] ca = actionName.toCharArray(); StringBuilder build = new StringBuilder("" + ca[0]); boolean lower = true; for (int i = 1; i < ca.length; i++) { char c = ca[i]; if (Character.isUpperCase(c) && lower) { build.append(separator); lower = false; } else if (!Character.isUpperCase(c)) { lower = true; } build.append(c); } actionName = build.toString(); if (lowerCase) { actionName = actionName.toLowerCase(); } if (LOG.isTraceEnabled()) { LOG.trace("Changed action name from [#0] to [#1]", className, actionName); } return actionName; } }
/** Helper methods to access text from TextProviders */ public class TextProviderHelper { private static final Logger LOG = LoggerFactory.getLogger(TextProviderHelper.class); /** * Get a message from the first TextProvider encountered in the stack. If the first TextProvider * doesn't provide the message the default message is returned. The stack is searched if if no * TextProvider is found, or the message is not found. * * @param key the message key in the resource bundle * @param defaultMessage the message to return if not found (evaluated for OGNL) * @param args an array args to be used in a {@link java.text.MessageFormat} message * @param stack the value stack to use for finding the text * @return the message if found, otherwise the defaultMessage */ public static String getText( String key, String defaultMessage, List<Object> args, ValueStack stack) { return getText(key, defaultMessage, args, stack, true); } /** * Get a message from the first TextProvider encountered in the stack. If the first TextProvider * doesn't provide the message the default message is returned. * * <p>The search for a TextProvider is iterative from the root of the stack. * * <p>This method was refactored from {@link org.apache.struts2.components.Text} to use a * consistent implementation across UIBean components. * * @param key the message key in the resource bundle * @param defaultMessage the message to return if not found (evaluated for OGNL) * @param args an array args to be used in a {@link java.text.MessageFormat} message * @param stack the value stack to use for finding the text * @param searchStack search stack for the key * @return the message if found, otherwise the defaultMessage */ public static String getText( String key, String defaultMessage, List<Object> args, ValueStack stack, boolean searchStack) { String msg = null; TextProvider tp = null; for (Object o : stack.getRoot()) { if (o instanceof TextProvider) { tp = (TextProvider) o; msg = tp.getText(key, null, args, stack); break; } } if (msg == null) { // evaluate the defaultMesage as an OGNL expression if (searchStack) msg = stack.findString(defaultMessage); if (msg == null) { // use the defaultMessage literal value msg = defaultMessage; } if (LOG.isWarnEnabled()) { if (tp != null) { LOG.warn( "The first TextProvider in the ValueStack (" + tp.getClass().getName() + ") could not locate the message resource with key '" + key + "'"); } else { LOG.warn( "Could not locate the message resource '" + key + "' as there is no TextProvider in the ValueStack."); } if (defaultMessage.equals(msg)) { LOG.warn( "The default value expression '" + defaultMessage + "' was evaluated and did not match a property. The literal value '" + defaultMessage + "' will be used."); } else { LOG.warn( "The default value expression '" + defaultMessage + "' evaluated to '" + msg + "'"); } } } return msg; } /** * Get a message from the first TextProvider encountered in the stack. If the first TextProvider * doesn't provide the message the default message is returned. * * <p>The search for a TextProvider is iterative from the root of the stack. * * <p>This method was refactored from {@link org.apache.struts2.components.Text} to use a * consistent implementation across UIBean components. * * @param key the message key in the resource bundle * @param defaultMessage the message to return if not found * @param stack the value stack to use for finding the text * @return the message if found, otherwise the defaultMessage */ public static String getText(String key, String defaultMessage, ValueStack stack) { return getText(key, defaultMessage, Collections.emptyList(), stack); } }
/** * Implementation of PropertyAccessor that sets and gets properties by storing and looking up values * in Maps. * * @author Gabriel Zimmerman */ public class XWorkMapPropertyAccessor extends MapPropertyAccessor { private static final Logger LOG = LoggerFactory.getLogger(XWorkMapPropertyAccessor.class); private static final String[] INDEX_ACCESS_PROPS = new String[] {"size", "isEmpty", "keys", "values"}; private XWorkConverter xworkConverter; private ObjectFactory objectFactory; private ObjectTypeDeterminer objectTypeDeterminer; @Inject public void setXWorkConverter(XWorkConverter conv) { this.xworkConverter = conv; } @Inject public void setObjectFactory(ObjectFactory fac) { this.objectFactory = fac; } @Inject public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) { this.objectTypeDeterminer = ot; } @Override public Object getProperty(Map context, Object target, Object name) throws OgnlException { if (LOG.isDebugEnabled()) { LOG.debug("Entering getProperty (" + context + "," + target + "," + name + ")"); } ReflectionContextState.updateCurrentPropertyPath(context, name); // if this is one of the regular index access // properties then just let the superclass deal with the // get. if (name instanceof String && contains(INDEX_ACCESS_PROPS, (String) name)) { return super.getProperty(context, target, name); } Object result = null; try { result = super.getProperty(context, target, name); } catch (ClassCastException ex) { } if (result == null) { // find the key class and convert the name to that class Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); if (lastClass == null || lastProperty == null) { return null; } Object key = getKey(context, name); Map map = (Map) target; result = map.get(key); if (result == null && context.get(ReflectionContextState.CREATE_NULL_OBJECTS) != null && objectTypeDeterminer.shouldCreateIfNew(lastClass, lastProperty, target, null, false)) { Class valueClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, key); try { result = objectFactory.buildBean(valueClass, context); map.put(key, result); } catch (Exception exc) { } } } return result; } /** * @param array * @param name */ private boolean contains(String[] array, String name) { for (String anArray : array) { if (anArray.equals(name)) { return true; } } return false; } @Override public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException { if (LOG.isDebugEnabled()) { LOG.debug("Entering setProperty(" + context + "," + target + "," + name + "," + value + ")"); } Object key = getKey(context, name); Map map = (Map) target; map.put(key, getValue(context, value)); } private Object getValue(Map context, Object value) { Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); if (lastClass == null || lastProperty == null) { return value; } Class elementClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, null); if (elementClass == null) { return value; // nothing is specified, we assume it will be the value passed in. } return xworkConverter.convertValue(context, value, elementClass); } private Object getKey(Map context, Object name) { Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED); String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED); if (lastClass == null || lastProperty == null) { // return java.lang.String.class; // commented out the above -- it makes absolutely no sense for when setting basic maps! return name; } Class keyClass = objectTypeDeterminer.getKeyClass(lastClass, lastProperty); if (keyClass == null) { keyClass = java.lang.String.class; } return xworkConverter.convertValue(context, name, keyClass); } }
@StrutsTag( name = "include", tldTagClass = "it.micronixnet.gaf.struts2.gui.jsp.IncludeTag", description = "Include a servlet's output " + "(result of servlet or a JSP page)") public class Include extends GAFComponent { private static final Logger LOG = LoggerFactory.getLogger(Include.class); private static String encoding; private static boolean encodingDefined = true; protected String value; private HttpServletRequest req; private HttpServletResponse res; private static String defaultEncoding; public Include(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { super(stack); this.req = req; this.res = res; } @Inject(StrutsConstants.STRUTS_I18N_ENCODING) public void setDefaultEncoding(String encoding) { defaultEncoding = encoding; } public boolean end(Writer writer, String body) { String page = findString(value, "value", "You must specify the URL to include. Example: /foo.jsp"); StringBuilder urlBuf = new StringBuilder(); // Add URL urlBuf.append(page); // Add request parameters if (parameters.size() > 0) { urlBuf.append('?'); String concat = ""; // Set parameters Iterator iter = parameters.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object name = entry.getKey(); List values = (List) entry.getValue(); for (int i = 0; i < values.size(); i++) { urlBuf.append(concat); urlBuf.append(name); urlBuf.append('='); try { urlBuf.append(URLEncoder.encode(values.get(i).toString(), "UTF-8")); } catch (Exception e) { LOG.warn("unable to url-encode " + values.get(i).toString() + ", it will be ignored"); } concat = "&"; } } } String result = urlBuf.toString(); // Include try { include(result, writer, req, res); } catch (Exception e) { LOG.warn("Exception thrown during include of " + result, e); } return super.end(writer, body); } @StrutsTagAttribute(description = "The jsp/servlet output to include", required = true) public void setValue(String value) { this.value = value; } public static String getContextRelativePath(ServletRequest request, String relativePath) { String returnValue; if (relativePath.startsWith("/")) { returnValue = relativePath; } else if (!(request instanceof HttpServletRequest)) { returnValue = relativePath; } else { HttpServletRequest hrequest = (HttpServletRequest) request; String uri = (String) request.getAttribute("javax.servlet.include.servlet_path"); if (uri == null) { uri = RequestUtils.getServletPath(hrequest); } returnValue = uri.substring(0, uri.lastIndexOf('/')) + '/' + relativePath; } // .. is illegal in an absolute path according to the Servlet Spec and will cause // known problems on Orion application servers. if (returnValue.indexOf("..") != -1) { Stack stack = new Stack(); StringTokenizer pathParts = new StringTokenizer(returnValue.replace('\\', '/'), "/"); while (pathParts.hasMoreTokens()) { String part = pathParts.nextToken(); if (!part.equals(".")) { if (part.equals("..")) { stack.pop(); } else { stack.push(part); } } } StringBuilder flatPathBuffer = new StringBuilder(); for (int i = 0; i < stack.size(); i++) { flatPathBuffer.append("/").append(stack.elementAt(i)); } returnValue = flatPathBuffer.toString(); } return returnValue; } public void addParameter(String key, Object value) { // don't use the default implementation of addParameter, // instead, include tag requires that each parameter be a list of objects, // just like the HTTP servlet interfaces are (String[]) if (value != null) { List currentValues = (List) parameters.get(key); if (currentValues == null) { currentValues = new ArrayList(); parameters.put(key, currentValues); } currentValues.add(value); } } public static void include( String aResult, Writer writer, ServletRequest request, HttpServletResponse response) throws ServletException, IOException { String resourcePath = getContextRelativePath(request, aResult); RequestDispatcher rd = request.getRequestDispatcher(resourcePath); if (rd == null) { throw new ServletException("Not a valid resource path:" + resourcePath); } PageResponse pageResponse = new PageResponse(response); // Include the resource rd.include((HttpServletRequest) request, pageResponse); // write the response back to the JspWriter, using the correct encoding. String encoding = getEncoding(); if (encoding != null) { // use the encoding specified in the property file pageResponse.getContent().writeTo(writer, encoding); } else { // use the platform specific encoding pageResponse.getContent().writeTo(writer, null); } } /** * Get the encoding specified by the property 'struts.i18n.encoding' in struts.properties, or * return the default platform encoding if not specified. * * <p>Note that if the property is not initially defined, this will return the system default, * even if the property is later defined. This is mainly for performance reasons. Undefined * properties throw exceptions, which are a costly operation. * * <p>If the property is initially defined, it is read every time, until is is undefined, and then * the system default is used. * * <p>Why not cache it completely? Some applications will wish to be able to dynamically set the * encoding at runtime. * * @return The encoding to be used. */ private static String getEncoding() { if (encodingDefined) { try { encoding = defaultEncoding; } catch (IllegalArgumentException e) { encoding = System.getProperty("file.encoding"); encodingDefined = false; } } return encoding; } /** * Implementation of ServletOutputStream that stores all data written to it in a temporary buffer * accessible from {@link #getBuffer()} . * * @author <a href="*****@*****.**">Joe Walnes</a> * @author <a href="mailto:[email protected]">Scott Farquhar</a> */ static final class PageOutputStream extends ServletOutputStream { private FastByteArrayOutputStream buffer; public PageOutputStream() { buffer = new FastByteArrayOutputStream(); } /** Return all data that has been written to this OutputStream. */ public FastByteArrayOutputStream getBuffer() throws IOException { flush(); return buffer; } public void close() throws IOException { buffer.close(); } public void flush() throws IOException { buffer.flush(); } public void write(byte[] b, int o, int l) throws IOException { buffer.write(b, o, l); } public void write(int i) throws IOException { buffer.write(i); } public void write(byte[] b) throws IOException { buffer.write(b); } } /** * Simple wrapper to HTTPServletResponse that will allow getWriter() and getResponse() to be * called as many times as needed without causing conflicts. * * <p>The underlying outputStream is a wrapper around {@link PageOutputStream} which will store * the written content to a buffer. * * <p>This buffer can later be retrieved by calling {@link #getContent}. * * @author <a href="mailto:[email protected]">Joe Walnes</a> * @author <a href="mailto:[email protected]">Scott Farquhar</a> */ static final class PageResponse extends HttpServletResponseWrapper { protected PrintWriter pagePrintWriter; protected ServletOutputStream outputStream; private PageOutputStream pageOutputStream = null; /** Create PageResponse wrapped around an existing HttpServletResponse. */ public PageResponse(HttpServletResponse response) { super(response); } /** * Return the content buffered inside the {@link PageOutputStream}. * * @return * @throws IOException */ public FastByteArrayOutputStream getContent() throws IOException { // if we are using a writer, we need to flush the // data to the underlying outputstream. // most containers do this - but it seems Jetty 4.0.5 doesn't if (pagePrintWriter != null) { pagePrintWriter.flush(); } return ((PageOutputStream) getOutputStream()).getBuffer(); } /** * Return instance of {@link PageOutputStream} allowing all data written to stream to be stored * in temporary buffer. */ public ServletOutputStream getOutputStream() throws IOException { if (pageOutputStream == null) { pageOutputStream = new PageOutputStream(); } return pageOutputStream; } /** Return PrintWriter wrapper around PageOutputStream. */ public PrintWriter getWriter() throws IOException { if (pagePrintWriter == null) { pagePrintWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), getCharacterEncoding())); } return pagePrintWriter; } } }
/** * ProxyElementAdapter is a pass-through adapter for objects which already implement the Element * interface. All methods are proxied to the underlying Node except getParent(), getNextSibling() * and getPreviousSibling(), which are implemented by the abstract adapter node to work with the * parent adapter. * * <p>Note: this class wants to be (extend) both an AbstractElementAdapter and ProxyElementAdapter, * but its proxy-ness is winning right now. */ public class ProxyElementAdapter extends ProxyNodeAdapter implements Element { private Logger log = LoggerFactory.getLogger(this.getClass()); public ProxyElementAdapter(AdapterFactory factory, AdapterNode parent, Element value) { super(factory, parent, value); } /** Get the proxied Element */ protected Element element() { return (Element) getPropertyValue(); } protected List<Node> buildChildAdapters() { List<Node> adapters = new ArrayList<Node>(); NodeList children = node().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); Node adapter = wrap(child); if (adapter != null) { log.debug("wrapped child node: " + child.getNodeName()); adapters.add(adapter); } } return adapters; } // Proxied Element methods public String getTagName() { return element().getTagName(); } public boolean hasAttribute(String name) { return element().hasAttribute(name); } public String getAttribute(String name) { return element().getAttribute(name); } public boolean hasAttributeNS(String namespaceURI, String localName) { return element().hasAttributeNS(namespaceURI, localName); } public Attr getAttributeNode(String name) { log.debug("wrapping attribute"); return (Attr) wrap(element().getAttributeNode(name)); } // I'm overriding this just for clarity. The base impl is correct. public NodeList getElementsByTagName(String name) { return super.getElementsByTagName(name); } public String getAttributeNS(String namespaceURI, String localName) { return element().getAttributeNS(namespaceURI, localName); } public Attr getAttributeNodeNS(String namespaceURI, String localName) { return (Attr) wrap(element().getAttributeNodeNS(namespaceURI, localName)); } public NodeList getElementsByTagNameNS(String namespaceURI, String localName) { return super.getElementsByTagNameNS(namespaceURI, localName); } // Unsupported mutators of Element public void removeAttribute(String name) throws DOMException { throw new UnsupportedOperationException(); } public void removeAttributeNS(String namespaceURI, String localName) throws DOMException { throw new UnsupportedOperationException(); } public void setAttribute(String name, String value) throws DOMException { throw new UnsupportedOperationException(); } public Attr removeAttributeNode(Attr oldAttr) throws DOMException { throw new UnsupportedOperationException(); } public Attr setAttributeNode(Attr newAttr) throws DOMException { throw new UnsupportedOperationException(); } public Attr setAttributeNodeNS(Attr newAttr) throws DOMException { throw new UnsupportedOperationException(); } public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException { throw new UnsupportedOperationException(); } // end proxied Element methods // unsupported DOM level 3 methods public TypeInfo getSchemaTypeInfo() { throw operationNotSupported(); } public void setIdAttribute(String string, boolean b) throws DOMException { throw operationNotSupported(); } public void setIdAttributeNS(String string, String string1, boolean b) throws DOMException { throw operationNotSupported(); } public void setIdAttributeNode(Attr attr, boolean b) throws DOMException { throw operationNotSupported(); } // end DOM level 3 methods public String toString() { return "ProxyElement for: " + element(); } }