struts-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mr...@apache.org
Subject svn commit: r359671 - in /struts/flow/trunk: ./ src/java/ src/java/org/apache/struts/flow/ src/java/org/apache/struts/flow/core/ src/java/org/apache/struts/flow/ibatis/ src/test/org/apache/struts/flow/ibatis/
Date Thu, 29 Dec 2005 01:29:27 GMT
Author: mrdon
Date: Wed Dec 28 17:29:23 2005
New Revision: 359671

URL: http://svn.apache.org/viewcvs?rev=359671&view=rev
Log:
Changing how ibatis integration works, moving to more dynamic model
similar to how activerecord works:
 - Functions added based on ibatis sql maps
 - Supports dynamic functions like findBy* to match findByFoo and findByBar
 - Moved to "inheritence" model rather than generated bytecode
Added simple configuration

Added:
    struts/flow/trunk/src/java/flow-defaults.properties
    struts/flow/trunk/src/java/org/apache/struts/flow/FlowConfiguration.java
Removed:
    struts/flow/trunk/src/java/org/apache/struts/flow/ibatis/SqlMap.template
Modified:
    struts/flow/trunk/project.xml
    struts/flow/trunk/src/java/org/apache/struts/flow/FlowPlugIn.java
    struts/flow/trunk/src/java/org/apache/struts/flow/core/FlowException.java
    struts/flow/trunk/src/java/org/apache/struts/flow/ibatis/SqlMap.java
    struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/Foo.xml
    struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/TestSqlMap.java

Modified: struts/flow/trunk/project.xml
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/project.xml?rev=359671&r1=359670&r2=359671&view=diff
==============================================================================
--- struts/flow/trunk/project.xml (original)
+++ struts/flow/trunk/project.xml Wed Dec 28 17:29:23 2005
@@ -206,17 +206,6 @@
       <url>http://ibatis.apache.org/</url>
 	</dependency>
  
-  <dependency>
-	  <groupId>janino</groupId>
-	  <artifactId>janino</artifactId>
-	  <version>2.3.8</version>
-	  <properties>
-		<war.bundle>true</war.bundle>
-	  </properties>
-      <url>http://www.janino.net/</url>
-	</dependency>
- 
-  
   </dependencies>
   
   <build>

Added: struts/flow/trunk/src/java/flow-defaults.properties
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/java/flow-defaults.properties?rev=359671&view=auto
==============================================================================
--- struts/flow/trunk/src/java/flow-defaults.properties (added)
+++ struts/flow/trunk/src/java/flow-defaults.properties Wed Dec 28 17:29:23 2005
@@ -0,0 +1,6 @@
+flow.ibatis.dynamic.findBy.pattern=findBy(.*)
+flow.ibatis.dynamic.findBy.return=first
+flow.ibatis.dynamic.findBy.queryName=findBy
+flow.ibatis.dynamic.findAllBy.pattern=findAllBy(.*)
+flow.ibatis.dynamic.findAllBy.return=all
+flow.ibatis.dynamic.findAllBy.queryName=findAllBy

Added: struts/flow/trunk/src/java/org/apache/struts/flow/FlowConfiguration.java
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/java/org/apache/struts/flow/FlowConfiguration.java?rev=359671&view=auto
==============================================================================
--- struts/flow/trunk/src/java/org/apache/struts/flow/FlowConfiguration.java (added)
+++ struts/flow/trunk/src/java/org/apache/struts/flow/FlowConfiguration.java Wed Dec 28 17:29:23
2005
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 1999-2004 The Apache Software Foundation.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.struts.flow;
+
+import java.util.Properties;
+import java.io.*;
+import org.apache.struts.flow.core.FlowException;
+
+public class FlowConfiguration extends Properties {
+    
+    private Properties props;
+    
+    private static final FlowConfiguration self = new FlowConfiguration();
+    
+    public static FlowConfiguration getInstance() {
+        return self;
+    }
+    
+    private FlowConfiguration() {
+        super();
+        
+        try {
+            InputStream in = getClass().getResourceAsStream("/flow-defaults.properties");
+            load(in);
+            
+            in = getClass().getResourceAsStream("/flow.properties");
+            if (in != null) {
+                load(in);
+            }
+        } catch (IOException ex) {
+            throw new FlowException("Unable to load properties", ex);
+        }
+    }
+    
+}
+

