Skip to content

Commit

Permalink
removed database thread local from hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
tglman committed Feb 16, 2015
1 parent d3f9d0e commit fb74038
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 89 deletions.
Expand Up @@ -389,7 +389,7 @@ public OSBTreeCollectionManager call() throws Exception {
metadata = new OMetadataDefault(); metadata = new OMetadataDefault();
metadata.create(); metadata.create();


registerHook(new OSecurityTrackerHook(metadata.getSecurity()), ORecordHook.HOOK_POSITION.LAST); registerHook(new OSecurityTrackerHook(metadata.getSecurity(), this), ORecordHook.HOOK_POSITION.LAST);


final OUser usr = getMetadata().getSecurity().getUser(OUser.ADMIN); final OUser usr = getMetadata().getSecurity().getUser(OUser.ADMIN);


Expand Down Expand Up @@ -2571,10 +2571,6 @@ protected void checkOpeness() {
throw new ODatabaseException("Database '" + getURL() + "' is closed"); throw new ODatabaseException("Database '" + getURL() + "' is closed");
} }


private void setCurrentDatabaseinThreadLocal() {
ODatabaseRecordThreadLocal.INSTANCE.set(this);
}

private void initAtFirstOpen(String iUserName, String iUserPassword) { private void initAtFirstOpen(String iUserName, String iUserPassword) {
if (initialized) if (initialized)
return; return;
Expand Down Expand Up @@ -2624,7 +2620,7 @@ public OSBTreeCollectionManager call() throws Exception {
} }


installHooks(); installHooks();
registerHook(new OSecurityTrackerHook(metadata.getSecurity()), ORecordHook.HOOK_POSITION.LAST); registerHook(new OSecurityTrackerHook(metadata.getSecurity(), this), ORecordHook.HOOK_POSITION.LAST);


user = null; user = null;
} else if (iUserName != null && iUserPassword != null) } else if (iUserName != null && iUserPassword != null)
Expand All @@ -2646,13 +2642,13 @@ public OSBTreeCollectionManager call() throws Exception {
} }


private void installHooks() { private void installHooks() {
registerHook(new OClassTrigger(), ORecordHook.HOOK_POSITION.FIRST); registerHook(new OClassTrigger(this), ORecordHook.HOOK_POSITION.FIRST);
registerHook(new ORestrictedAccessHook(), ORecordHook.HOOK_POSITION.FIRST); registerHook(new ORestrictedAccessHook(this), ORecordHook.HOOK_POSITION.FIRST);
registerHook(new OUserTrigger(), ORecordHook.HOOK_POSITION.EARLY); registerHook(new OUserTrigger(this), ORecordHook.HOOK_POSITION.EARLY);
registerHook(new OFunctionTrigger(), ORecordHook.HOOK_POSITION.REGULAR); registerHook(new OFunctionTrigger(this), ORecordHook.HOOK_POSITION.REGULAR);
registerHook(new OClassIndexManager(), ORecordHook.HOOK_POSITION.LAST); registerHook(new OClassIndexManager(this), ORecordHook.HOOK_POSITION.LAST);
registerHook(new OSchedulerTrigger(), ORecordHook.HOOK_POSITION.LAST); registerHook(new OSchedulerTrigger(this), ORecordHook.HOOK_POSITION.LAST);
registerHook(new ORidBagDeleteHook(), ORecordHook.HOOK_POSITION.LAST); registerHook(new ORidBagDeleteHook(this), ORecordHook.HOOK_POSITION.LAST);
} }


