Lucio Lelii 2015-11-24 18:26:46 +00:00
parent e613651151
commit a1d1770cb2
16 changed files with 552 additions and 186 deletions

View File

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.gcube.common</groupId>
<artifactId>common-authorization</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>2.0.0-SNAPSHOT</version>
<name>authorization service common library</name>
<parent>
@ -22,6 +22,12 @@
<artifactId>common-scope</artifactId>
<version>[1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT)</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>

View File

@ -11,49 +11,49 @@ import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.FIELD)
public class AuthorizationEntry {
private String userName;
private String clientId;
private List<String> roles;
private String scope;
private List<BannedService> bannedServices = new ArrayList<BannedService>();
private String context;
private List<CalledService> bannedServices = new ArrayList<CalledService>();
protected AuthorizationEntry(){}
public AuthorizationEntry(String userName, List<String> roles, String scope) {
public AuthorizationEntry(String clientId, List<String> roles, String context) {
super();
this.userName = userName;
this.clientId = clientId;
this.roles = roles;
this.scope = scope;
this.context = context;
}
public AuthorizationEntry(String userName, List<String> roles, String scope, List<BannedService> bannedServices) {
public AuthorizationEntry(String userName, List<String> roles, String scope, List<CalledService> bannedServices) {
this(userName, roles, scope);
this.bannedServices = bannedServices;
}
public String getUserName() {
return userName;
public String getClientId() {
return clientId;
}
public List<String> getRoles() {
return roles;
}
public String getScope() {
return scope;
public String getContext() {
return context;
}
public List<BannedService> getBannedServices() {
public List<CalledService> getBannedServices() {
return bannedServices;
}
public void setBannedServices(List<BannedService> bannedServices) {
public void setBannedServices(List<CalledService> bannedServices) {
this.bannedServices = bannedServices;
}
@Override
public String toString() {
return "AuthorizationEntry [userName=" + userName + ", roles=" + roles
+ ", scope=" + scope + " bannedServices "+ bannedServices+"]";
return "AuthorizationEntry [clientId=" + clientId + ", roles=" + roles
+ ", context=" + context + " bannedServices "+ bannedServices+"]";
}

View File

@ -2,13 +2,9 @@ package org.gcube.common.authorization.library;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.gcube.common.authorization.library.annotations.IsAllowedFor;
import org.gcube.common.authorization.library.annotations.SubjectToQuota;
import org.gcube.common.authorization.library.provider.AuthorizationProvider;
import org.gcube.common.authorization.library.provider.UserInfo;
import org.gcube.common.authorization.library.provider.ClientInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -21,7 +17,7 @@ public class AuthorizationInvocationHandler<T, I extends T> implements Invocatio
private Object obj;
ResourceAuthorizationProxy<T, I> resourceAuthorizationProxy;
protected AuthorizationInvocationHandler(I obj, String className, ResourceAuthorizationProxy<T, I> resourceAuthorizationProxy) {
handledClass = className;
this.obj = obj;
@ -31,12 +27,12 @@ public class AuthorizationInvocationHandler<T, I extends T> implements Invocatio
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
log.trace("calling proxed method "+method.getName()+" on "+handledClass);
UserInfo info = AuthorizationProvider.instance.get();
checkSubjectToQuota(info, method);
checkIsAllowedFor(info, method);
ClientInfo info = AuthorizationProvider.instance.get();
//checkSubjectToQuota(info, method);
//checkIsAllowedFor(info, method);
return method.invoke(obj, args);
}
/*
private static boolean isOneElementContainedinRoles(List<String> elements, String[] allowedRoles){
for (String role: allowedRoles )
if (elements.contains(role))
@ -44,11 +40,11 @@ public class AuthorizationInvocationHandler<T, I extends T> implements Invocatio
return false;
}
private void checkSubjectToQuota(UserInfo info, Method method){
private void checkSubjectToQuota(ClientInfo info, Method method){
if(method.isAnnotationPresent(SubjectToQuota.class)){
BannedService service = new BannedService(resourceAuthorizationProxy.getServiceClass(), resourceAuthorizationProxy.getServiceName());
log.debug("subjectToQuota annotation present, checking for service {} in bannedServices {}",service, info.getBannedServices());
if (info.getBannedServices().contains(service)){
if (info.getPolicies().contains(service)){
String message = "blocking method "+method.getName()+" for user "+info.getUserName()+": overquota reached";
log.warn(message);
throw new SecurityException(message);
@ -56,7 +52,7 @@ public class AuthorizationInvocationHandler<T, I extends T> implements Invocatio
} else log.debug("is subjectToQuota not present in "+method.getName());
}
private void checkIsAllowedFor(UserInfo info, Method method){
private void checkIsAllowedFor(ClientInfo info, Method method){
if(method.isAnnotationPresent(IsAllowedFor.class)){
IsAllowedFor allowed = method.getAnnotation(IsAllowedFor.class);
if (allowed.roles().length>0 && !isOneElementContainedinRoles(info.getRoles(), allowed.roles())){
@ -66,5 +62,5 @@ public class AuthorizationInvocationHandler<T, I extends T> implements Invocatio
}
} else log.debug("is allowedFor not present in "+method.getName());
}
*/
}

View File

@ -3,7 +3,7 @@ package org.gcube.common.authorization.library;
import java.util.concurrent.Callable;
import org.gcube.common.authorization.library.provider.AuthorizationProvider;
import org.gcube.common.authorization.library.provider.UserInfo;
import org.gcube.common.authorization.library.provider.ClientInfo;
import org.gcube.common.scope.api.ScopeProvider;
@ -18,7 +18,7 @@ public class AuthorizedTasks {
final String callScope = ScopeProvider.instance.get();
final UserInfo userCall = AuthorizationProvider.instance.get();
final ClientInfo userCall = AuthorizationProvider.instance.get();
return new Callable<V>() {
@Override
@ -49,7 +49,7 @@ public class AuthorizedTasks {
final String callScope = ScopeProvider.instance.get();
final UserInfo userCall = AuthorizationProvider.instance.get();
final ClientInfo userCall = AuthorizationProvider.instance.get();
return new Runnable() {
@Override

View File

@ -1,86 +0,0 @@
package org.gcube.common.authorization.library;
import java.util.Calendar;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class BannedService {
private String serviceClass;
private String serviceName;
private Calendar banTime;
protected BannedService() {
super();
}
public BannedService(String serviceClass, String serviceName, Calendar banTime) {
super();
this.serviceClass = serviceClass;
this.serviceName = serviceName;
this.banTime = banTime;
}
public BannedService(String serviceClass, String serviceName) {
super();
this.serviceClass = serviceClass;
this.serviceName = serviceName;
this.banTime = Calendar.getInstance();
}
public String getServiceClass() {
return serviceClass;
}
public String getServiceName() {
return serviceName;
}
public Calendar getCreationTime() {
return banTime;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((serviceClass == null) ? 0 : serviceClass.hashCode());
result = prime * result
+ ((serviceName == null) ? 0 : serviceName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BannedService other = (BannedService) obj;
if (serviceClass == null) {
if (other.serviceClass != null)
return false;
} else if (!serviceClass.equals(other.serviceClass))
return false;
if (serviceName == null) {
if (other.serviceName != null)
return false;
} else if (!serviceName.equals(other.serviceName))
return false;
return true;
}
@Override
public String toString() {
return "BannedService [serviceClass=" + serviceClass + ", serviceName="
+ serviceName + ", banTime=" + banTime.getTimeInMillis() + "]";
}
}

View File

@ -1,30 +0,0 @@
package org.gcube.common.authorization.library;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class BannedServices {
private List<BannedService> services;
protected BannedServices(){}
public BannedServices(List<BannedService> services) {
super();
this.services = services;
}
public List<BannedService> get() {
return services;
}
}

View File

@ -0,0 +1,32 @@
package org.gcube.common.authorization.library;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class CalledService {
private String serviceClass;
private String serviceName;
protected CalledService() {
super();
}
public CalledService(String serviceClass, String serviceName) {
super();
this.serviceClass = serviceClass;
this.serviceName = serviceName;
}
public String getServiceClass() {
return serviceClass;
}
public String getServiceName() {
return serviceName;
}
}

View File

@ -0,0 +1,77 @@
package org.gcube.common.authorization.library.policies;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class EnvironmentPolicy extends Policy{
private String environment;
private ServiceAccess service;
protected EnvironmentPolicy() {}
public EnvironmentPolicy(String environment, ServiceAccess service) {
super();
this.environment = environment;
this.service = service;
}
@Override
public PolicyType getPolicyType() {
return PolicyType.ENVIRONMENT;
}
@Override
public String getPolicyAsString() {
return service.getAsString();
}
public String getEnvironment() {
return environment;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((environment == null) ? 0 : environment.hashCode());
result = prime * result + ((service == null) ? 0 : service.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EnvironmentPolicy other = (EnvironmentPolicy) obj;
if (environment == null) {
if (other.environment != null)
return false;
} else if (!environment.equals(other.environment))
return false;
if (service == null) {
if (other.service != null)
return false;
} else if (!service.equals(other.service))
return false;
return true;
}
@Override
public String toString() {
return "EnvironmentPolicy [environment=" + environment + ", service="
+ service + "]";
}
}

View File

@ -0,0 +1,17 @@
package org.gcube.common.authorization.library.policies;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Policy {
protected Policy() {}
public abstract PolicyType getPolicyType();
public abstract String getPolicyAsString();
}

View File

@ -0,0 +1,8 @@
package org.gcube.common.authorization.library.policies;
public enum PolicyType {
ENVIRONMENT,
SERVICE,
USER
}

View File

@ -0,0 +1,113 @@
package org.gcube.common.authorization.library.policies;
import java.util.HashMap;
import java.util.Map.Entry;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ServiceAccess {
private String serviceClass;
private String name ;
private String serviceId;
private HashMap<String, String> serviceSpecificPolices;
public ServiceAccess() {}
public ServiceAccess(String name, String serviceClass) {
super();
this.name = name;
this.serviceClass = serviceClass;
}
public ServiceAccess(String name, String serviceClass, String serviceId) {
this(name, serviceClass);
this.serviceId = serviceId;
}
public ServiceAccess(String name, String serviceClass, String serviceId,
HashMap<String, String> serviceSpecificPolices) {
this(name, serviceClass, serviceId);
this.serviceSpecificPolices = serviceSpecificPolices;
}
public String getAsString(){
if (serviceClass == null)
return "*";
StringBuilder toReturn = new StringBuilder(serviceClass);
if (name == null)
return toReturn.append(":").append("*").toString();
toReturn.append(":").append(name);
if (serviceId==null && (serviceSpecificPolices==null || serviceSpecificPolices.size()==0))
return toReturn.append(":").append("*").toString();
if (serviceId!=null)
toReturn.append(":").append(serviceId);
if (serviceSpecificPolices!=null && serviceSpecificPolices.size()!=0){
toReturn.append("{");
for (Entry<String , String> entry: serviceSpecificPolices.entrySet())
toReturn.append(entry.getKey()).append(":").append(entry.getValue());
toReturn.append("}");
}
return toReturn.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result
+ ((serviceClass == null) ? 0 : serviceClass.hashCode());
result = prime * result
+ ((serviceId == null) ? 0 : serviceId.hashCode());
result = prime
* result
+ ((serviceSpecificPolices == null) ? 0
: serviceSpecificPolices.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ServiceAccess other = (ServiceAccess) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (serviceClass == null) {
if (other.serviceClass != null)
return false;
} else if (!serviceClass.equals(other.serviceClass))
return false;
if (serviceId == null) {
if (other.serviceId != null)
return false;
} else if (!serviceId.equals(other.serviceId))
return false;
if (serviceSpecificPolices == null) {
if (other.serviceSpecificPolices != null)
return false;
} else if (!serviceSpecificPolices.equals(other.serviceSpecificPolices))
return false;
return true;
}
@Override
public String toString() {
return "ServiceAccess ["+getAsString()+"]";
}
}

View File

@ -0,0 +1,93 @@
package org.gcube.common.authorization.library.policies;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ServicePolicy extends Policy{
private String clientID;
private String environment;
private ServiceAccess serviceAccess;
protected ServicePolicy(){}
public ServicePolicy(String environment, ServiceAccess serviceAccess,
String clientID) {
this.environment = environment;
this.serviceAccess = serviceAccess;
this.clientID = clientID;
}
@Override
public PolicyType getPolicyType() {
return PolicyType.SERVICE;
}
@Override
public String getPolicyAsString() {
return serviceAccess.getAsString();
}
public String getClientID() {
return clientID;
}
public String getEnvironment() {
return environment;
}
public ServiceAccess getServiceAccess() {
return serviceAccess;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((clientID == null) ? 0 : clientID.hashCode());
result = prime * result
+ ((environment == null) ? 0 : environment.hashCode());
result = prime * result
+ ((serviceAccess == null) ? 0 : serviceAccess.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ServicePolicy other = (ServicePolicy) obj;
if (clientID == null) {
if (other.clientID != null)
return false;
} else if (!clientID.equals(other.clientID))
return false;
if (environment == null) {
if (other.environment != null)
return false;
} else if (!environment.equals(other.environment))
return false;
if (serviceAccess == null) {
if (other.serviceAccess != null)
return false;
} else if (!serviceAccess.equals(other.serviceAccess))
return false;
return true;
}
@Override
public String toString() {
return "ServicePolicy [clientID=" + clientID + ", environment="
+ environment + ", serviceAccess=" + serviceAccess + "]";
}
}

View File

@ -0,0 +1,87 @@
package org.gcube.common.authorization.library.policies;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class UserPolicy extends Policy {
private String clientID;
private String environment;
private ServiceAccess serviceAccess;
protected UserPolicy(){}
public UserPolicy(String environment, ServiceAccess serviceAccess, String clientID) {
this.environment = environment;
this.serviceAccess = serviceAccess;
this.clientID = clientID;
}
public String getClientID() {
return clientID;
}
@Override
public PolicyType getPolicyType() {
return PolicyType.USER;
}
@Override
public String getPolicyAsString() {
return serviceAccess.getAsString();
}
public String getEnvironment() {
return environment;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((clientID == null) ? 0 : clientID.hashCode());
result = prime * result
+ ((environment == null) ? 0 : environment.hashCode());
result = prime * result
+ ((serviceAccess == null) ? 0 : serviceAccess.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UserPolicy other = (UserPolicy) obj;
if (clientID == null) {
if (other.clientID != null)
return false;
} else if (!clientID.equals(other.clientID))
return false;
if (environment == null) {
if (other.environment != null)
return false;
} else if (!environment.equals(other.environment))
return false;
if (serviceAccess == null) {
if (other.serviceAccess != null)
return false;
} else if (!serviceAccess.equals(other.serviceAccess))
return false;
return true;
}
@Override
public String toString() {
return "UserPolicy [clientID=" + clientID + ", environment="
+ environment + ", serviceAccess=" + serviceAccess + "]";
}
}

View File

@ -11,10 +11,10 @@ public class AuthorizationProvider {
private static Logger logger = LoggerFactory.getLogger(AuthorizationProvider.class);
// Thread local variable containing each thread's ID
private static final InheritableThreadLocal<UserInfo> threadAuth =
new InheritableThreadLocal<UserInfo>() {
private static final InheritableThreadLocal<ClientInfo> threadAuth =
new InheritableThreadLocal<ClientInfo>() {
@Override protected UserInfo initialValue() {
@Override protected ClientInfo initialValue() {
return null;
}
@ -22,13 +22,13 @@ public class AuthorizationProvider {
private AuthorizationProvider(){}
public UserInfo get(){
UserInfo info = threadAuth.get();
public ClientInfo get(){
ClientInfo info = threadAuth.get();
logger.trace("getting "+info+" in thread "+Thread.currentThread().getId() );
return info;
}
public void set(UserInfo authorizationToken){
public void set(ClientInfo authorizationToken){
threadAuth.set(authorizationToken);
logger.trace("setting "+authorizationToken+" in thread "+Thread.currentThread().getId() );
}

View File

@ -1,56 +1,55 @@
package org.gcube.common.authorization.library.provider;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.gcube.common.authorization.library.BannedService;
import org.gcube.common.authorization.library.policies.Policy;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class UserInfo {
public class ClientInfo {
private String userName;
private List<String> roles;
private List<BannedService> bannedServices;
private String clientId;
private List<String> roles = Collections.emptyList();
private List<Policy> policies;
protected UserInfo(){}
protected ClientInfo(){}
public UserInfo(String userName, List<String> roles, List<BannedService> bannedServices) {
public ClientInfo(String clientId, List<String> roles, List<Policy> policies) {
super();
this.userName = userName;
this.clientId = clientId;
this.roles = roles;
this.bannedServices = bannedServices;
this.policies = policies;
}
public String getUserName() {
return userName;
public String getClientId() {
return clientId;
}
public List<String> getRoles() {
return roles;
public List<Policy> getPolicies() {
return policies;
}
public List<BannedService> getBannedServices() {
return bannedServices;
}
/*
public boolean isTokenBannedForService(BannedService service){
return (bannedServices.contains(service));
}
}*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((roles == null) ? 0 : roles.hashCode());
result = prime * result
+ ((userName == null) ? 0 : userName.hashCode());
+ ((clientId == null) ? 0 : clientId.hashCode());
result = prime * result
+ ((policies == null) ? 0 : policies.hashCode());
result = prime * result + ((roles == null) ? 0 : roles.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
@ -59,25 +58,32 @@ public class UserInfo {
return false;
if (getClass() != obj.getClass())
return false;
UserInfo other = (UserInfo) obj;
ClientInfo other = (ClientInfo) obj;
if (clientId == null) {
if (other.clientId != null)
return false;
} else if (!clientId.equals(other.clientId))
return false;
if (policies == null) {
if (other.policies != null)
return false;
} else if (!policies.equals(other.policies))
return false;
if (roles == null) {
if (other.roles != null)
return false;
} else if (!roles.equals(other.roles))
return false;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
return true;
}
@Override
public String toString() {
return "UserInfo [userName=" + userName + ", roles=" + roles + "]";
return "ClientInfo [clientId=" + clientId + ", roles=" + roles
+ ", policies=" + policies + "]";
}
}

View File

@ -0,0 +1,47 @@
package org.gcube.common.authorization.library.policies;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class SerializationTest {
static JAXBContext context;
@BeforeClass
public static void before() throws Exception{
context = JAXBContext.newInstance(EnvironmentPolicy.class, UserPolicy.class, ServicePolicy.class);
}
@Test
public void serializeEnvironmentPolicy() throws Exception{
EnvironmentPolicy ep = new EnvironmentPolicy("/gcube", new ServiceAccess());
StringWriter sw = new StringWriter();
context.createMarshaller().marshal(ep, sw);
EnvironmentPolicy epCopy = (EnvironmentPolicy)context.createUnmarshaller().unmarshal(new StringReader(sw.toString()));
Assert.assertEquals(ep, epCopy);
}
@Test
public void serializeUserPolicy() throws Exception{
UserPolicy up = new UserPolicy("/gcube", new ServiceAccess("ServiceName", "ServiceClass","serviceID"), "userID");
StringWriter sw = new StringWriter();
context.createMarshaller().marshal(up, sw);
UserPolicy upCopy = (UserPolicy)context.createUnmarshaller().unmarshal(new StringReader(sw.toString()));
Assert.assertEquals(up, upCopy);
}
@Test
public void serializeServicePolicy() throws Exception{
ServicePolicy sp = new ServicePolicy("/gcube", new ServiceAccess("ServiceName","ServiceClass"),"ServiceName:ServiceClass");
StringWriter sw = new StringWriter();
context.createMarshaller().marshal(sp, sw);
ServicePolicy spCopy = (ServicePolicy)context.createUnmarshaller().unmarshal(new StringReader(sw.toString()));
Assert.assertEquals(sp, spCopy);
}
}