Modified: struts/flow/trunk/src/java/org/apache/struts/flow/FlowPlugIn.java
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/java/org/apache/struts/flow/FlowPlugIn.java?rev=359671&r1=359670&r2=359671&view=diff
==============================================================================
--- struts/flow/trunk/src/java/org/apache/struts/flow/FlowPlugIn.java (original)
+++ struts/flow/trunk/src/java/org/apache/struts/flow/FlowPlugIn.java Wed Dec 28 17:29:23
2005
@@ -168,14 +168,6 @@
             SqlMapClient client = SqlMapClientBuilder.buildSqlMapClient(reader);
             SqlMap.setSqlMapClient(client);
             classesToRegister.add(SqlMap.class);
-        
-            Map classes = SqlMap.buildNamespaceClasses();
-            
-            for (Iterator i = classes.values().iterator(); i.hasNext(); ) {
-                Class cls = (Class) i.next();
-                log.info("Registring "+cls.getName());
-                classesToRegister.add(cls);
-            }
         } catch (Exception ex) {
             ex.printStackTrace();
         }

Modified: struts/flow/trunk/src/java/org/apache/struts/flow/core/FlowException.java
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/java/org/apache/struts/flow/core/FlowException.java?rev=359671&r1=359670&r2=359671&view=diff
==============================================================================
--- struts/flow/trunk/src/java/org/apache/struts/flow/core/FlowException.java (original)
+++ struts/flow/trunk/src/java/org/apache/struts/flow/core/FlowException.java Wed Dec 28 17:29:23
2005
@@ -96,7 +96,7 @@
      * @return a (fake) located exception
      * @throws FlowException or <code>LocatedRuntimeException</code>
      */
