Commit 7018ba41 authored by daryl herzmann's avatar daryl herzmann

Merge pull request #409 from Redor/plugins

Update REST API plugin to 1.2.1
parents 82e13c81 19c5198d
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html> <html>
<head> <head>
<title>REST API Plugin Changelog</title> <title>REST API Plugin Changelog</title>
<style type="text/css"> <style type="text/css">
BODY { BODY {
font-size : 100%; font-size : 100%;
} }
BODY, TD, TH { BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif; font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em; font-size : 0.8em;
} }
H2 { H2 {
font-size : 10pt; font-size : 10pt;
font-weight : bold; font-weight : bold;
padding-left : 1em; padding-left : 1em;
} }
A:hover { A:hover {
text-decoration : none; text-decoration : none;
} }
H1 { H1 {
font-family : tahoma, arial, helvetica, sans-serif; font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em; font-size : 1.4em;
font-weight: bold; font-weight: bold;
border-bottom : 1px #ccc solid; border-bottom : 1px #ccc solid;
padding-bottom : 2px; padding-bottom : 2px;
} }
TT { TT {
font-family : courier new; font-family : courier new;
font-weight : bold; font-weight : bold;
color : #060; color : #060;
} }
PRE { PRE {
font-family : courier new; font-family : courier new;
font-size : 100%; font-size : 100%;
} }
</style> </style>
</head> </head>
<body> <body>
<h1> <h1>
REST API Plugin Changelog REST API Plugin Changelog
</h1> </h1>
<p><b>1.2.0</b> -- October 12, 2015</p> <p><b>1.2.1</b> -- November 24th, 2015</p>
<ul> <ul>
<li>[<a href='http://www.igniterealtime.org/issues/browse/OF-953'>OF-953</a>] - Updated JSP libraries.</li> <li>Fixed: Cluster issue by creating a new chat room</li>
<li>Requires Openfire 3.11.0.</li> </ul>
</ul>
<p><b>1.2.0</b> -- October 12th, 2015</p>
<p><b>1.1.7</b> -- November 13th, 2015</p> <ul>
<ul> <li>[<a href='http://www.igniterealtime.org/issues/browse/OF-953'>OF-953</a>] - Updated JSP libraries.</li>
<li>Added: Provide the possibility to use a Custom Auth Filter</li> <li>Requires Openfire 3.11.0.</li>
<li>Fixed: Preflight request will be not blocked by Authentication</li> </ul>
<li>Added: Group names in ChatRoom Entity</li>
</ul> <p><b>1.1.7</b> -- November 13th, 2015</p>
<p><b>1.1.6</b> -- September 24th, 2015</p> <ul>
<ul> <li>Added: Provide the possibility to use a Custom Auth Filter</li>
<li>Added: Endpoints to add / remove a user from a user group</li> <li>Fixed: Preflight request will be not blocked by Authentication</li>
<li>Fixed: Error response in JSON format</li> <li>Added: Group names in ChatRoom Entity</li>
</ul> </ul>
<p><b>1.1.6</b> -- September 24th, 2015</p>
<p><b>1.1.5</b> -- September 1st, 2015</p> <ul>
<ul> <li>Added: Endpoints to add / remove a user from a user group</li>
<li>Added: Send broadcast message to all online users</li> <li>Fixed: Error response in JSON format</li>
</ul> </ul>
<p><b>1.1.4</b> -- August 19th, 2015</p> <p><b>1.1.5</b> -- September 1st, 2015</p>
<ul> <ul>
<li>Added: get concurrent sessions (local or cluster wide)</li> <li>Added: Send broadcast message to all online users</li>
</ul> </ul>
<p><b>1.1.3</b> -- August 15th, 2015</p> <p><b>1.1.4</b> -- August 19th, 2015</p>
<ul> <ul>
<li>Added: get count of users unread messages</li> <li>Added: get concurrent sessions (local or cluster wide)</li>
</ul> </ul>
<p><b>1.1.2</b> -- August 4th, 2015</p> <p><b>1.1.3</b> -- August 15th, 2015</p>
<ul> <ul>
<li>Added: CORS to all endpoints</li> <li>Added: get count of users unread messages</li>
</ul> </ul>
<p><b>1.1.1</b> -- June 29th, 2015</p> <p><b>1.1.2</b> -- August 4th, 2015</p>
<ul> <ul>
<li>Added: new endpoint to close user sessions</li> <li>Added: CORS to all endpoints</li>
</ul> </ul>
<p><b>1.1.0</b> -- June 3rd, 2015</p> <p><b>1.1.1</b> -- June 29th, 2015</p>
<ul> <ul>
<li>Added: new endpoints for sessions (Get overview over all or specific user sessions)</li> <li>Added: new endpoint to close user sessions</li>
</ul> </ul>
<p><b>1.0.2</b> -- March 3rd, 2015</p> <p><b>1.1.0</b> -- June 3rd, 2015</p>
<ul> <ul>
<li>User will be kicked by a lockout (ban)</li> <li>Added: new endpoints for sessions (Get overview over all or specific user sessions)</li>
<li>Added: new endpoints for groups (Get overview over all or specific group and to create, update or delete a group)</li> </ul>
</ul>
<p><b>1.0.2</b> -- March 3rd, 2015</p>
<p><b>1.0.1</b> -- February 20th, 2015</p> <ul>
<ul> <li>User will be kicked by a lockout (ban)</li>
<li>Added possibility to rename a user (Thanks to JustMarried plugin)</li> <li>Added: new endpoints for groups (Get overview over all or specific group and to create, update or delete a group)</li>
<li>Adjusted HTTP Codes by conflict to HTTP CODE: 409</li> </ul>
<li>Added subject to Chat room</li>
<li>Disabled jersey logging on startup</li> <p><b>1.0.1</b> -- February 20th, 2015</p>
<li>By create a new chat room the chat room service will be created if it was not there</li> <ul>
</ul> <li>Added possibility to rename a user (Thanks to JustMarried plugin)</li>
<li>Adjusted HTTP Codes by conflict to HTTP CODE: 409</li>
<p><b>1.0.0</b> -- February 3rd, 2015</p> <li>Added subject to Chat room</li>
<ul> <li>Disabled jersey logging on startup</li>
<li>UserService plugin and MUC Service plugin are merged to the REST API plugin.</li> <li>By create a new chat room the chat room service will be created if it was not there</li>
<li>Extended REST API with JSON data format.</li> </ul>
</ul>
<p><b>1.0.0</b> -- February 3rd, 2015</p>
<p><b>0.1.0</b> -- November 14th, 2014</p> <ul>
<ul> <li>UserService plugin and MUC Service plugin are merged to the REST API plugin.</li>
<li>Initial release of REST API Plugin with possibility to manage system properties.</li> <li>Extended REST API with JSON data format.</li>
</ul> </ul>
</body> <p><b>0.1.0</b> -- November 14th, 2014</p>
</html> <ul>
<li>Initial release of REST API Plugin with possibility to manage system properties.</li>
</ul>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<plugin> <plugin>
<class>org.jivesoftware.openfire.plugin.rest.RESTServicePlugin</class> <class>org.jivesoftware.openfire.plugin.rest.RESTServicePlugin</class>
<name>REST API</name> <name>REST API</name>
<description>Allows administration over a RESTful API.</description> <description>Allows administration over a RESTful API.</description>
<author>Roman Soldatow</author> <author>Roman Soldatow</author>
<version>1.2.0</version> <version>1.2.1</version>
<date>10/12/2015</date> <date>11/24/2015</date>
<minServerVersion>3.11.0 alpha</minServerVersion> <minServerVersion>3.10.0</minServerVersion>
<adminconsole> <adminconsole>
<tab id="tab-server"> <tab id="tab-server">
<sidebar id="sidebar-server-settings"> <sidebar id="sidebar-server-settings">
<item id="rest-api" name="REST API" url="rest-api.jsp" <item id="rest-api" name="REST API" url="rest-api.jsp"
description="Click to manage the service that allows to configure the Openfire over a RESTFul API" /> description="Click to manage the service that allows to configure the Openfire over a RESTFul API" />
</sidebar> </sidebar>
</tab> </tab>
</adminconsole> </adminconsole>
</plugin> </plugin>
package org.jivesoftware.openfire.plugin.rest.controller; package org.jivesoftware.openfire.plugin.rest.controller;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.plugin.rest.entity.MUCChannelType; import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.plugin.rest.entity.MUCRoomEntities; import org.jivesoftware.openfire.plugin.rest.entity.MUCChannelType;
import org.jivesoftware.openfire.plugin.rest.entity.MUCRoomEntity; import org.jivesoftware.openfire.plugin.rest.entity.MUCRoomEntities;
import org.jivesoftware.openfire.plugin.rest.entity.ParticipantEntities; import org.jivesoftware.openfire.plugin.rest.entity.MUCRoomEntity;
import org.jivesoftware.openfire.plugin.rest.entity.ParticipantEntity; import org.jivesoftware.openfire.plugin.rest.entity.ParticipantEntities;
import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType; import org.jivesoftware.openfire.plugin.rest.entity.ParticipantEntity;
import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException; import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType;
import org.jivesoftware.openfire.muc.ConflictException; import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException;
import org.jivesoftware.openfire.muc.ForbiddenException; import org.jivesoftware.openfire.muc.ConflictException;
import org.jivesoftware.openfire.muc.MUCRole; import org.jivesoftware.openfire.muc.ForbiddenException;
import org.jivesoftware.openfire.muc.MUCRoom; import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.NotAllowedException; import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.group.ConcurrentGroupList; import org.jivesoftware.openfire.muc.NotAllowedException;
import org.jivesoftware.openfire.group.Group; import org.jivesoftware.openfire.muc.cluster.RoomUpdatedEvent;
import org.jivesoftware.openfire.plugin.rest.utils.MUCRoomUtils; import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.openfire.plugin.rest.utils.UserUtils; import org.jivesoftware.openfire.group.ConcurrentGroupList;
import org.jivesoftware.util.AlreadyExistsException; import org.jivesoftware.openfire.group.Group;
import org.xmpp.packet.JID; import org.jivesoftware.openfire.plugin.rest.utils.MUCRoomUtils;
import org.jivesoftware.openfire.plugin.rest.utils.UserUtils;
/** import org.jivesoftware.util.AlreadyExistsException;
* The Class MUCRoomController. import org.jivesoftware.util.cache.CacheFactory;
*/ import org.xmpp.packet.JID;
public class MUCRoomController {
/** The Constant INSTANCE. */ /**
public static final MUCRoomController INSTANCE = new MUCRoomController(); * The Class MUCRoomController.
*/
/** public class MUCRoomController {
* Gets the single instance of MUCRoomController. /** The Constant INSTANCE. */
* public static final MUCRoomController INSTANCE = new MUCRoomController();
* @return single instance of MUCRoomController
*/ /**
public static MUCRoomController getInstance() { * Gets the single instance of MUCRoomController.
return INSTANCE; *
} * @return single instance of MUCRoomController
*/
/** public static MUCRoomController getInstance() {
* Gets the chat rooms. return INSTANCE;
* }
* @param serviceName
* the service name /**
* @param channelType * Gets the chat rooms.
* the channel type *
* @param roomSearch * @param serviceName
* the room search * the service name
* @return the chat rooms * @param channelType
*/ * the channel type
public MUCRoomEntities getChatRooms(String serviceName, String channelType, String roomSearch, boolean expand) { * @param roomSearch
List<MUCRoom> rooms = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * the room search
.getChatRooms(); * @return the chat rooms
*/
List<MUCRoomEntity> mucRoomEntities = new ArrayList<MUCRoomEntity>(); public MUCRoomEntities getChatRooms(String serviceName, String channelType, String roomSearch, boolean expand) {
List<MUCRoom> rooms = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
for (MUCRoom chatRoom : rooms) { .getChatRooms();
if (roomSearch != null) {
if (!chatRoom.getName().contains(roomSearch)) { List<MUCRoomEntity> mucRoomEntities = new ArrayList<MUCRoomEntity>();
continue;
} for (MUCRoom chatRoom : rooms) {
} if (roomSearch != null) {
if (!chatRoom.getName().contains(roomSearch)) {
if (channelType.equals(MUCChannelType.ALL)) { continue;
mucRoomEntities.add(convertToMUCRoomEntity(chatRoom, expand)); }
} else if (channelType.equals(MUCChannelType.PUBLIC) && chatRoom.isPublicRoom()) { }
mucRoomEntities.add(convertToMUCRoomEntity(chatRoom, expand));
} if (channelType.equals(MUCChannelType.ALL)) {
} mucRoomEntities.add(convertToMUCRoomEntity(chatRoom, expand));
} else if (channelType.equals(MUCChannelType.PUBLIC) && chatRoom.isPublicRoom()) {
return new MUCRoomEntities(mucRoomEntities); mucRoomEntities.add(convertToMUCRoomEntity(chatRoom, expand));
} }
}
/**
* Gets the chat room. return new MUCRoomEntities(mucRoomEntities);
* }
* @param roomName
* the room name /**
* @param serviceName * Gets the chat room.
* the service name *
* @return the chat room * @param roomName
* @throws ServiceException * the room name
* the service exception * @param serviceName
*/ * the service name
public MUCRoomEntity getChatRoom(String roomName, String serviceName, boolean expand) throws ServiceException { * @return the chat room
MUCRoom chatRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * @throws ServiceException
.getChatRoom(roomName); * the service exception
*/
if (chatRoom == null) { public MUCRoomEntity getChatRoom(String roomName, String serviceName, boolean expand) throws ServiceException {
throw new ServiceException("Could not find the chat room", roomName, ExceptionType.ROOM_NOT_FOUND, Response.Status.NOT_FOUND); MUCRoom chatRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
} .getChatRoom(roomName);
MUCRoomEntity mucRoomEntity = convertToMUCRoomEntity(chatRoom, expand); if (chatRoom == null) {
return mucRoomEntity; throw new ServiceException("Could not find the chat room", roomName, ExceptionType.ROOM_NOT_FOUND, Response.Status.NOT_FOUND);
} }
/** MUCRoomEntity mucRoomEntity = convertToMUCRoomEntity(chatRoom, expand);
* Delete chat room. return mucRoomEntity;
* }
* @param roomName
* the room name /**
* @param serviceName * Delete chat room.
* the service name *
* @throws ServiceException * @param roomName
* the service exception * the room name
*/ * @param serviceName
public void deleteChatRoom(String roomName, String serviceName) throws ServiceException { * the service name
MUCRoom chatRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * @throws ServiceException
.getChatRoom(roomName.toLowerCase()); * the service exception
*/
if (chatRoom != null) { public void deleteChatRoom(String roomName, String serviceName) throws ServiceException {
chatRoom.destroyRoom(null, null); MUCRoom chatRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
} else { .getChatRoom(roomName.toLowerCase());
throw new ServiceException("Could not remove the channel", roomName, ExceptionType.ROOM_NOT_FOUND, Response.Status.NOT_FOUND);
} if (chatRoom != null) {
} chatRoom.destroyRoom(null, null);
} else {
/** throw new ServiceException("Could not remove the channel", roomName, ExceptionType.ROOM_NOT_FOUND, Response.Status.NOT_FOUND);
* Creates the chat room. }
* }
* @param serviceName
* the service name /**
* @param mucRoomEntity * Creates the chat room.
* the MUC room entity *
* @throws ServiceException * @param serviceName
* the service exception * the service name
*/ * @param mucRoomEntity
public void createChatRoom(String serviceName, MUCRoomEntity mucRoomEntity) throws ServiceException { * the MUC room entity
try { * @throws ServiceException
createRoom(mucRoomEntity, serviceName); * the service exception
} catch (NotAllowedException e) { */
throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(), public void createChatRoom(String serviceName, MUCRoomEntity mucRoomEntity) throws ServiceException {
ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); try {
} catch (ForbiddenException e) { createRoom(mucRoomEntity, serviceName);
throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(), } catch (NotAllowedException e) {
ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(),
} catch (ConflictException e) { ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(), } catch (ForbiddenException e) {
ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e); throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(),
} catch (AlreadyExistsException e) { ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(), } catch (ConflictException e) {
ExceptionType.ALREADY_EXISTS, Response.Status.CONFLICT, e); throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(),
} ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e);
} } catch (AlreadyExistsException e) {
throw new ServiceException("Could not create the channel", mucRoomEntity.getRoomName(),
/** ExceptionType.ALREADY_EXISTS, Response.Status.CONFLICT, e);
* Update chat room. }
* }
* @param roomName
* the room name /**
* @param serviceName * Update chat room.
* the service name *
* @param mucRoomEntity * @param roomName
* the MUC room entity * the room name
* @throws ServiceException * @param serviceName
* the service exception * the service name
*/ * @param mucRoomEntity
public void updateChatRoom(String roomName, String serviceName, MUCRoomEntity mucRoomEntity) * the MUC room entity
throws ServiceException { * @throws ServiceException
try { * the service exception
// If the room name is different throw exception */
if (!roomName.equals(mucRoomEntity.getRoomName())) { public void updateChatRoom(String roomName, String serviceName, MUCRoomEntity mucRoomEntity)
throw new ServiceException( throws ServiceException {
"Could not update the channel. The room name is different to the entity room name.", roomName, try {
ExceptionType.ILLEGAL_ARGUMENT_EXCEPTION, Response.Status.BAD_REQUEST); // If the room name is different throw exception
} if (!roomName.equals(mucRoomEntity.getRoomName())) {
createRoom(mucRoomEntity, serviceName); throw new ServiceException(
} catch (NotAllowedException e) { "Could not update the channel. The room name is different to the entity room name.", roomName,
throw new ServiceException("Could not update the channel", roomName, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); ExceptionType.ILLEGAL_ARGUMENT_EXCEPTION, Response.Status.BAD_REQUEST);
} catch (ForbiddenException e) { }
throw new ServiceException("Could not update the channel", roomName, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); createRoom(mucRoomEntity, serviceName);
} catch (ConflictException e) { } catch (NotAllowedException e) {
throw new ServiceException("Could not update the channel", roomName, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e); throw new ServiceException("Could not update the channel", roomName, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
} catch (AlreadyExistsException e) { } catch (ForbiddenException e) {
throw new ServiceException("Could not update the channel", mucRoomEntity.getRoomName(), throw new ServiceException("Could not update the channel", roomName, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
ExceptionType.ALREADY_EXISTS, Response.Status.CONFLICT, e); } catch (ConflictException e) {
} throw new ServiceException("Could not update the channel", roomName, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e);
} } catch (AlreadyExistsException e) {
throw new ServiceException("Could not update the channel", mucRoomEntity.getRoomName(),
/** ExceptionType.ALREADY_EXISTS, Response.Status.CONFLICT, e);
* Creates the room. }
* }
* @param mucRoomEntity
* the MUC room entity /**
* @param serviceName * Creates the room.
* the service name *
* @throws NotAllowedException * @param mucRoomEntity
* the not allowed exception * the MUC room entity
* @throws ForbiddenException * @param serviceName
* the forbidden exception * the service name
* @throws ConflictException * @throws NotAllowedException
* the conflict exception * the not allowed exception
* @throws AlreadyExistsException * @throws ForbiddenException
*/ * the forbidden exception
private void createRoom(MUCRoomEntity mucRoomEntity, String serviceName) throws NotAllowedException, * @throws ConflictException
ForbiddenException, ConflictException, AlreadyExistsException { * the conflict exception
* @throws AlreadyExistsException
// Set owner */
JID owner = XMPPServer.getInstance().createJID("admin", null); private void createRoom(MUCRoomEntity mucRoomEntity, String serviceName) throws NotAllowedException,
if (mucRoomEntity.getOwners() != null && mucRoomEntity.getOwners().size() > 0) { ForbiddenException, ConflictException, AlreadyExistsException {
owner = new JID(mucRoomEntity.getOwners().get(0));
} else { // Set owner
List<String> owners = new ArrayList<String>(); JID owner = XMPPServer.getInstance().createJID("admin", null);
owners.add(owner.toBareJID()); if (mucRoomEntity.getOwners() != null && mucRoomEntity.getOwners().size() > 0) {
mucRoomEntity.setOwners(owners); owner = new JID(mucRoomEntity.getOwners().get(0));
} } else {
List<String> owners = new ArrayList<String>();
// Check if chat service is available, if not create a new one owners.add(owner.toBareJID());
boolean serviceRegistered = XMPPServer.getInstance().getMultiUserChatManager().isServiceRegistered(serviceName); mucRoomEntity.setOwners(owners);
if(!serviceRegistered) { }
XMPPServer.getInstance().getMultiUserChatManager().createMultiUserChatService(serviceName, serviceName, false);
} // Check if chat service is available, if not create a new one
boolean serviceRegistered = XMPPServer.getInstance().getMultiUserChatManager().isServiceRegistered(serviceName);
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) if(!serviceRegistered) {
.getChatRoom(mucRoomEntity.getRoomName().toLowerCase(), owner); XMPPServer.getInstance().getMultiUserChatManager().createMultiUserChatService(serviceName, serviceName, false);
}
// Set values
room.setNaturalLanguageName(mucRoomEntity.getNaturalName()); MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
room.setSubject(mucRoomEntity.getSubject()); .getChatRoom(mucRoomEntity.getRoomName().toLowerCase(), owner);
room.setDescription(mucRoomEntity.getDescription());
room.setPassword(mucRoomEntity.getPassword()); // Set values
room.setPersistent(mucRoomEntity.isPersistent()); room.setNaturalLanguageName(mucRoomEntity.getNaturalName());
room.setPublicRoom(mucRoomEntity.isPublicRoom()); room.setSubject(mucRoomEntity.getSubject());
room.setRegistrationEnabled(mucRoomEntity.isRegistrationEnabled()); room.setDescription(mucRoomEntity.getDescription());
room.setCanAnyoneDiscoverJID(mucRoomEntity.isCanAnyoneDiscoverJID()); room.setPassword(mucRoomEntity.getPassword());
room.setCanOccupantsChangeSubject(mucRoomEntity.isCanOccupantsChangeSubject()); room.setPersistent(mucRoomEntity.isPersistent());
room.setCanOccupantsInvite(mucRoomEntity.isCanOccupantsInvite()); room.setPublicRoom(mucRoomEntity.isPublicRoom());
room.setChangeNickname(mucRoomEntity.isCanChangeNickname()); room.setRegistrationEnabled(mucRoomEntity.isRegistrationEnabled());
room.setModificationDate(mucRoomEntity.getModificationDate()); room.setCanAnyoneDiscoverJID(mucRoomEntity.isCanAnyoneDiscoverJID());
room.setLogEnabled(mucRoomEntity.isLogEnabled()); room.setCanOccupantsChangeSubject(mucRoomEntity.isCanOccupantsChangeSubject());
room.setLoginRestrictedToNickname(mucRoomEntity.isLoginRestrictedToNickname()); room.setCanOccupantsInvite(mucRoomEntity.isCanOccupantsInvite());
room.setMaxUsers(mucRoomEntity.getMaxUsers()); room.setChangeNickname(mucRoomEntity.isCanChangeNickname());
room.setMembersOnly(mucRoomEntity.isMembersOnly()); room.setModificationDate(mucRoomEntity.getModificationDate());
room.setModerated(mucRoomEntity.isModerated()); room.setLogEnabled(mucRoomEntity.isLogEnabled());
room.setLoginRestrictedToNickname(mucRoomEntity.isLoginRestrictedToNickname());
// Set broadcast presence roles room.setMaxUsers(mucRoomEntity.getMaxUsers());
if (mucRoomEntity.getBroadcastPresenceRoles() != null) { room.setMembersOnly(mucRoomEntity.isMembersOnly());
room.setRolesToBroadcastPresence(mucRoomEntity.getBroadcastPresenceRoles()); room.setModerated(mucRoomEntity.isModerated());
} else {
room.setRolesToBroadcastPresence(new ArrayList<String>()); // Fire RoomUpdateEvent if cluster is started
} if (ClusterManager.isClusteringStarted()) {
// Set all roles CacheFactory.doClusterTask(new RoomUpdatedEvent((LocalMUCRoom) room));
setRoles(room, mucRoomEntity); }
// Set creation date // Set broadcast presence roles
if (mucRoomEntity.getCreationDate() != null) { if (mucRoomEntity.getBroadcastPresenceRoles() != null) {
room.setCreationDate(mucRoomEntity.getCreationDate()); room.setRolesToBroadcastPresence(mucRoomEntity.getBroadcastPresenceRoles());
} else { } else {
room.setCreationDate(new Date()); room.setRolesToBroadcastPresence(new ArrayList<String>());
} }
// Set all roles
// Set modification date setRoles(room, mucRoomEntity);
if (mucRoomEntity.getModificationDate() != null) {
room.setModificationDate(mucRoomEntity.getModificationDate()); // Set creation date
} else { if (mucRoomEntity.getCreationDate() != null) {
room.setModificationDate(new Date()); room.setCreationDate(mucRoomEntity.getCreationDate());
} } else {
room.setCreationDate(new Date());
// Unlock the room, because the default configuration lock the room. }
room.unlock(room.getRole());
// Set modification date
// Save the room to the DB if the room should be persistant if (mucRoomEntity.getModificationDate() != null) {
if (room.isPersistent()) { room.setModificationDate(mucRoomEntity.getModificationDate());
room.saveToDB(); } else {
} room.setModificationDate(new Date());
} }
/** // Unlock the room, because the default configuration lock the room.
* Gets the room participants. room.unlock(room.getRole());
*
* @param roomName // Save the room to the DB if the room should be persistant
* the room name if (room.isPersistent()) {
* @param serviceName room.saveToDB();
* the service name }
* @return the room participants }
*/
public ParticipantEntities getRoomParticipants(String roomName, String serviceName) { /**
ParticipantEntities participantEntities = new ParticipantEntities(); * Gets the room participants.
List<ParticipantEntity> participants = new ArrayList<ParticipantEntity>(); *
* @param roomName
Collection<MUCRole> serverParticipants = XMPPServer.getInstance().getMultiUserChatManager() * the room name
.getMultiUserChatService(serviceName).getChatRoom(roomName).getParticipants(); * @param serviceName
* the service name
for (MUCRole role : serverParticipants) { * @return the room participants
ParticipantEntity participantEntity = new ParticipantEntity(); */
participantEntity.setJid(role.getRoleAddress().toFullJID()); public ParticipantEntities getRoomParticipants(String roomName, String serviceName) {
participantEntity.setRole(role.getRole().name()); ParticipantEntities participantEntities = new ParticipantEntities();
participantEntity.setAffiliation(role.getAffiliation().name()); List<ParticipantEntity> participants = new ArrayList<ParticipantEntity>();
participants.add(participantEntity); Collection<MUCRole> serverParticipants = XMPPServer.getInstance().getMultiUserChatManager()
} .getMultiUserChatService(serviceName).getChatRoom(roomName).getParticipants();
participantEntities.setParticipants(participants); for (MUCRole role : serverParticipants) {
return participantEntities; ParticipantEntity participantEntity = new ParticipantEntity();
} participantEntity.setJid(role.getRoleAddress().toFullJID());
participantEntity.setRole(role.getRole().name());
/** participantEntity.setAffiliation(role.getAffiliation().name());
* Convert to MUC room entity.
* participants.add(participantEntity);
* @param room }
* the room
* @return the MUC room entity participantEntities.setParticipants(participants);
*/ return participantEntities;
public MUCRoomEntity convertToMUCRoomEntity(MUCRoom room, boolean expand) { }
MUCRoomEntity mucRoomEntity = new MUCRoomEntity(room.getNaturalLanguageName(), room.getName(),
room.getDescription()); /**
* Convert to MUC room entity.
mucRoomEntity.setSubject(room.getSubject()); *
mucRoomEntity.setCanAnyoneDiscoverJID(room.canAnyoneDiscoverJID()); * @param room
mucRoomEntity.setCanChangeNickname(room.canChangeNickname()); * the room
mucRoomEntity.setCanOccupantsChangeSubject(room.canOccupantsChangeSubject()); * @return the MUC room entity
mucRoomEntity.setCanOccupantsInvite(room.canOccupantsInvite()); */
public MUCRoomEntity convertToMUCRoomEntity(MUCRoom room, boolean expand) {
mucRoomEntity.setPublicRoom(room.isPublicRoom()); MUCRoomEntity mucRoomEntity = new MUCRoomEntity(room.getNaturalLanguageName(), room.getName(),
mucRoomEntity.setPassword(room.getPassword()); room.getDescription());
mucRoomEntity.setPersistent(room.isPersistent());
mucRoomEntity.setRegistrationEnabled(room.isRegistrationEnabled()); mucRoomEntity.setSubject(room.getSubject());
mucRoomEntity.setLogEnabled(room.isLogEnabled()); mucRoomEntity.setCanAnyoneDiscoverJID(room.canAnyoneDiscoverJID());
mucRoomEntity.setLoginRestrictedToNickname(room.isLoginRestrictedToNickname()); mucRoomEntity.setCanChangeNickname(room.canChangeNickname());
mucRoomEntity.setMaxUsers(room.getMaxUsers()); mucRoomEntity.setCanOccupantsChangeSubject(room.canOccupantsChangeSubject());
mucRoomEntity.setMembersOnly(room.isMembersOnly()); mucRoomEntity.setCanOccupantsInvite(room.canOccupantsInvite());
mucRoomEntity.setModerated(room.isModerated());
mucRoomEntity.setPublicRoom(room.isPublicRoom());
ConcurrentGroupList<JID> owners = new ConcurrentGroupList<JID>(room.getOwners()); mucRoomEntity.setPassword(room.getPassword());
ConcurrentGroupList<JID> admins = new ConcurrentGroupList<JID>(room.getAdmins()); mucRoomEntity.setPersistent(room.isPersistent());
ConcurrentGroupList<JID> members = new ConcurrentGroupList<JID>(room.getMembers()); mucRoomEntity.setRegistrationEnabled(room.isRegistrationEnabled());
ConcurrentGroupList<JID> outcasts = new ConcurrentGroupList<JID>(room.getOutcasts()); mucRoomEntity.setLogEnabled(room.isLogEnabled());
mucRoomEntity.setLoginRestrictedToNickname(room.isLoginRestrictedToNickname());
if (expand) { mucRoomEntity.setMaxUsers(room.getMaxUsers());
for(Group ownerGroup : owners.getGroups()) { mucRoomEntity.setMembersOnly(room.isMembersOnly());
owners.addAllAbsent(ownerGroup.getAll()); mucRoomEntity.setModerated(room.isModerated());
}
for(Group adminGroup : admins.getGroups()) { ConcurrentGroupList<JID> owners = new ConcurrentGroupList<JID>(room.getOwners());
admins.addAllAbsent(adminGroup.getAll()); ConcurrentGroupList<JID> admins = new ConcurrentGroupList<JID>(room.getAdmins());
} ConcurrentGroupList<JID> members = new ConcurrentGroupList<JID>(room.getMembers());
for(Group memberGroup : members.getGroups()) { ConcurrentGroupList<JID> outcasts = new ConcurrentGroupList<JID>(room.getOutcasts());
members.addAllAbsent(memberGroup.getAll());
} if (expand) {
for(Group outcastGroup : outcasts.getGroups()) { for(Group ownerGroup : owners.getGroups()) {
outcasts.addAllAbsent(outcastGroup.getAll()); owners.addAllAbsent(ownerGroup.getAll());
} }
} for(Group adminGroup : admins.getGroups()) {
admins.addAllAbsent(adminGroup.getAll());
mucRoomEntity.setOwners(MUCRoomUtils.convertJIDsToStringList(owners)); }
mucRoomEntity.setAdmins(MUCRoomUtils.convertJIDsToStringList(admins)); for(Group memberGroup : members.getGroups()) {
mucRoomEntity.setMembers(MUCRoomUtils.convertJIDsToStringList(members)); members.addAllAbsent(memberGroup.getAll());
mucRoomEntity.setOutcasts(MUCRoomUtils.convertJIDsToStringList(outcasts)); }
for(Group outcastGroup : outcasts.getGroups()) {
mucRoomEntity.setOwnerGroups(MUCRoomUtils.convertGroupsToStringList(owners.getGroups())); outcasts.addAllAbsent(outcastGroup.getAll());
mucRoomEntity.setAdminGroups(MUCRoomUtils.convertGroupsToStringList(admins.getGroups())); }
mucRoomEntity.setMemberGroups(MUCRoomUtils.convertGroupsToStringList(members.getGroups())); }
mucRoomEntity.setOutcastGroups(MUCRoomUtils.convertGroupsToStringList(outcasts.getGroups()));
mucRoomEntity.setOwners(MUCRoomUtils.convertJIDsToStringList(owners));
mucRoomEntity.setBroadcastPresenceRoles(room.getRolesToBroadcastPresence()); mucRoomEntity.setAdmins(MUCRoomUtils.convertJIDsToStringList(admins));
mucRoomEntity.setMembers(MUCRoomUtils.convertJIDsToStringList(members));
mucRoomEntity.setCreationDate(room.getCreationDate()); mucRoomEntity.setOutcasts(MUCRoomUtils.convertJIDsToStringList(outcasts));
mucRoomEntity.setModificationDate(room.getModificationDate());
mucRoomEntity.setOwnerGroups(MUCRoomUtils.convertGroupsToStringList(owners.getGroups()));
return mucRoomEntity; mucRoomEntity.setAdminGroups(MUCRoomUtils.convertGroupsToStringList(admins.getGroups()));
} mucRoomEntity.setMemberGroups(MUCRoomUtils.convertGroupsToStringList(members.getGroups()));
mucRoomEntity.setOutcastGroups(MUCRoomUtils.convertGroupsToStringList(outcasts.getGroups()));
/**
* Reset roles. mucRoomEntity.setBroadcastPresenceRoles(room.getRolesToBroadcastPresence());
*
* @param room mucRoomEntity.setCreationDate(room.getCreationDate());
* the room mucRoomEntity.setModificationDate(room.getModificationDate());
* @param mucRoomEntity
* the muc room entity return mucRoomEntity;
* @throws ForbiddenException }
* the forbidden exception
* @throws NotAllowedException /**
* the not allowed exception * Reset roles.
* @throws ConflictException *
* the conflict exception * @param room
*/ * the room
private void setRoles(MUCRoom room, MUCRoomEntity mucRoomEntity) throws ForbiddenException, NotAllowedException, * @param mucRoomEntity
ConflictException { * the muc room entity
List<JID> roles = new ArrayList<JID>(); * @throws ForbiddenException
Collection<JID> owners = new ArrayList<JID>(); * the forbidden exception
Collection<JID> existingOwners = new ArrayList<JID>(); * @throws NotAllowedException
* the not allowed exception
List<JID> mucRoomEntityOwners = MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getOwners()); * @throws ConflictException
owners.addAll(room.getOwners()); * the conflict exception
*/
// Find same owners private void setRoles(MUCRoom room, MUCRoomEntity mucRoomEntity) throws ForbiddenException, NotAllowedException,
for (JID jid : owners) { ConflictException {
if (mucRoomEntityOwners.contains(jid)) { List<JID> roles = new ArrayList<JID>();
existingOwners.add(jid); Collection<JID> owners = new ArrayList<JID>();
} Collection<JID> existingOwners = new ArrayList<JID>();
}
List<JID> mucRoomEntityOwners = MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getOwners());
// Don't delete the same owners owners.addAll(room.getOwners());
owners.removeAll(existingOwners);
room.addOwners(MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getOwners()), room.getRole()); // Find same owners
for (JID jid : owners) {
// Collect all roles to reset if (mucRoomEntityOwners.contains(jid)) {
roles.addAll(owners); existingOwners.add(jid);
roles.addAll(room.getAdmins()); }
roles.addAll(room.getMembers()); }
roles.addAll(room.getOutcasts());
// Don't delete the same owners
for (JID jid : roles) { owners.removeAll(existingOwners);
room.addNone(jid, room.getRole()); room.addOwners(MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getOwners()), room.getRole());
}
// Collect all roles to reset
room.addOwners(MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getOwners()), room.getRole()); roles.addAll(owners);
if (mucRoomEntity.getAdmins() != null) { roles.addAll(room.getAdmins());
room.addAdmins(MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getAdmins()), room.getRole()); roles.addAll(room.getMembers());
} roles.addAll(room.getOutcasts());
if (mucRoomEntity.getMembers() != null) {
for (String memberJid : mucRoomEntity.getMembers()) { for (JID jid : roles) {
room.addMember(new JID(memberJid), null, room.getRole()); room.addNone(jid, room.getRole());
} }
}
if (mucRoomEntity.getOutcasts() != null) { room.addOwners(MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getOwners()), room.getRole());
for (String outcastJid : mucRoomEntity.getOutcasts()) { if (mucRoomEntity.getAdmins() != null) {
room.addOutcast(new JID(outcastJid), null, room.getRole()); room.addAdmins(MUCRoomUtils.convertStringsToJIDs(mucRoomEntity.getAdmins()), room.getRole());
} }
} if (mucRoomEntity.getMembers() != null) {
} for (String memberJid : mucRoomEntity.getMembers()) {
room.addMember(new JID(memberJid), null, room.getRole());
/** }
* Adds the admin. }
* if (mucRoomEntity.getOutcasts() != null) {
* @param serviceName for (String outcastJid : mucRoomEntity.getOutcasts()) {
* the service name room.addOutcast(new JID(outcastJid), null, room.getRole());
* @param roomName }
* the room name }
* @param jid }
* the jid
* @throws ServiceException /**
* the service exception * Adds the admin.
*/ *
public void addAdmin(String serviceName, String roomName, String jid) throws ServiceException { * @param serviceName
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * the service name
.getChatRoom(roomName.toLowerCase()); * @param roomName
try { * the room name
room.addAdmin(UserUtils.checkAndGetJID(jid), room.getRole()); * @param jid
} catch (ForbiddenException e) { * the jid
throw new ServiceException("Could not add admin", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); * @throws ServiceException
} catch (ConflictException e) { * the service exception
throw new ServiceException("Could not add admin", jid, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e); */
} public void addAdmin(String serviceName, String roomName, String jid) throws ServiceException {
} MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
.getChatRoom(roomName.toLowerCase());
/** try {
* Adds the owner. room.addAdmin(UserUtils.checkAndGetJID(jid), room.getRole());
* } catch (ForbiddenException e) {
* @param serviceName throw new ServiceException("Could not add admin", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
* the service name } catch (ConflictException e) {
* @param roomName throw new ServiceException("Could not add admin", jid, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e);
* the room name }
* @param jid }
* the jid
* @throws ServiceException /**
* the service exception * Adds the owner.
*/ *
public void addOwner(String serviceName, String roomName, String jid) throws ServiceException { * @param serviceName
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * the service name
.getChatRoom(roomName.toLowerCase()); * @param roomName
try { * the room name
room.addOwner(UserUtils.checkAndGetJID(jid), room.getRole()); * @param jid
} catch (ForbiddenException e) { * the jid
throw new ServiceException("Could not add owner", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); * @throws ServiceException
} * the service exception
} */
public void addOwner(String serviceName, String roomName, String jid) throws ServiceException {
/** MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
* Adds the member. .getChatRoom(roomName.toLowerCase());
* try {
* @param serviceName room.addOwner(UserUtils.checkAndGetJID(jid), room.getRole());
* the service name } catch (ForbiddenException e) {
* @param roomName throw new ServiceException("Could not add owner", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
* the room name }
* @param jid }
* the jid
* @throws ServiceException /**
* the service exception * Adds the member.
*/ *
public void addMember(String serviceName, String roomName, String jid) throws ServiceException { * @param serviceName
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * the service name
.getChatRoom(roomName.toLowerCase()); * @param roomName
try { * the room name
room.addMember(UserUtils.checkAndGetJID(jid), null, room.getRole()); * @param jid
} catch (ForbiddenException e) { * the jid
throw new ServiceException("Could not add member", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); * @throws ServiceException
} catch (ConflictException e) { * the service exception
throw new ServiceException("Could not add member", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); */
} public void addMember(String serviceName, String roomName, String jid) throws ServiceException {
} MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
.getChatRoom(roomName.toLowerCase());
/** try {
* Adds the outcast. room.addMember(UserUtils.checkAndGetJID(jid), null, room.getRole());
* } catch (ForbiddenException e) {
* @param serviceName throw new ServiceException("Could not add member", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
* the service name } catch (ConflictException e) {
* @param roomName throw new ServiceException("Could not add member", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
* the room name }
* @param jid }
* the jid
* @throws ServiceException /**
* the service exception * Adds the outcast.
*/ *
public void addOutcast(String serviceName, String roomName, String jid) throws ServiceException { * @param serviceName
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * the service name
.getChatRoom(roomName.toLowerCase()); * @param roomName
try { * the room name
room.addOutcast(UserUtils.checkAndGetJID(jid), null, room.getRole()); * @param jid
} catch (NotAllowedException e) { * the jid
throw new ServiceException("Could not add outcast", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); * @throws ServiceException
} catch (ForbiddenException e) { * the service exception
throw new ServiceException("Could not add outcast", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); */
} catch (ConflictException e) { public void addOutcast(String serviceName, String roomName, String jid) throws ServiceException {
throw new ServiceException("Could not add outcast", jid, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e); MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
} .getChatRoom(roomName.toLowerCase());
} try {
room.addOutcast(UserUtils.checkAndGetJID(jid), null, room.getRole());
/** } catch (NotAllowedException e) {
* Delete affiliation. throw new ServiceException("Could not add outcast", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
* } catch (ForbiddenException e) {
* @param serviceName throw new ServiceException("Could not add outcast", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
* the service name } catch (ConflictException e) {
* @param roomName throw new ServiceException("Could not add outcast", jid, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e);
* the room name }
* @param jid }
* the jid
* @throws ServiceException /**
* the service exception * Delete affiliation.
*/ *
public void deleteAffiliation(String serviceName, String roomName, String jid) throws ServiceException { * @param serviceName
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName) * the service name
.getChatRoom(roomName.toLowerCase()); * @param roomName
try { * the room name
room.addNone(UserUtils.checkAndGetJID(jid), room.getRole()); * @param jid
} catch (ForbiddenException e) { * the jid
throw new ServiceException("Could not delete affiliation", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e); * @throws ServiceException
} catch (ConflictException e) { * the service exception
throw new ServiceException("Could not delete affiliation", jid, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e); */
} public void deleteAffiliation(String serviceName, String roomName, String jid) throws ServiceException {
} MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(serviceName)
.getChatRoom(roomName.toLowerCase());
try {
room.addNone(UserUtils.checkAndGetJID(jid), room.getRole());
} catch (ForbiddenException e) {
throw new ServiceException("Could not delete affiliation", jid, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN, e);
} catch (ConflictException e) {
throw new ServiceException("Could not delete affiliation", jid, ExceptionType.NOT_ALLOWED, Response.Status.CONFLICT, e);
}
}
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment