NIHVIVO-1232 Pull the code that covers restricted Namespaces and URIs into its own class.

This commit is contained in:
jeb228 2010-12-02 19:11:13 +00:00
parent 5cc801ee2c
commit d27c258ad5
4 changed files with 360 additions and 266 deletions

View file

@ -0,0 +1,152 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.auth.policy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.rdf.model.impl.Util;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
/**
* Used by several policies to disallow the modification of Vitro-reserved
* resources and/or properties.
*/
public class AdministrativeUriRestrictor {
private static final Log log = LogFactory
.getLog(AdministrativeUriRestrictor.class);
private static final String[] DEFAULT_PROHIBITED_PROPERTIES = {};
private static final String[] DEFAULT_PROHIBITED_RESOURCES = {};
private static final String[] DEFAULT_PROHIBITED_NAMESPACES = {
VitroVocabulary.vitroURI,
VitroVocabulary.OWL,
"" };
private static final String[] DEFAULT_EDITABLE_VITRO_URIS = {
VitroVocabulary.MONIKER,
VitroVocabulary.BLURB,
VitroVocabulary.DESCRIPTION,
VitroVocabulary.MODTIME,
VitroVocabulary.TIMEKEY,
VitroVocabulary.CITATION,
VitroVocabulary.IND_MAIN_IMAGE,
VitroVocabulary.LINK,
VitroVocabulary.PRIMARY_LINK,
VitroVocabulary.ADDITIONAL_LINK,
VitroVocabulary.LINK_ANCHOR,
VitroVocabulary.LINK_URL,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION_INVOLVESKEYWORD,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION_INVOLVESINDIVIDUAL,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION_MODE };
/**
* Namespaces from which Self Editors should not be able to use resources.
*/
private final Set<String> prohibitedNamespaces;
/**
* URIs of properties that SelfEditors should not be able to use in
* statements
*/
protected final Set<String> prohibitedProperties;
/**
* URIs of resources that SelfEditors should not be able to use in
* statements
*/
protected final Set<String> prohibitedResources;
/**
* URIs of properties from prohibited namespaces that Self Editors need to
* be able to edit
*/
protected final Set<String> editableVitroUris;
public AdministrativeUriRestrictor(Set<String> prohibitedProperties,
Set<String> prohibitedResources, Set<String> prohibitedNamespaces,
Set<String> editableVitroUris) {
this.prohibitedProperties = useDefaultIfNull(prohibitedProperties,
DEFAULT_PROHIBITED_PROPERTIES);
this.prohibitedResources = useDefaultIfNull(prohibitedResources,
DEFAULT_PROHIBITED_RESOURCES);
this.prohibitedNamespaces = useDefaultIfNull(prohibitedNamespaces,
DEFAULT_PROHIBITED_NAMESPACES);
this.editableVitroUris = useDefaultIfNull(editableVitroUris,
DEFAULT_EDITABLE_VITRO_URIS);
}
private Set<String> useDefaultIfNull(Set<String> valueSet,
String[] defaultArray) {
Collection<String> strings = (valueSet == null) ? Arrays
.asList(defaultArray) : valueSet;
return Collections.unmodifiableSet(new HashSet<String>(strings));
}
public boolean canModifyResource(String uri) {
if (uri == null || uri.length() == 0) {
log.debug("Resource URI is empty: " + uri);
return false;
}
if (editableVitroUris.contains(uri)) {
log.debug("Resource matches an editable URI: " + uri);
return true;
}
String namespace = uri.substring(0, Util.splitNamespace(uri));
if (prohibitedNamespaces.contains(namespace)) {
log.debug("Resource matches a prohibited namespace: " + uri);
return false;
}
log.debug("Resource is not prohibited: " + uri);
return true;
}
public boolean canModifyPredicate(String uri) {
if (uri == null || uri.length() == 0) {
log.debug("Predicate URI is empty: " + uri);
return false;
}
if (prohibitedProperties.contains(uri)) {
log.debug("Predicate matches a prohibited predicate: " + uri);
return false;
}
if (editableVitroUris.contains(uri)) {
return true;
}
String namespace = uri.substring(0, Util.splitNamespace(uri));
if (prohibitedNamespaces.contains(namespace)) {
log.debug("Predicate matches a prohibited namespace: " + uri);
return false;
}
return true;
}
@Override
public String toString() {
return "AdministrativeUriRestrictor[prohibitedNamespaces="
+ prohibitedNamespaces + ", prohibitedProperties="
+ prohibitedProperties + ", prohibitedResources="
+ prohibitedResources + ", editableVitroUris="
+ editableVitroUris + "]";
}
}

