Author: sebawagner
Date: Tue Nov 20 09:24:43 2012
New Revision: 1411600

URL: http://svn.apache.org/viewvc?rev=1411600&view=rev
Log:
OPENMEETINGS-460 fix sync of slave session to master store, fix connection 
admin UI and add new column "server address"

Added:
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ConnectionAdminUITransportDTO.java
Modified:
    
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClient.lzx
    
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClientListItem.lzx
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/cluster/sync/RestClient.java
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ClientListHashMapStore.java
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/IClientList.java
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/RoomClient.java
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/SlaveClientDto.java
    
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/remote/ConferenceService.java

Modified: 
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClient.lzx
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClient.lzx?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClient.lzx
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClient.lzx
 Tue Nov 20 09:24:43 2012
@@ -38,8 +38,9 @@
                        this.addHeaderItem(599,80);
                        this.addHeaderItem(600,240);
                        this.addHeaderItem(601,130);
-            this.addHeaderItem(602,150);
-            this.addHeaderItem(603,150);
+            this.addHeaderItem(602,100);
+            this.addHeaderItem(603,100);
+            this.addHeaderItem(1501,100);
                        this.getRoomClientsMap.doCall();
                </handler>
                
@@ -74,12 +75,18 @@
                <![CDATA[
                this.clearList();
                for (var i=0;i<records.length;i++){
+                       var tServer = "master";
+                       if (records[i].server != null) {
+                               tServer = "slave "+records[i].server.address+" 
["+records[i].server.id+"]";
+                       }
+               
                        new lz.roomClientListItem(this._innerlist._inn._inn,{
-                           obj:records[i],
-                           streamid:records[i].streamid,
-                           login:records[i].username,
-                        
dateConnected:parseDateToStringTime(records[i].connectedSince),
-                           scope:records[i].scope
+                           obj:records[i].roomClient,
+                           streamid:records[i].roomClient.streamid,
+                           login:records[i].roomClient.username,
+                        
dateConnected:parseDateToStringTime(records[i].roomClient.connectedSince),
+                           scope:records[i].roomClient.scope,
+                           serverAddr:tServer
                     });
                }
             this.sendInitialWidthUpdate();

Modified: 
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClientListItem.lzx
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClientListItem.lzx?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClientListItem.lzx
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/WebContent/src/modules/admin/connections/roomClientListItem.lzx
 Tue Nov 20 09:24:43 2012
@@ -27,11 +27,13 @@
        <attribute name="login" value="" type="string" />
        <attribute name="dateConnected" value="" type="string" />
     <attribute name="scope" value="" type="string" />
+    <attribute name="serverAddr" value="" type="string" />
        
        <turnOverTextItem text="$once{ parent.streamid }" />
        <turnOverTextItem text="$once{ parent.login }" />
        <turnOverTextItem text="$once{ parent.dateConnected }" />
     <turnOverTextItem text="$once{ parent.scope }" />
+    <turnOverTextItem text="$once{ parent.serverAddr }" />
     <simpleLabelButton labelid="603" >
         <handler name="onclick">
             if ($debug) Debug.write("onclick ",this);

Modified: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/cluster/sync/RestClient.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/cluster/sync/RestClient.java?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/cluster/sync/RestClient.java
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/cluster/sync/RestClient.java
 Tue Nov 20 09:24:43 2012
@@ -18,6 +18,7 @@
  */
 package org.apache.openmeetings.cluster.sync;
 
+import java.lang.reflect.Constructor;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -70,6 +71,8 @@ public class RestClient {
        
        private boolean pingRunning = false;
        
+       private static String nameSpaceForSlaveDto = 
"http://room.conference.openmeetings.apache.org/xsd";;
+       
        /**
         * returns true as long as the RestClient performs a ping and parses 
the result
         * 
@@ -373,40 +376,62 @@ public class RestClient {
        private List<SlaveClientDto> pingFromResult(OMElement result) throws 
Exception {
 
                QName pingResult = new QName(NAMESPACE_PREFIX, "return");
-               String nameSpaceForSlaveDto = 
"http://room.conference.openmeetings.apache.org/xsd";;
 
                @SuppressWarnings("unchecked")
                Iterator<OMElement> elements = 
result.getChildrenWithName(pingResult);
                List<SlaveClientDto> clients = new ArrayList<SlaveClientDto>();
                while (elements.hasNext()) {
                        OMElement resultElement = elements.next();
-
-                       Long roomId = null;
-                       String roomIdAsXmlString = 
resultElement.getFirstChildWithName(
-                                       new QName(nameSpaceForSlaveDto, 
"roomId")).getText();
-                       if (roomIdAsXmlString != null && 
roomIdAsXmlString.length() > 0) {
-                               roomId = 
Long.valueOf(roomIdAsXmlString).longValue();
-                       }
-
-                       Long userId = null;
-                       String userIdAsXmlString = 
resultElement.getFirstChildWithName(
-                                       new QName(nameSpaceForSlaveDto, 
"userId")).getText();
-                       if (userIdAsXmlString != null && 
userIdAsXmlString.length() > 0) {
-                               userId = 
Long.valueOf(userIdAsXmlString).longValue();
-                       }
-
-                       clients.add(new SlaveClientDto( //
-                                       resultElement.getFirstChildWithName(new 
QName(nameSpaceForSlaveDto, "streamid")).getText(), //
-                                       resultElement.getFirstChildWithName(new 
QName(nameSpaceForSlaveDto, "publicSID")).getText(), //
-                                       roomId, //
-                                       userId, //
-                                       resultElement.getFirstChildWithName(new 
QName(nameSpaceForSlaveDto, "firstName")).getText(), //
-                                       resultElement.getFirstChildWithName(new 
QName(nameSpaceForSlaveDto, "lastName")).getText(), //
-                                       
Boolean.valueOf(resultElement.getFirstChildWithName(new 
QName(nameSpaceForSlaveDto,"isAVClient")).getText()).booleanValue() //
-                       ) //
-                       );
+                       SlaveClientDto slaveDto = new SlaveClientDto( //
+                                       getElementTextByName(resultElement, 
"streamid", String.class), //
+                                       getElementTextByName(resultElement, 
"publicSID", String.class), //
+                                       getElementTextByName(resultElement, 
"roomId", Long.class), //
+                                       getElementTextByName(resultElement, 
"userId", Long.class), //
+                                       getElementTextByName(resultElement, 
"firstName", String.class), //
+                                       getElementTextByName(resultElement, 
"lastName", String.class), //
+                                       getElementTextByName(resultElement, 
"AVClient", Boolean.class), //
+                                       getElementTextByName(resultElement, 
"scope", String.class), //
+                                       getElementTextByName(resultElement, 
"username", String.class), //
+                                       getElementTextByName(resultElement, 
"connectedSince", String.class)
+                               ); //
+                       log.debug(slaveDto.toString());
+                       clients.add(slaveDto);
                }
                return clients;
        }
+       
+       /**
+        * Get and cast the element's text (if there is any)
+        * 
+        * @param resultElement
+        * @param elementName
+        * @param typeObject
+        * @return
+        */
+       private <T> T getElementTextByName(OMElement resultElement, String 
elementName, Class<T> typeObject) {
+               try {
+                       OMElement userIdElement = resultElement
+                                       .getFirstChildWithName(new 
QName(nameSpaceForSlaveDto, elementName));
+                       if (userIdElement != null && userIdElement.getText() != 
null
+                                       && userIdElement.getText().length() > 
0) {
+                               
+                               String defaultValue = userIdElement.getText();
+                               
+                               // Either this can be directly assigned or try 
to find a constructor
+                               // that handles it
+                               if 
(typeObject.isAssignableFrom(defaultValue.getClass())) {
+                                       return typeObject.cast(defaultValue);
+                               }
+                               Constructor<T> c = 
typeObject.getConstructor(defaultValue
+                                               .getClass());
+                               return c.newInstance(defaultValue);
+                               
+                       }
+               } catch (Exception err) {
+                       //Catch any class cast exception, but log only
+                       log.error("[getElementTextByName]", err);
+               }
+               return null;
+       }
 
 }

Modified: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ClientListHashMapStore.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ClientListHashMapStore.java?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ClientListHashMapStore.java
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ClientListHashMapStore.java
 Tue Nov 20 09:24:43 2012
@@ -22,10 +22,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map.Entry;
 
 import org.apache.openmeetings.OpenmeetingsVariables;
 import org.apache.openmeetings.data.beans.basic.SearchResult;
@@ -415,31 +413,17 @@ public class ClientListHashMapStore impl
 
        /*
         * (non-Javadoc)
-        * 
-        * @see
-        * 
org.apache.openmeetings.remote.red5.IClientList#getListByStartAndMax(int,
-        * int, java.lang.String, boolean)
+        * @see 
org.apache.openmeetings.conference.room.IClientList#getListByStartAndMax(int, 
int, java.lang.String, boolean)
         */
        // FIXME not sorted
-       public synchronized SearchResult<RoomClient> getListByStartAndMax(
+       public synchronized SearchResult<ClientSession> getListByStartAndMax(
                        int start, int max, String orderby, boolean asc) {
-               SearchResult<RoomClient> sResult = new 
SearchResult<RoomClient>();
+               SearchResult<ClientSession> sResult = new 
SearchResult<ClientSession>();
                sResult.setObjectName(RoomClient.class.getName());
                sResult.setRecords(Long.valueOf(clientList.size()).longValue());
-               LinkedList<RoomClient> myList = new LinkedList<RoomClient>();
-
-               int i = 0;
-               for (ClientSession cSession : clientList.values()) {
-                       if (i >= start) {
-                               myList.add(cSession.getRoomClient());
-                       }
-                       if (i > max) {
-                               break;
-                       }
-                       i++;
-               }
+               ArrayList<ClientSession> myList = new 
ArrayList<ClientSession>();
+               myList.addAll(clientList.values());
                sResult.setResult(myList);
-
                return sResult;
        }
 
@@ -500,10 +484,10 @@ public class ClientListHashMapStore impl
        // FIXME: Add multiple lists to enhance performance
        public void syncSlaveClientSession(Server server,
                        List<SlaveClientDto> clients) {
-
-               System.err.println("Session 1 Length: " + clientList.size());
+               
+               log.debug("Session 1 Length: " + clientList.size());
                for (ClientSession cSession : clientList.values()) {
-                       System.err.println("cSession: " + cSession.getServer()
+                       log.debug("cSession: " + cSession.getServer()
                                        + " cSession RCL " + 
cSession.getRoomClient());
                }
 
@@ -512,29 +496,37 @@ public class ClientListHashMapStore impl
                // makes no sense, we don't know anything about the start or 
end date
                // so at this point we can just remove them all and add them new
 
-               for (Iterator<Entry<String, ClientSession>> iter = clientList
-                               .entrySet().iterator(); iter.hasNext();) {
-                       Entry<String, ClientSession> entry = iter.next();
-                       if (entry.getValue().getServer() != null
-                                       && 
entry.getValue().getServer().equals(server)) {
-                               iter.remove();
-                       }
-               }
+//             for (Iterator<Entry<String, ClientSession>> iter = clientList
+//                             .entrySet().iterator(); iter.hasNext();) {
+//                     Entry<String, ClientSession> entry = iter.next();
+//                     if (entry.getValue().getServer() != null
+//                                     && 
entry.getValue().getServer().equals(server)) {
+//                             iter.remove();
+//                     }
+//             }
 
                log.debug("Session 2 Length: " + clientList.size());
 
                for (SlaveClientDto slaveClientDto : clients) {
-                       String uniqueKey = 
ClientSessionUtil.getClientSessionKey(null,
+                       String uniqueKey = 
ClientSessionUtil.getClientSessionKey(server,
                                        slaveClientDto.getStreamid());
+                       
+                       log.debug("Session 2 uniqueKey: " + uniqueKey);
+                       
                        clientList.put(
                                        uniqueKey,
-                                       new ClientSession(server, new 
RoomClient(slaveClientDto
-                                                       .getStreamid(), 
slaveClientDto.getPublicSID(),
-                                                       
slaveClientDto.getRoomId(), slaveClientDto
-                                                                       
.getUserId(),
-                                                       
slaveClientDto.getFirstName(), slaveClientDto
-                                                                       
.getLastName(), slaveClientDto
-                                                                       
.getIsAVClient())));
+                                       new ClientSession(server, new 
RoomClient(
+                                                               
slaveClientDto.getStreamid(), 
+                                                               
slaveClientDto.getPublicSID(),
+                                                               
slaveClientDto.getRoomId(), 
+                                                               
slaveClientDto.getUserId(),
+                                                               
slaveClientDto.getFirstName(), 
+                                                               
slaveClientDto.getLastName(), 
+                                                               
slaveClientDto.isAVClient(),
+                                                               
slaveClientDto.getUsername(),
+                                                               
slaveClientDto.getConnectedSince(),
+                                                               
slaveClientDto.getScope()
+                                                       )));
 
                }
 

Added: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ConnectionAdminUITransportDTO.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ConnectionAdminUITransportDTO.java?rev=1411600&view=auto
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ConnectionAdminUITransportDTO.java
 (added)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/ConnectionAdminUITransportDTO.java
 Tue Nov 20 09:24:43 2012
@@ -0,0 +1,5 @@
+package org.apache.openmeetings.conference.room;
+
+public class ConnectionAdminUITransportDTO {
+
+}

Modified: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/IClientList.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/IClientList.java?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/IClientList.java
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/IClientList.java
 Tue Nov 20 09:24:43 2012
@@ -115,7 +115,7 @@ public interface IClientList {
         * @param asc
         * @return
         */
-       public abstract SearchResult<RoomClient> getListByStartAndMax(int start,
+       public abstract SearchResult<ClientSession> getListByStartAndMax(int 
start,
                        int max, String orderby, boolean asc);
 
        public abstract void removeAllClients();

Modified: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/RoomClient.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/RoomClient.java?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/RoomClient.java
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/RoomClient.java
 Tue Nov 20 09:24:43 2012
@@ -28,6 +28,8 @@ import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.apache.openmeetings.utils.math.CalendarPatterns;
+
 public class RoomClient implements Serializable {
        
        private static final long serialVersionUID = 1831858089607111565L;
@@ -183,7 +185,8 @@ public class RoomClient implements Seria
     }
     
        public RoomClient(String streamid, String publicSID, Long room_id,
-                       Long user_id, String firstname, String lastname, 
boolean isAVClient) {
+                       Long user_id, String firstname, String lastname, 
boolean isAVClient,
+                       String username, String connectedSince, String scope) {
                super();
                this.streamid = streamid;
                this.publicSID = publicSID;
@@ -192,6 +195,9 @@ public class RoomClient implements Seria
                this.firstname = firstname;
                this.lastname = lastname;
                this.isAVClient = isAVClient;
+               this.username = username;
+               this.connectedSince = 
CalendarPatterns.parseDateWithHour(connectedSince);
+               this.scope = scope;
        }
 
        public void setUserObject(Long user_id, String username, String 
firstname, String lastname) {

Modified: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/SlaveClientDto.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/SlaveClientDto.java?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/SlaveClientDto.java
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/conference/room/SlaveClientDto.java
 Tue Nov 20 09:24:43 2012
@@ -18,6 +18,8 @@
  */
 package org.apache.openmeetings.conference.room;
 
+import org.apache.openmeetings.utils.math.CalendarPatterns;
+
 /**
  * 
  * Transfer object to send the master in a cluster the user load of a single
@@ -30,6 +32,9 @@ public class SlaveClientDto {
 
        private String streamid;
        private String publicSID;
+       private String username;
+       private String connectedSince;
+       private String scope;
        private String firstName;
        private String lastName;
        private Long userId;
@@ -44,10 +49,16 @@ public class SlaveClientDto {
                this.userId = roomClient.getUser_id();
                this.roomId = roomClient.getRoom_id();
                this.isAVClient = roomClient.getIsAVClient();
+               this.username = roomClient.getUsername();
+               this.scope = roomClient.getScope();
+               if (roomClient.getConnectedSince() != null) {
+                       this.connectedSince = 
CalendarPatterns.getDateWithTimeByMiliSeconds(roomClient.getConnectedSince());
+               }
        }
 
        public SlaveClientDto(String streamid, String publicSID, Long roomId2,
-                       Long userId2, String firstName, String lastName, 
boolean isAVClient) {
+                       Long userId2, String firstName, String lastName, 
boolean isAVClient,
+                       String scope, String username, String connectedSince) {
                this.streamid = streamid;
                this.publicSID = publicSID;
                this.firstName = firstName;
@@ -55,6 +66,9 @@ public class SlaveClientDto {
                this.userId = userId2;
                this.roomId = roomId2;
                this.isAVClient = isAVClient;
+               this.scope = scope;
+               this.username = username;
+               this.connectedSince = connectedSince;
        }
 
        public String getStreamid() {
@@ -105,12 +119,43 @@ public class SlaveClientDto {
                this.publicSID = publicSID;
        }
 
-       public boolean getIsAVClient() {
+       public boolean isAVClient() {
                return isAVClient;
        }
 
-       public void setIsAVClient(boolean isAVClient) {
+       public String getUsername() {
+               return username;
+       }
+
+       public void setUsername(String username) {
+               this.username = username;
+       }
+
+       public String getConnectedSince() {
+               return connectedSince;
+       }
+
+       public void setConnectedSince(String connectedSince) {
+               this.connectedSince = connectedSince;
+       }
+
+       public String getScope() {
+               return scope;
+       }
+
+       public void setScope(String scope) {
+               this.scope = scope;
+       }
+
+       public void setAVClient(boolean isAVClient) {
                this.isAVClient = isAVClient;
        }
 
+       @Override
+       public String toString() {
+               return " streamid: "+streamid+" publicSID: "+publicSID+" 
roomId: "+roomId+" userId: "+
+                               userId+" firstName: "+firstName+" lastName: 
"+lastName+" isAVClient: "+isAVClient+" scope: "+
+                               scope+" username: "+username+" connectedSince: 
"+connectedSince;
+       }
+
 }

Modified: 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/remote/ConferenceService.java
URL: 
http://svn.apache.org/viewvc/incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/remote/ConferenceService.java?rev=1411600&r1=1411599&r2=1411600&view=diff
==============================================================================
--- 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/remote/ConferenceService.java
 (original)
+++ 
incubator/openmeetings/trunk/singlewebapp/src/org/apache/openmeetings/remote/ConferenceService.java
 Tue Nov 20 09:24:43 2012
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.TimeZone;
 
 import org.apache.openmeetings.OpenmeetingsVariables;
+import org.apache.openmeetings.conference.room.ClientSession;
 import org.apache.openmeetings.conference.room.IClientList;
 import org.apache.openmeetings.conference.room.RoomClient;
 import org.apache.openmeetings.data.basic.AuthLevelmanagement;
@@ -759,7 +760,17 @@ public class ConferenceService {
                return clientListManager.getClientListByRoom(room_id);
        }
 
-       public SearchResult<RoomClient> getRoomClientsMap(String SID, int 
start, int max,
+       /**
+        * invoked in the admin interface to show the connections currently open
+        * 
+        * @param SID
+        * @param start
+        * @param max
+        * @param orderby
+        * @param asc
+        * @return
+        */
+       public SearchResult<ClientSession> getRoomClientsMap(String SID, int 
start, int max,
                        String orderby, boolean asc) {
                try {
                        Long users_id = sessionManagement.checkSession(SID);


Reply via email to