private void closeOnDelete() { private void closeOnDelete() {
Expand Down
Expand Up @@ -31,16 +31,14 @@
import com.orientechnologies.orient.core.command.script.OCommandScriptException; import com.orientechnologies.orient.core.command.script.OCommandScriptException;
import com.orientechnologies.orient.core.command.script.OScriptManager; import com.orientechnologies.orient.core.command.script.OScriptManager;
import com.orientechnologies.orient.core.db.ODatabase.STATUS; import com.orientechnologies.orient.core.db.ODatabase.STATUS;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.exception.OConfigurationException; import com.orientechnologies.orient.core.exception.OConfigurationException;
import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; import com.orientechnologies.orient.core.hook.ODocumentHookAbstract;
import com.orientechnologies.orient.core.id.ORID; import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId; import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.function.OFunction; import com.orientechnologies.orient.core.metadata.function.OFunction;
import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OClassImpl;
import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal; import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
Expand Down Expand Up @@ -75,7 +73,8 @@ public class OClassTrigger extends ODocumentHookAbstract {
public static final String ONAFTER_DELETE = "onAfterDelete"; public static final String ONAFTER_DELETE = "onAfterDelete";
public static final String PROP_AFTER_DELETE = ONAFTER_DELETE; public static final String PROP_AFTER_DELETE = ONAFTER_DELETE;


public OClassTrigger() { public OClassTrigger(ODatabaseDocument database) {
super(database);
} }


public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
Expand Down Expand Up @@ -175,7 +174,7 @@ else if (func instanceof Object[])
} }


public RESULT onTrigger(final TYPE iType, final ORecord iRecord) { public RESULT onTrigger(final TYPE iType, final ORecord iRecord) {
if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && ODatabaseRecordThreadLocal.INSTANCE.get().getStatus() != STATUS.OPEN) if (database.getStatus() != STATUS.OPEN)
return RESULT.RECORD_NOT_CHANGED; return RESULT.RECORD_NOT_CHANGED;


if (!(iRecord instanceof ODocument)) if (!(iRecord instanceof ODocument))
Expand Down Expand Up @@ -206,14 +205,13 @@ private Object checkClzAttribute(final ODocument iDocument, String attr) {
final Object[] clzMethod = this.checkMethod(fieldName); final Object[] clzMethod = this.checkMethod(fieldName);
if (clzMethod != null) if (clzMethod != null)
return clzMethod; return clzMethod;
func = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getFunctionLibrary().getFunction(fieldName); func = database.getMetadata().getFunctionLibrary().getFunction(fieldName);
if (func == null) { // check if it is rid if (func == null) { // check if it is rid
if (OStringSerializerHelper.contains(fieldName, ORID.SEPARATOR)) { if (OStringSerializerHelper.contains(fieldName, ORID.SEPARATOR)) {
try { try {
ODocument funcDoc = ODatabaseRecordThreadLocal.INSTANCE.get().load(new ORecordId(fieldName)); ODocument funcDoc = database.load(new ORecordId(fieldName));
if (funcDoc != null) { if (funcDoc != null) {
func = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getFunctionLibrary() func = database.getMetadata().getFunctionLibrary().getFunction((String) funcDoc.field("name"));
.getFunction((String) funcDoc.field("name"));
} }
} catch (Exception ex) { } catch (Exception ex) {
OLogManager.instance().error(this, "illegal record id : ", ex.getMessage()); OLogManager.instance().error(this, "illegal record id : ", ex.getMessage());
Expand All @@ -225,7 +223,7 @@ private Object checkClzAttribute(final ODocument iDocument, String attr) {
if (funcProp != null) { if (funcProp != null) {
final String funcName = funcProp instanceof ODocument ? (String) ((ODocument) funcProp).field("name") : funcProp final String funcName = funcProp instanceof ODocument ? (String) ((ODocument) funcProp).field("name") : funcProp
.toString(); .toString();
func = ODatabaseRecordThreadLocal.INSTANCE.get().getMetadata().getFunctionLibrary().getFunction(funcName); func = database.getMetadata().getFunctionLibrary().getFunction(funcName);
} }
} }
return func; return func;
Expand Down Expand Up @@ -274,17 +272,15 @@ private RESULT executeFunction(final ODocument iDocument, final OFunction func)
if (func == null) if (func == null)
return RESULT.RECORD_NOT_CHANGED; return RESULT.RECORD_NOT_CHANGED;


ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.INSTANCE.get();

final OScriptManager scriptManager = Orient.instance().getScriptManager(); final OScriptManager scriptManager = Orient.instance().getScriptManager();


final OPartitionedObjectPool.PoolEntry<ScriptEngine> entry = scriptManager.acquireDatabaseEngine(db.getName(), final OPartitionedObjectPool.PoolEntry<ScriptEngine> entry = scriptManager.acquireDatabaseEngine(database.getName(),
func.getLanguage()); func.getLanguage());
final ScriptEngine scriptEngine = entry.object; final ScriptEngine scriptEngine = entry.object;
try { try {
final Bindings binding = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); final Bindings binding = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);


scriptManager.bind(binding, (ODatabaseDocumentTx) db, null, null); scriptManager.bind(binding, (ODatabaseDocumentTx) database, null, null);


String result = null; String result = null;
try { try {
Expand Down Expand Up @@ -320,7 +316,7 @@ private RESULT executeFunction(final ODocument iDocument, final OFunction func)
return RESULT.valueOf(result); return RESULT.valueOf(result);


} finally { } finally {
scriptManager.releaseDatabaseEngine(func.getLanguage(), db.getName(), entry); scriptManager.releaseDatabaseEngine(func.getLanguage(), database.getName(), entry);
} }
} }
} }
Expand Up @@ -19,6 +19,7 @@
*/ */
package com.orientechnologies.orient.core.db.record.ridbag.sbtree; package com.orientechnologies.orient.core.db.record.ridbag.sbtree;


import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.document.ODocumentFieldVisitor; import com.orientechnologies.orient.core.db.document.ODocumentFieldVisitor;
import com.orientechnologies.orient.core.db.document.ODocumentFieldWalker; import com.orientechnologies.orient.core.db.document.ODocumentFieldWalker;
import com.orientechnologies.orient.core.db.record.ORecordOperation; import com.orientechnologies.orient.core.db.record.ORecordOperation;
Expand All @@ -31,6 +32,11 @@
import com.orientechnologies.orient.core.version.ORecordVersion; import com.orientechnologies.orient.core.version.ORecordVersion;


public class ORidBagDeleteHook extends ODocumentHookAbstract { public class ORidBagDeleteHook extends ODocumentHookAbstract {

public ORidBagDeleteHook(ODatabaseDocument database) {
super(database);
}

@Override @Override
public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
return DISTRIBUTED_EXECUTION_MODE.TARGET_NODE; return DISTRIBUTED_EXECUTION_MODE.TARGET_NODE;
Expand Down
Expand Up @@ -20,6 +20,7 @@
package com.orientechnologies.orient.core.hook; package com.orientechnologies.orient.core.hook;


import com.orientechnologies.orient.core.db.ODatabase.STATUS; import com.orientechnologies.orient.core.db.ODatabase.STATUS;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.record.ORecord;
Expand All @@ -33,10 +34,17 @@
* @see ORecordHook * @see ORecordHook
*/ */
public abstract class ODocumentHookAbstract implements ORecordHook { public abstract class ODocumentHookAbstract implements ORecordHook {
private String[] includeClasses; private String[] includeClasses;
private String[] excludeClasses; private String[] excludeClasses;


protected ODocumentHookAbstract() { protected ODatabaseDocument database;

public ODocumentHookAbstract() {
this.database = ODatabaseRecordThreadLocal.INSTANCE.get();
}

public ODocumentHookAbstract(ODatabaseDocument database) {
this.database = database;
} }


@Override @Override
Expand Down Expand Up @@ -202,7 +210,7 @@ public void onRecordFinalizeCreation(final ODocument document) {
} }


public RESULT onTrigger(final TYPE iType, final ORecord iRecord) { public RESULT onTrigger(final TYPE iType, final ORecord iRecord) {
if (ODatabaseRecordThreadLocal.INSTANCE.isDefined() && ODatabaseRecordThreadLocal.INSTANCE.get().getStatus() != STATUS.OPEN) if (database.getStatus() != STATUS.OPEN)
return RESULT.RECORD_NOT_CHANGED; return RESULT.RECORD_NOT_CHANGED;


if (!(iRecord instanceof ODocument)) if (!(iRecord instanceof ODocument))
Expand Down
Expand Up @@ -22,6 +22,7 @@


import com.orientechnologies.common.log.OLogManager; import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.OHookReplacedRecordThreadLocal; import com.orientechnologies.orient.core.db.OHookReplacedRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable; import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent; import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeTimeLine; import com.orientechnologies.orient.core.db.record.OMultiValueChangeTimeLine;
Expand Down Expand Up @@ -51,7 +52,8 @@
public class OClassIndexManager extends ODocumentHookAbstract { public class OClassIndexManager extends ODocumentHookAbstract {
private final ThreadLocal<Deque<TreeMap<OIndex<?>, List<Object>>>> lockedKeys = new ThreadLocal<Deque<TreeMap<OIndex<?>, List<Object>>>>(); private final ThreadLocal<Deque<TreeMap<OIndex<?>, List<Object>>>> lockedKeys = new ThreadLocal<Deque<TreeMap<OIndex<?>, List<Object>>>>();


public OClassIndexManager() { public OClassIndexManager(ODatabaseDocument database) {
super(database);
} }


private static void processCompositeIndexUpdate(final OIndex<?> index, final Set<String> dirtyFields, final ODocument iRecord) { private static void processCompositeIndexUpdate(final OIndex<?> index, final Set<String> dirtyFields, final ODocument iRecord) {
Expand Down
Expand Up @@ -20,7 +20,6 @@
package com.orientechnologies.orient.core.metadata.function; package com.orientechnologies.orient.core.metadata.function;


import com.orientechnologies.orient.core.Orient; import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument; import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; import com.orientechnologies.orient.core.hook.ODocumentHookAbstract;
import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.record.impl.ODocument;
Expand All @@ -31,7 +30,9 @@
* @author Luca Garulli * @author Luca Garulli
*/ */
public class OFunctionTrigger extends ODocumentHookAbstract { public class OFunctionTrigger extends ODocumentHookAbstract {
public OFunctionTrigger() {
public OFunctionTrigger(ODatabaseDocument database) {
super(database);
setIncludeClasses("OFunction"); setIncludeClasses("OFunction");
} }


Expand All @@ -55,9 +56,8 @@ public void onRecordAfterDelete(final ODocument iDocument) {
} }


protected void reloadLibrary() { protected void reloadLibrary() {
final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); database.getMetadata().getFunctionLibrary().load();
db.getMetadata().getFunctionLibrary().load();


Orient.instance().getScriptManager().close(db.getName()); Orient.instance().getScriptManager().close(database.getName());
} }
} }
Expand Up @@ -21,13 +21,11 @@


import java.util.Set; import java.util.Set;


import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument; import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable; import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OConfigurationException; import com.orientechnologies.orient.core.exception.OConfigurationException;
import com.orientechnologies.orient.core.exception.OSecurityException; import com.orientechnologies.orient.core.exception.OSecurityException;
import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; import com.orientechnologies.orient.core.hook.ODocumentHookAbstract;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OImmutableClass; import com.orientechnologies.orient.core.metadata.schema.OImmutableClass;
import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal; import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
Expand All @@ -39,7 +37,9 @@
* @author Luca Garulli * @author Luca Garulli
*/ */
public class ORestrictedAccessHook extends ODocumentHookAbstract { public class ORestrictedAccessHook extends ODocumentHookAbstract {
public ORestrictedAccessHook() {
public ORestrictedAccessHook(ODatabaseDocument database) {
super(database);
} }


public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() { public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
Expand All @@ -58,15 +58,13 @@ public RESULT onRecordBeforeCreate(final ODocument iDocument) {
if (identityType == null) if (identityType == null)
identityType = "user"; identityType = "user";


final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get();

OIdentifiable identity = null; OIdentifiable identity = null;
if (identityType.equals("user")) { if (identityType.equals("user")) {
final OSecurityUser user = db.getUser(); final OSecurityUser user = database.getUser();
if (user != null) if (user != null)
identity = user.getIdentity(); identity = user.getIdentity();
} else if (identityType.equals("role")) { } else if (identityType.equals("role")) {
final Set<? extends OSecurityRole> roles = db.getUser().getRoles(); final Set<? extends OSecurityRole> roles = database.getUser().getRoles();
if (!roles.isEmpty()) if (!roles.isEmpty())
identity = roles.iterator().next().getIdentity(); identity = roles.iterator().next().getIdentity();
} else } else
Expand All @@ -75,7 +73,7 @@ public RESULT onRecordBeforeCreate(final ODocument iDocument) {


if (identity != null) { if (identity != null) {
for (String f : fields) for (String f : fields)
db.getMetadata().getSecurity().allowIdentity(iDocument, f, identity); database.getMetadata().getSecurity().allowIdentity(iDocument, f, identity);
return RESULT.RECORD_CHANGED; return RESULT.RECORD_CHANGED;
} }
} }
Expand Down Expand Up @@ -106,24 +104,22 @@ protected boolean isAllowed(final ODocument iDocument, final String iAllowOperat
final OImmutableClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument); final OImmutableClass cls = ODocumentInternal.getImmutableSchemaClass(iDocument);
if (cls != null && cls.isRestricted()) { if (cls != null && cls.isRestricted()) {


final ODatabaseDocument db = ODatabaseRecordThreadLocal.INSTANCE.get(); if (database.getUser() == null)

if (db.getUser() == null)
return true; return true;


if (db.getUser().isRuleDefined(ORule.ResourceGeneric.BYPASS_RESTRICTED, null)) if (database.getUser().isRuleDefined(ORule.ResourceGeneric.BYPASS_RESTRICTED, null))
if (db.getUser().checkIfAllowed(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_READ) != null) if (database.getUser().checkIfAllowed(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_READ) != null)
// BYPASS RECORD LEVEL SECURITY: ONLY "ADMIN" ROLE CAN BY DEFAULT // BYPASS RECORD LEVEL SECURITY: ONLY "ADMIN" ROLE CAN BY DEFAULT
return true; return true;


final ODocument doc; final ODocument doc;
if (iReadOriginal) if (iReadOriginal)
// RELOAD TO AVOID HACKING OF "_ALLOW" FIELDS // RELOAD TO AVOID HACKING OF "_ALLOW" FIELDS
doc = (ODocument) db.load(iDocument.getIdentity()); doc = (ODocument) database.load(iDocument.getIdentity());
else else
doc = iDocument; doc = iDocument;


return db return database
.getMetadata() .getMetadata()
.getSecurity() .getSecurity()
.isAllowed((Set<OIdentifiable>) doc.field(OSecurityShared.ALLOW_ALL_FIELD), .isAllowed((Set<OIdentifiable>) doc.field(OSecurityShared.ALLOW_ALL_FIELD),
Expand Down
@@ -1,5 +1,6 @@
package com.orientechnologies.orient.core.metadata.security; package com.orientechnologies.orient.core.metadata.security;


import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.hook.ODocumentHookAbstract; import com.orientechnologies.orient.core.hook.ODocumentHookAbstract;
import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal; import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
Expand All @@ -13,7 +14,8 @@
public class OSecurityTrackerHook extends ODocumentHookAbstract { public class OSecurityTrackerHook extends ODocumentHookAbstract {
private final WeakReference<OSecurity> security; private final WeakReference<OSecurity> security;


public OSecurityTrackerHook(OSecurity security) { public OSecurityTrackerHook(OSecurity security, ODatabaseDocument database) {
super(database);
this.security = new WeakReference<OSecurity>(security); this.security = new WeakReference<OSecurity>(security);
} }


Expand Down Expand Up @@ -53,8 +55,8 @@ public void onRecordDeleteReplicated(ODocument doc) {
} }


private void incrementSchemaVersion(ODocument doc) { private void incrementSchemaVersion(ODocument doc) {
if (ODocumentInternal.getImmutableSchemaClass(doc) == null) if (ODocumentInternal.getImmutableSchemaClass(doc) == null)
return; return;


final String className = ODocumentInternal.getImmutableSchemaClass(doc).getName(); final String className = ODocumentInternal.getImmutableSchemaClass(doc).getName();


Expand Down

0 comments on commit fb74038

Please sign in to comment.