View file

@ -3,10 +3,6 @@
package edu.cornell.mannlib.vitro.webapp.auth.policy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -14,7 +10,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.model.impl.Util;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.Identifier;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
@ -44,92 +39,29 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.EditDataPr
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.EditObjPropStmt;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.resource.AddResource;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.resource.DropResource;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
/**
* Policy to use for Vivo Self-Editing based on NetId for use at Cornell.
* All methods in this class should be thread safe
* and side effect free.
* Policy to use for Vivo Self-Editing based on NetId for use at Cornell. All
* methods in this class should be thread safe and side effect free.
*/
public class SelfEditingPolicy implements VisitingPolicyIface {
protected static Log log = LogFactory.getLog(SelfEditingPolicy.class);
private static final String[] DEFAULT_PROHIBITED_PROPERTIES = {};
protected final OntModel model;
private final AdministrativeUriRestrictor restrictor;
private static final String[] DEFAULT_PROHIBITED_RESOURCES = {};
private static final String[] DEFAULT_PROHIBITED_NAMESPACES = {
VitroVocabulary.vitroURI,
VitroVocabulary.OWL,
""
};
private static final String[] DEFAULT_EDITABLE_VITRO_URIS = {
VitroVocabulary.MONIKER,
VitroVocabulary.BLURB,
VitroVocabulary.DESCRIPTION,
VitroVocabulary.MODTIME,
VitroVocabulary.TIMEKEY,
VitroVocabulary.CITATION,
VitroVocabulary.IND_MAIN_IMAGE,
VitroVocabulary.LINK,
VitroVocabulary.PRIMARY_LINK,
VitroVocabulary.ADDITIONAL_LINK,
VitroVocabulary.LINK_ANCHOR,
VitroVocabulary.LINK_URL,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION_INVOLVESKEYWORD,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION_INVOLVESINDIVIDUAL,
VitroVocabulary.KEYWORD_INDIVIDUALRELATION_MODE
};
/**
* Namespaces from which Self Editors should not be able to use resources.
*/
private Set<String> prohibitedNs;
/** URIs of properties that SelfEditors should not be able to use in statements*/
protected Set<String>prohibitedProperties;
/** URIs of resources that SelfEditors should not be able to use in statements*/
protected Set<String>prohibitedResources;
/** URIs of properties from prohibited namespaces that Self Editors need to be
* able to edit */
protected Set<String> editableVitroUris;
protected OntModel model;
public SelfEditingPolicy(
Set<String>prohibitedProperties,
Set<String>prohibitedResources,
Set<String>prohibitedNamespaces,
Set<String> editableVitroUris ,
OntModel model){
public SelfEditingPolicy(Set<String> prohibitedProperties,
Set<String> prohibitedResources, Set<String> prohibitedNamespaces,
Set<String> editableVitroUris, OntModel model) {
this.model = model;
this.prohibitedProperties = useDefaultIfNull(prohibitedProperties,
DEFAULT_PROHIBITED_PROPERTIES);
this.prohibitedResources = useDefaultIfNull(prohibitedResources,
DEFAULT_PROHIBITED_RESOURCES);
this.prohibitedNs = useDefaultIfNull(prohibitedNamespaces,
DEFAULT_PROHIBITED_NAMESPACES);
this.editableVitroUris = useDefaultIfNull(editableVitroUris,
DEFAULT_EDITABLE_VITRO_URIS);
}
private Set<String> useDefaultIfNull(Set<String> valueSet, String[] defaultArray) {
Collection<String> strings = (valueSet == null) ? Arrays
.asList(defaultArray) : valueSet;
return Collections.unmodifiableSet(new HashSet<String>(strings));
this.restrictor = new AdministrativeUriRestrictor(prohibitedProperties,
prohibitedResources, prohibitedNamespaces, editableVitroUris);
}
private static final Authorization DEFAULT_AUTHORIZATION = Authorization.INCONCLUSIVE;
public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, RequestedAction whatToAuth) {
public PolicyDecision isAuthorized(IdentifierBundle whoToAuth,
RequestedAction whatToAuth) {
if (whoToAuth == null) {
return defaultDecision("whoToAuth was null");
}
@ -143,8 +75,8 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
return defaultDecision("Won't authorize AdminRequestedActions");
}
if (getUrisOfSelfEditor(whoToAuth).isEmpty()) {
return defaultDecision("no non-blacklisted SelfEditing Identifier " +
"found in IdentifierBundle");
return defaultDecision("no non-blacklisted SelfEditing Identifier "
+ "found in IdentifierBundle");
}
// kick off the visitor pattern
@ -157,72 +89,106 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
public PolicyDecision visit(IdentifierBundle ids, AddResource action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null) pd = authorizedDecision("May add resource.");
if (pd == null)
pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null)
pd = authorizedDecision("May add resource.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, DropResource action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null) pd = authorizedDecision("May remove resource.");
if (pd == null)
pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null)
pd = authorizedDecision("May remove resource.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, AddObjectPropStmt action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.uriOfSubject);
if (pd == null) pd = checkRestrictedResource(action.uriOfObject);
if (pd == null) pd = checkRestrictedPredicate(action.uriOfPredicate);
if (pd == null) pd = checkUserEditsAsSubjectOrObjectOfStmt(ids, action.uriOfSubject, action.uriOfObject);
if (pd == null) pd = defaultDecision("No basis for decision.");
if (pd == null)
pd = checkRestrictedResource(action.uriOfSubject);
if (pd == null)
pd = checkRestrictedResource(action.uriOfObject);
if (pd == null)
pd = checkRestrictedPredicate(action.uriOfPredicate);
if (pd == null)
pd = checkUserEditsAsSubjectOrObjectOfStmt(ids,
action.uriOfSubject, action.uriOfObject);
if (pd == null)
pd = defaultDecision("No basis for decision.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, EditObjPropStmt action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.uriOfSubject);
if (pd == null) pd = checkRestrictedResource(action.uriOfObject);
if (pd == null) pd = checkRestrictedPredicate(action.uriOfPredicate);
if (pd == null) pd = checkUserEditsAsSubjectOrObjectOfStmt(ids, action.uriOfSubject, action.uriOfObject);
if (pd == null) pd = defaultDecision("No basis for decision.");
if (pd == null)
pd = checkRestrictedResource(action.uriOfSubject);
if (pd == null)
pd = checkRestrictedResource(action.uriOfObject);
if (pd == null)
pd = checkRestrictedPredicate(action.uriOfPredicate);
if (pd == null)
pd = checkUserEditsAsSubjectOrObjectOfStmt(ids,
action.uriOfSubject, action.uriOfObject);
if (pd == null)
pd = defaultDecision("No basis for decision.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, DropObjectPropStmt action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.uriOfSubject);
if (pd == null) pd = checkRestrictedResource(action.uriOfObject);
if (pd == null) pd = checkRestrictedPredicate(action.uriOfPredicate);
if (pd == null) pd = checkUserEditsAsSubjectOrObjectOfStmt(ids, action.uriOfSubject, action.uriOfObject);
if (pd == null) pd = defaultDecision("No basis for decision.");
if (pd == null)
pd = checkRestrictedResource(action.uriOfSubject);
if (pd == null)
pd = checkRestrictedResource(action.uriOfObject);
if (pd == null)
pd = checkRestrictedPredicate(action.uriOfPredicate);
if (pd == null)
pd = checkUserEditsAsSubjectOrObjectOfStmt(ids,
action.uriOfSubject, action.uriOfObject);
if (pd == null)
pd = defaultDecision("No basis for decision.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, AddDataPropStmt action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null) pd = checkRestrictedPredicate(action.getPredicateUri());
if (pd == null) pd = checkUserEditsAsSubjectOfStmt(ids, action.getSubjectUri());
if (pd == null) pd = defaultDecision("No basis for decision.");
if (pd == null)
pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null)
pd = checkRestrictedPredicate(action.getPredicateUri());
if (pd == null)
pd = checkUserEditsAsSubjectOfStmt(ids, action.getSubjectUri());
if (pd == null)
pd = defaultDecision("No basis for decision.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, EditDataPropStmt action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null) pd = checkRestrictedPredicate(action.getPredicateUri());
if (pd == null) pd = checkUserEditsAsSubjectOfStmt(ids, action.getSubjectUri());
if (pd == null) pd = defaultDecision("No basis for decision.");
if (pd == null)
pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null)
pd = checkRestrictedPredicate(action.getPredicateUri());
if (pd == null)
pd = checkUserEditsAsSubjectOfStmt(ids, action.getSubjectUri());
if (pd == null)
pd = defaultDecision("No basis for decision.");
return pd;
}
public PolicyDecision visit(IdentifierBundle ids, DropDataPropStmt action) {
PolicyDecision pd = checkNullArguments(ids, action);
if (pd == null) pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null) pd = checkRestrictedPredicate(action.getPredicateUri());
if (pd == null) pd = checkUserEditsAsSubjectOfStmt(ids, action.getSubjectUri());
if (pd == null) pd = defaultDecision("No basis for decision.");
if (pd == null)
pd = checkRestrictedResource(action.getSubjectUri());
if (pd == null)
pd = checkRestrictedPredicate(action.getPredicateUri());
if (pd == null)
pd = checkUserEditsAsSubjectOfStmt(ids, action.getSubjectUri());
if (pd == null)
pd = defaultDecision("No basis for decision.");
return pd;
}
@ -262,7 +228,8 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
return defaultDecision("does not authorize administrative modifications");
}
public PolicyDecision visit(IdentifierBundle ids, DefineObjectProperty action) {
public PolicyDecision visit(IdentifierBundle ids,
DefineObjectProperty action) {
return defaultDecision("does not authorize administrative modifications");
}
@ -283,7 +250,7 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
}
private PolicyDecision checkRestrictedResource(String uri) {
if (!canModifyResource(uri)) {
if (!restrictor.canModifyResource(uri)) {
return defaultDecision("No access to admin resources; "
+ "cannot modify " + uri);
}
@ -291,7 +258,7 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
}
private PolicyDecision checkRestrictedPredicate(String uri) {
if (!canModifyPredicate(uri)) {
if (!restrictor.canModifyPredicate(uri)) {
return defaultDecision("No access to admin predicates; "
+ "cannot modify " + uri);
}
@ -309,8 +276,8 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
return null;
}
private PolicyDecision checkUserEditsAsSubjectOrObjectOfStmt(IdentifierBundle ids,
String uriOfSubject, String uriOfObject) {
private PolicyDecision checkUserEditsAsSubjectOrObjectOfStmt(
IdentifierBundle ids, String uriOfSubject, String uriOfObject) {
List<String> userUris = getUrisOfSelfEditor(ids);
for (String userUri : userUris) {
if (userUri.equals(uriOfSubject)) {
@ -338,52 +305,6 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
return uris;
}
/** Package-level access to allow for unit tests. */
boolean canModifyResource(String uri) {
if (uri == null || uri.length() == 0) {
log.debug("Resource URI is empty: " + uri);
return false;
}
if (editableVitroUris.contains(uri)) {
log.debug("Resource matches an editable URI: " + uri);
return true;
}
String namespace = uri.substring(0, Util.splitNamespace(uri));
if (prohibitedNs.contains(namespace)) {
log.debug("Resource matches a prohibited namespace: " + uri);
return false;
}
log.debug("Resource is not prohibited: " + uri);
return true;
}
/** Package-level access to allow for unit tests. */
boolean canModifyPredicate(String uri) {
if (uri == null || uri.length() == 0) {
log.debug("Predicate URI is empty: " + uri);
return false;
}
if (prohibitedProperties.contains(uri)) {
log.debug("Predicate matches a prohibited predicate: " + uri);
return false;
}
if (editableVitroUris.contains(uri)) {
return true;
}
String namespace = uri.substring(0, Util.splitNamespace(uri));
if (prohibitedNs.contains(namespace)) {
log.debug("Predicate matches a prohibited namespace: " + uri);
return false;
}
return true;
}
private PolicyDecision defaultDecision(String message) {
return new BasicPolicyDecision(DEFAULT_AUTHORIZATION,
"SelfEditingPolicy: " + message);
@ -396,11 +317,7 @@ public class SelfEditingPolicy implements VisitingPolicyIface {
@Override
public String toString() {
return "SelfEditingPolicy " + hashCode() + "[prohibitedNs="
+ prohibitedNs + ", prohibitedProperties="
+ prohibitedProperties + ", prohibitedResources="
+ prohibitedResources + ", editableVitroUris="
+ editableVitroUris + "]";
return "SelfEditingPolicy " + hashCode() + "[" + restrictor + "]";
}
}

View file

@ -0,0 +1,45 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.auth.policy;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
/**
* TODO
*/
public class AdministrativeUriRestrictorTest extends AbstractTestClass {
private static final String SAFE_NS = "http://test.mannlib.cornell.edu/ns/01#";
private static final String UNSAFE_NS = VitroVocabulary.vitroURI;
private static final String SAFE_RESOURCE = SAFE_NS + "otherIndividual77777";
private static final String UNSAFE_RESOURCE = UNSAFE_NS + "otherIndividual99999";
private static final String SAFE_PREDICATE = SAFE_NS + "hasHairStyle";
private static final String UNSAFE_PREDICATE = UNSAFE_NS + "hasSuperPowers";
private AdministrativeUriRestrictor restrictor;
@Before
public void setup() {
restrictor = new AdministrativeUriRestrictor(null, null, null, null);
}
@Test
public void testCanModifiyNs(){
Assert.assertTrue( restrictor.canModifyResource("http://bobs.com#hats") );
Assert.assertTrue( restrictor.canModifyResource("ftp://bobs.com#hats"));
Assert.assertTrue( restrictor.canModifyResource( SAFE_RESOURCE ));
Assert.assertTrue( restrictor.canModifyPredicate( SAFE_PREDICATE ));
Assert.assertTrue( restrictor.canModifyResource("http://bobs.com/hats"));
Assert.assertTrue( ! restrictor.canModifyResource(""));
Assert.assertTrue( ! restrictor.canModifyResource(VitroVocabulary.vitroURI + "something"));
Assert.assertTrue( ! restrictor.canModifyResource(VitroVocabulary.OWL + "Ontology"));
Assert.assertTrue( ! restrictor.canModifyPredicate( UNSAFE_PREDICATE ));
Assert.assertTrue( ! restrictor.canModifyResource( UNSAFE_RESOURCE ));
Assert.assertTrue( ! restrictor.canModifyResource( UNSAFE_NS ));
}
}

View file

@ -7,12 +7,10 @@ import static edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization.
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.*;
import java.util.HashSet;
import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -70,24 +68,6 @@ public class SelfEditingPolicyTest extends AbstractTestClass {
ind.setURI( SELFEDITOR_URI );
ids.add( new SelfEditingIdentifierFactory.SelfEditing( ind, SelfEditingIdentifierFactory.NOT_BLACKLISTED ) );
}
@Test
public void testCanModifiyNs(){
Assert.assertTrue( policy.canModifyResource("http://bobs.com#hats") );
Assert.assertTrue( policy.canModifyResource("ftp://bobs.com#hats"));
Assert.assertTrue( policy.canModifyResource( SAFE_RESOURCE ));
Assert.assertTrue( policy.canModifyPredicate( SAFE_PREDICATE ));
Assert.assertTrue( policy.canModifyResource("http://bobs.com/hats"));
Assert.assertTrue( ! policy.canModifyResource(""));
Assert.assertTrue( ! policy.canModifyResource(VitroVocabulary.vitroURI + "something"));
Assert.assertTrue( ! policy.canModifyResource(VitroVocabulary.OWL + "Ontology"));
Assert.assertTrue( ! policy.canModifyPredicate( UNSAFE_PREDICATE ));
Assert.assertTrue( ! policy.canModifyResource( UNSAFE_RESOURCE ));
Assert.assertTrue( ! policy.canModifyResource( UNSAFE_NS ));
}
@Test