-    public static FlowException throwLocated(String message, Throwable thr, Location location)
throws FlowException {
+    public static FlowException throwLocated(String message, Throwable thr, Location location)
{
         if (thr instanceof FlowException) {
             FlowException pe = (FlowException)thr;
             pe.addLocation(location);

Modified: struts/flow/trunk/src/java/org/apache/struts/flow/ibatis/SqlMap.java
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/java/org/apache/struts/flow/ibatis/SqlMap.java?rev=359671&r1=359670&r2=359671&view=diff
==============================================================================
--- struts/flow/trunk/src/java/org/apache/struts/flow/ibatis/SqlMap.java (original)
+++ struts/flow/trunk/src/java/org/apache/struts/flow/ibatis/SqlMap.java Wed Dec 28 17:29:23
2005
@@ -16,19 +16,22 @@
 package org.apache.struts.flow.ibatis;
 
 import org.mozilla.javascript.*;
+
 import com.ibatis.sqlmap.engine.mapping.statement.*;
 import com.ibatis.sqlmap.engine.impl.*;
+
 import com.ibatis.sqlmap.client.*;
+import org.apache.struts.flow.*;
 import org.apache.struts.flow.core.*;
 import org.apache.struts.flow.core.javascript.*;
 import org.apache.struts.flow.sugar.*;
 
-import org.mozilla.javascript.JavaScriptException;
 import java.util.*;
 import java.io.*;
 import java.sql.SQLException;
 
-import org.codehaus.janino.SimpleCompiler;
+import java.util.regex.*;
+
 
 import org.apache.commons.logging.*;
 
@@ -37,108 +40,119 @@
  *
  *@jsname log
  */
-public class SqlMap extends ScriptableObject {
+public class SqlMap extends ScriptableObject implements Function {
     protected static SqlMapClient client;
     protected String namespace;
+    protected Function initFunction;
     protected Map statements = new HashMap();
+    protected List dynamicQueries = new ArrayList();
+    
+    public static final String FIRST_TYPE = "first";
+    public static final String ALL_TYPE = "all";
+    public static final String SCALAR_TYPE = "scalar";
     
     protected static final Log log = LogFactory.getLog(SqlMap.class);
 
 
     /**  Constructor for the JSLog object */
-    public SqlMap() { }
+    public SqlMap() { 
+        FlowConfiguration config = FlowConfiguration.getInstance();
+        dynamicQueries = buildPatterns(config);
+        
+    }
+
+    protected List buildPatterns(Map patterns) { 
+        List queries = new ArrayList();
+        String prefix = "flow.ibatis.dynamic.";
+        String key, pattern, query, type, name;
+        HashSet found = new HashSet();
+        for (Iterator i = patterns.keySet().iterator(); i.hasNext(); ) {
+            key = (String)i.next();
+            
+            if (key.startsWith(prefix)) {
+                name = key.substring(prefix.length(), key.lastIndexOf('.'));
+                if (!found.contains(name)) {
+                    pattern = (String) patterns.get(prefix + name + ".pattern");
+                    query = (String) patterns.get(prefix + name + ".query");
+                    type = (String) patterns.get(prefix + name + ".type");
+                    queries.add(new DynamicQuery(type, pattern, query));
+                    found.add(name);
+                }
+            }
+        }
+        return queries;
+    }
 
     public static void setSqlMapClient(SqlMapClient c) {
         client = c;
     }
-    
-    public static Map buildNamespaceClasses() {
-        InputStream in = SqlMap.class.getResourceAsStream("SqlMap.template");
-        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-        StringBuffer sb = new StringBuffer();
-        String line;
-        try {
-            while ((line = reader.readLine()) != null) {
-                sb.append(line);
-            }
-        } catch (IOException ex) {
-            ex.printStackTrace();
-            return null;
-        }
-           
-        String templateText = sb.toString();
-        
-        Map classes = new HashMap();
-        Set namespaces = getNamespaces();
-        String name, source;
-        for (Iterator i = namespaces.iterator(); i.hasNext(); ) {
-            name = (String) i.next(); 
-            source = templateText.replaceAll("%ns", name);
+    public Scriptable construct(Context cx, Scriptable scope, java.lang.Object[] args) {
+        
+        if (args.length == 0 && this.namespace == null) {
+            throw new FlowException("The namespace parameter is required");
+        }
+        
+        if (this.namespace == null) {
+            setNamespace(args[0].toString());
+        }
+        
+        if (args.length == 2 && this.initFunction == null) {
+            this.initFunction = (Function)args[1];
+        }
+        
+        SqlMap self = new SqlMap();
+        self.setPrototype(getPrototype());
+        self.setParentScope(getParentScope());
+        self.setNamespace(this.namespace);
+        
+        
+        if (this.initFunction != null) {
             try {
-                SimpleCompiler compiler = new SimpleCompiler(name+".java", new StringReader(source));
-                ClassLoader cl = compiler.getClassLoader();
-                
-                Class cls = cl.loadClass("org.apache.struts.flow.ibatis."+name);
-                if (cls != null) {
-                    classes.put(name, cls);
-                } else {
-                    System.err.println("Unable to compile class for namespace "+name);
-                }
+                this.initFunction.call(cx, scope, self, new Object[0]);
             } catch (Exception ex) {
-                ex.printStackTrace();
+                throw Context.throwAsScriptRuntimeEx(ex);
             }
         }
         
-        return classes;
+        return self;
+        
     }
-
-    public Object jsFunction_setNamespace(String ns) throws JavaScriptException {
-        setNamespace(ns);
-        return null;
+    
+    protected void setInitFunction(Function func) {
+        this.initFunction = func;
     }
-
-    public void setNamespace(String ns) {
-        log.debug("setting namespace");
+    
+    protected void setNamespace(String ns) {
         this.namespace = ns;
-        statements.clear();
-        SqlMapClientImpl cimpl = (SqlMapClientImpl)client;
-        SqlMapExecutorDelegate del = cimpl.getDelegate();
-        String name;
-        for (Iterator i = del.getMappedStatementNames(); i.hasNext(); ) {
-            name = (String) i.next();
-            log.debug("looking at statement "+name);
-            if (name.startsWith(namespace+".")) {
-                log.debug("adding statement "+name);
-                statements.put(name.substring(namespace.length() + 1), del.getMappedStatement(name));
+        log.debug("setting namespace: "+namespace);
+        if (client != null) {
+            SqlMapClientImpl cimpl = (SqlMapClientImpl)client;
+            SqlMapExecutorDelegate del = cimpl.getDelegate();
+            String name;
+            for (Iterator i = del.getMappedStatementNames(); i.hasNext(); ) {
+                name = (String) i.next();
+                log.debug("looking at statement "+name);
+                if (name.startsWith(namespace+".")) {
+                    log.debug("adding statement "+name);
+                    statements.put(name.substring(namespace.length() + 1), del.getMappedStatement(name));
+                }
             }
+        } else {
+            log.warn("ibatis client is missing");
         }
     }
-
-    public static Set getNamespaces() {
-        Set result = new HashSet();
-        SqlMapClientImpl cimpl = (SqlMapClientImpl)client;
-        SqlMapExecutorDelegate del = cimpl.getDelegate();
-        String name, ns;
-        int pos;
-        for (Iterator i = del.getMappedStatementNames(); i.hasNext(); ) {
-            name = (String) i.next();
-            pos = name.indexOf('.');
-            if (pos > -1) {
-                ns = name.substring(0, pos);    
-                result.add(ns);
-            }
-        }
-        return result;
+    
+    public Object call(Context cx, Scriptable scope, Scriptable thisObj, java.lang.Object[]
args) {
+        throw new FlowException("This function cannot not be called without the 'new' operator");
     }
 
-
     /**
      *  Gets the class name
      *
      *@return    The className value
      */
     public String getClassName() {
-        return (this.namespace == null ? "SqlMap" : this.namespace);
+        return (this.namespace);
     }
 
     public Object get(final String name, Scriptable start) {
@@ -146,38 +160,93 @@
         log.debug("getting property "+name);
         final MappedStatement ms = (MappedStatement) statements.get(name);
         if (ms != null) {
-            log.debug("found as a function");
-            return new ExtensionFunction() {    
-                public Object execute(Context cx, Scriptable scope, Scriptable thisObj, java.lang.Object[]
args) 
-                    throws IOException {
-                    
-                    Map params = null;
-                    if (args.length == 1 && args[0] instanceof Scriptable) {
-                        params = ConversionHelper.jsobjectToMap((Scriptable)args[0]);
-                    } else {
-                        params = ConversionHelper.jsobjectToMap(thisObj);
+            return buildStatementFunction(ms, null, null);
+        } else {
+            DynamicQuery query;
+            Matcher m;
+            for (Iterator i = dynamicQueries.iterator(); i.hasNext(); ) {
+                query = (DynamicQuery)i.next();
+                System.out.println("testing: "+query.pattern.pattern()+" against "+name);
+                m = query.getPattern().matcher(name);
+                if (m.matches()) {
+                    Map params = new HashMap();
+                    for (int x = 1; x<= m.groupCount(); x++) {
+                        String paramname = m.group(x);
+                        params.put("name"+x, paramname);
                     }
-                    String stmName = namespace+"."+name;
-                    Object result = null;
-                    StatementType type = ms.getStatementType();
-                    try {  
-                        if (type == StatementType.INSERT) {
-                            result = client.insert(stmName, params);
-                        } else if (type == StatementType.DELETE) {
-                            result = new Integer(client.delete(stmName, params));
-                        } else if (type == StatementType.UPDATE) {
-                            result = new Integer(client.update(stmName, params));
+                    return buildStatementFunction(ms, query.getType(), params);
+                }
+            }
+            
+            return super.get(name, start);
+        }
+    }
+
+    private Object buildStatementFunction(final MappedStatement ms, final String resultType,
final Map coreParams) {
+        log.debug("found as a function");
+        return new ExtensionFunction() {    
+            public Object execute(Context cx, Scriptable scope, Scriptable thisObj, java.lang.Object[]
args) 
+                throws IOException {
+                
+                Map params = null;
+                if (args.length == 1 && args[0] instanceof Scriptable) {
+                    params = ConversionHelper.jsobjectToMap((Scriptable)args[0]);
+                } else {
+                    params = ConversionHelper.jsobjectToMap(thisObj);
+                }
+                
+                if (coreParams != null) {
+                    params.putAll(coreParams);
+                }
+                String stmName = ms.getId();
+                Object result = null;
+                StatementType type = ms.getStatementType();
+                try {  
+                    if (type == StatementType.INSERT) {
+                        result = client.insert(stmName, params);
+                    } else if (type == StatementType.DELETE) {
+                        result = new Integer(client.delete(stmName, params));
+                    } else if (type == StatementType.UPDATE) {
+                        result = new Integer(client.update(stmName, params));
+                    } else {
+                        if (SCALAR_TYPE.equals(resultType)) {
+                            result = client.queryForObject(stmName, params);
+                        } else if (FIRST_TYPE.equals(resultType)) {
+                            List list = client.queryForList(stmName, params);
+                            if (list.size() > 0) {
+                                result = list.get(0);
+                            } 
                         } else {
                             result = client.queryForList(stmName, params);
                         }
-                    } catch (SQLException ex) {
-                        ex.printStackTrace();
                     }
-                    return result;
+                } catch (SQLException ex) {
+                    ex.printStackTrace();
                 }
-            };
-        } else {
-            return super.get(name, start);
+                return result;
+            }
+        };
+    }
+    
+    protected class DynamicQuery {
+        
+        private String type;
+        private Pattern pattern;
+        private MappedStatement query;
+        
+        public DynamicQuery(String type, String pattern, String query) {
+            this.type = type;
+            this.pattern = Pattern.compile(pattern);
+            this.query = (MappedStatement)statements.get(query);
+        }
+        public Pattern getPattern() {
+            return pattern;
+        }
+        public MappedStatement getQuery() {
+            return query;
+        }
+        public String getType() {
+            return type;
         }
     }
  

Modified: struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/Foo.xml
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/Foo.xml?rev=359671&r1=359670&r2=359671&view=diff
==============================================================================
--- struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/Foo.xml (original)
+++ struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/Foo.xml Wed Dec 28 17:29:23 2005
@@ -14,4 +14,5 @@
     from
       system
   </select>
+  
 </sqlMap>

Modified: struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/TestSqlMap.java
URL: http://svn.apache.org/viewcvs/struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/TestSqlMap.java?rev=359671&r1=359670&r2=359671&view=diff
==============================================================================
--- struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/TestSqlMap.java (original)
+++ struts/flow/trunk/src/test/org/apache/struts/flow/ibatis/TestSqlMap.java Wed Dec 28 17:29:23
2005
@@ -7,34 +7,96 @@
 import com.ibatis.sqlmap.engine.mapping.statement.*;
 import com.ibatis.sqlmap.engine.impl.*;
 import com.ibatis.sqlmap.client.*;
-import com.ibatis.common.resources.*;
+import com.ibatis.common.resources.*; 
+import org.mozilla.javascript.Scriptable;
+
+import org.apache.struts.flow.sugar.*;
 
 /* JUnitTest case for class: org.apache.struts.chain.commands.servlet.SqlMap */
 public class TestSqlMap extends TestCase {
 
-
     public TestSqlMap(String _name) {
         super(_name);
     }
+    
+    public void setUp() throws Exception {
+        Reader reader = Resources.getResourceAsReader("org/apache/struts/flow/ibatis/sqlmap.conf");
+        SqlMapClient client = SqlMapClientBuilder.buildSqlMapClient(reader);
+         
+        SqlMap.setSqlMapClient(client);
+        
+    }
 
+    private SqlMap getMap(String name) {
+        SqlMap fmap = new SqlMap();
+        return (SqlMap) fmap.construct(null, null, new Object[] {name});
+    }
 
-    public void testLoadNamespaceClasses() throws Exception {
+    public void testConstructor() throws Exception {
         
-        Reader reader = Resources.getResourceAsReader("org/apache/struts/flow/ibatis/sqlmap.conf");
-        SqlMapClient client = SqlMapClientBuilder.buildSqlMapClient(reader);
+        SqlMap nmap = getMap("Foo");
+        assertNotNull(nmap);
         
-        SqlMap.setSqlMapClient(client);
-        Map classes = SqlMap.buildNamespaceClasses();
-        assertNotNull(classes);
-        assertTrue("Size wrong, "+classes.size(), classes.size() == 2);
+        assertNotNull(nmap.statements);
+        assertTrue(nmap.statements.size() == 1);
+        
+        SqlMap map2 = (SqlMap) nmap.construct(null, null, new Object[0]);
+        assertNotNull(map2.statements);
+        assertTrue(map2.statements.size() == 1);
+        
+    }
+    
+    public void testGetIbatisFunctions() throws Exception {
+        
+        SqlMap nmap = getMap("Foo");
+        
+        Object obj = nmap.get("getAll", null);
+        assertTrue(obj instanceof ExtensionFunction);
+        
+        obj = nmap.get("asdfgetAll", null);
+        assertTrue(obj == Scriptable.NOT_FOUND);
+        
+    }
+    
+    
+    public void testBuildPatterns() throws Exception {
+        Map props = new HashMap();
+        props.put("flow.ibatis.dynamic.foo.pattern", "foo(.*)");
+        props.put("flow.ibatis.dynamic.foo.query", "getAll");
+        props.put("flow.ibatis.dynamic.foo.type", "all");
+        
+        props.put("flow.ibatis.dynamic.bar.pattern", "bar(.*)");
+        props.put("flow.ibatis.dynamic.bar.query", "getAll");
+        props.put("flow.ibatis.dynamic.bar.type", "scalar");
+        
+        SqlMap map = getMap("Foo");
+        List queries = map.buildPatterns(props);
+        assertNotNull(queries);
+        assertTrue(queries.size() == 2);
+        
+    }
+    
+    public void testDynamicFunctions() throws Exception {
+        Map props = new HashMap();
+        props.put("flow.ibatis.dynamic.foo.pattern", "foo(.*)");
+        props.put("flow.ibatis.dynamic.foo.query", "getAll");
+        props.put("flow.ibatis.dynamic.foo.type", "all");
+        
+        props.put("flow.ibatis.dynamic.bar.pattern", "bar(.*)");
+        props.put("flow.ibatis.dynamic.bar.query", "getAll");
+        props.put("flow.ibatis.dynamic.bar.type", "scalar");
+        
+        SqlMap map = getMap("Foo");
+        map.dynamicQueries = map.buildPatterns(props);
+        Object obj = map.get("fooBar", null);
+        assertTrue(obj instanceof ExtensionFunction);
         
-        Class cls = (Class) classes.get("System");
-        assertNotNull(cls);
+        obj = map.get("barBar", null);
+        assertTrue(obj instanceof ExtensionFunction);
         
-        assertTrue("org.apache.struts.flow.ibatis.System".equals(cls.getName()));
+        obj = map.get("fosoBar", null);
+        assertTrue(obj == Scriptable.NOT_FOUND);
         
-        SqlMap map = (SqlMap) cls.newInstance();
-        assertTrue("class name wrong ", "System".equals(map.getClassName()));
     }
 
     /* Executes the test case */



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@struts.apache.org
For additional commands, e-mail: dev-help@struts.apache.org


Mime
View raw message