Some other SPI deficiencies off the top of my head, while we're on the
subject:
1. Default workspace name. They should not be specified in JCR2SPI.
When you log into Exchange or an IMAP server, or a SQL Server, you get a
default namespace (or database) based on what the server thinks is the
right default name for you. The name should not be known at the client
(that's why it's a default!). Once you're logged in, you can ask for
other namespace names, which also fits in nicely with JCR
(Workspace.getAccessibleWorkspaceNames). But specifying a default
namespace name in JCR2SPI is wrong, because a null should be passed when
the server should choose.
2. Repository descriptors over SPI. This is a deficiency. I have a web
service that remotes SPI, and I can't ask the target web service for the
repository descriptors. It doesn't help that JCR2SPI asks for the
descriptors before a login. That makes it almost mandatory to mock up
fake values in the SPI, before a dynamic proxy can initialize itself, if
initialization is dependent on logging in, as is the case in 2 out of
the 5 SPIs I have in front of me, or in cases where the target web
service is deployed under the umbrella of container-managed security.
3. Out-of-band data. Since JCR doesn't address configuration, or a
RepositoryFactory pattern (even though Jackrabbit provides one), it is
up to each implementation to get configuration done. SPI could use a
place to pass this data. SimpleCredential attributes are not enough.
IIOP for CORBA is a good example, where you can stuff profiles with your
extra data, like timezone of the client, alternate server names,
whatever. For SPI, the out-of-band data could be a place where the
implementation-specific BatchReadConfig for a specific getItemInfos
operation could be placed. I'm not sure this is a good idea for SPI, but
I'm thinking about it.
David
On Mon, 2008-02-11 at 18:49 +0100, Julian Reschke wrote:
> Julian Reschke wrote:
> > At the end of the day, what we should do is *measure* the performance of
> > JCR2SPI compared to native implementations. I'll try to submit a few
> > tests soon.
> > ...
>
> OK, I've got tests (not polished) and numbers.
>
> Scenario:
>
> A collection /a/b with 500 members, each 1024 in size, content type
> application/octet-stream.
>
> Test code that obtains all members, checking content type, size, and
> total number.
>
> My store can do that in ~80ms.
>
> Why doing it through SPI (with limited batch read support), it will take
> ~1500ms.
>
> Wrapping that with JCR2SPI, it takes around ~2700ms.
>
> So it seems we need drastically remove the overhead introduced by the
> SPI API.
>
> Test code below:
>
>
> private String createTestCollJcr(String p_parentpath, int p_members,
> int p_size) throws Exception {
> Repository l_repository = getRepository();
> Session l_session = null;
>
> try {
> l_session = l_repository.login(getCredentials());
>
> Node l_folder = null;
> try {
> l_folder = (Node)l_session.getItem(p_parentpath + "/bigcoll");
> }
> catch (RepositoryException ex) {
> // nothing to do
> }
>
> // delete when needed
> if (l_folder != null) {
> l_folder.remove();
> l_session.save();
> }
>
> Node l_parent = (Node)l_session.getItem(p_parentpath);
> l_folder = l_parent.addNode("bigcoll", "nt:folder");
> assertNotNull(l_folder);
>
> long l_cnt = 0;
>
> while (l_cnt < p_members) {
> InputStream l_is = new BufferedInputStream(new
> ContentGenerator(p_size), p_size);
> Node l_new = l_folder.addNode("tst" + l_cnt, "nt:file");
> Node l_cnew = l_new.addNode("jcr:content", "nt:resource");
> l_cnew.setProperty("jcr:data", l_is);
> l_cnew.setProperty("jcr:mimeType", "application/octet-stream");
> l_session.save();
> l_cnt += 1;
> }
> }
> finally {
> if (l_session != null) {
> l_session.logout();
> }
> }
>
> return p_parentpath + "/bigcoll";
> }
>
>
> private static int BIGCOLLMEMBERS = 500;
> private static int BIGCOLLMEMBERSIZE = 1024;
> private static String BIGCOLLMIMETYPE = "application/octet-stream";
>
> public void testGetMembersSpi() throws Exception {
>
> String l_path = createTestColl(this.m_path, BIGCOLLMEMBERS,
> BIGCOLLMEMBERSIZE);
>
> RepositoryService l_rs = getRepositoryService();
> SessionInfo l_si = null;
>
> try {
> l_si = l_rs.obtain(getCredentials(), null);
>
> long l_start = System.currentTimeMillis();
> long l_cnt = 0;
>
> while (System.currentTimeMillis() - l_start < MS || l_cnt < 5) {
> NodeId l_nid = TestPerf.computeNodeId(l_rs, l_si, l_path);
> int l_members = 0;
> for (Iterator<ChildInfo> l_it =
> (Iterator<ChildInfo>)l_rs.getChildInfos(l_si, l_nid); l_it.hasNext(); ) {
> ChildInfo l_c = l_it.next();
> assertNotNull(l_c);
> NodeId l_cnid =
> l_rs.getIdFactory().createNodeId(l_c.getUniqueID());
> NodeInfo l_node = null;
> NodeInfo l_contentnode = null;
> PropertyInfo l_mimetype = null;
> PropertyInfo l_data = null;
> Iterator l_iteminfos = l_rs.getItemInfos(l_si, l_cnid);
> l_node = (NodeInfo)l_iteminfos.next();
> assertNotNull(l_node);
>
> while (l_iteminfos.hasNext()) {
> ItemInfo l_i = (ItemInfo)l_iteminfos.next();
> if (l_i.getParentId().equals(l_node.getId()) &&
> NameConstants.JCR_CONTENT.equals(l_i.getName())) {
> l_contentnode = (NodeInfo)l_i;
> }
> if (l_contentnode != null &&
> l_i.getParentId().equals(l_contentnode.getId()) &&
> NameConstants.JCR_MIMETYPE.equals(l_i.getName())) {
> l_mimetype = (PropertyInfo)l_i;
> }
> if (l_contentnode != null &&
> l_i.getParentId().equals(l_contentnode.getId()) &&
> NameConstants.JCR_DATA.equals(l_i.getName())) {
> l_data = (PropertyInfo)l_i;
> }
> }
>
> if (l_contentnode == null) {
> // explicitly fetch the content node, it wasn't returned
> with the parent
> NodeId l_contentnodeid =
> l_rs.getIdFactory().createNodeId(l_c.getUniqueID(),
> l_rs.getPathFactory().create(NameConstants.JCR_CONTENT));
> Iterator l_iteminfos2 = l_rs.getItemInfos(l_si,
> l_contentnodeid);
> l_contentnode = (NodeInfo)l_iteminfos2.next();
> while (l_iteminfos2.hasNext()) {
> ItemInfo l_i = (ItemInfo)l_iteminfos2.next();
> if (l_i.getParentId().equals(l_contentnode.getId()) &&
> NameConstants.JCR_MIMETYPE.equals(l_i.getName())) {
> l_mimetype = (PropertyInfo)l_i;
> }
> if (l_i.getParentId().equals(l_contentnode.getId()) &&
> NameConstants.JCR_DATA.equals(l_i.getName())) {
> l_data = (PropertyInfo)l_i;
> }
> }
> }
>
> assertNotNull(l_contentnode);
>
> if (l_mimetype == null) {
> // explicitly fetch the mime type property, it wasn't
> returned with the parent
> PropertyId l_mimetypeid =
> l_rs.getIdFactory().createPropertyId(l_contentnode.getId(),
> NameConstants.JCR_MIMETYPE);
> l_mimetype = l_rs.getPropertyInfo(l_si, l_mimetypeid);
> }
>
> assertNotNull(l_mimetype);
> assertEquals(BIGCOLLMIMETYPE,
> l_mimetype.getValues()[0].getString());
>
> if (l_data == null) {
> // explicitly fetch the mime type property, it wasn't
> returned with the parent
> PropertyId l_dataid =
> l_rs.getIdFactory().createPropertyId(l_contentnode.getId(),
> NameConstants.JCR_DATA);
> l_data = l_rs.getPropertyInfo(l_si, l_dataid);
> }
>
> assertNotNull(l_data);
> assertEquals(BIGCOLLMEMBERSIZE,
> l_data.getValues()[0].getLength());
>
> l_members += 1;
> }
> assertEquals(BIGCOLLMEMBERS, l_members);
> l_cnt += 1;
> }
>
> long l_elapsed = System.currentTimeMillis() - l_start;
>
> LOG.info(String.format("GetMembers - SPI: %.4fms per call (%d
> iterations)", (double)l_elapsed / l_cnt, l_cnt));
>
> }
> finally {
> if (l_si != null) {
> l_rs.dispose(l_si);
> }
> }
> }
>
> public void testGetMembersJcr() throws Exception {
>
> String l_path = createTestCollJcr(this.m_path, BIGCOLLMEMBERS,
> BIGCOLLMEMBERSIZE);
>
> Repository l_repository = getRepository();
> Session l_session = null;
>
> try {
> l_session = l_repository.login(getCredentials(), null);
>
> long l_start = System.currentTimeMillis();
> long l_cnt = 0;
>
> while (System.currentTimeMillis() - l_start < MS || l_cnt < 5) {
> Node l_dir = (Node)l_session.getItem(l_path);
> assertNotNull(l_dir);
> int l_members = 0;
> for (NodeIterator l_it = l_dir.getNodes(); l_it.hasNext(); ) {
> Node l_c = l_it.nextNode();
> Node l_e = l_c.getNode("jcr:content");
> String l_type = l_e.getProperty("jcr:mimeType").getString();
> long l_length = l_e.getProperty("jcr:data").getLength();
> assertTrue(l_c.isNode());
> assertEquals(BIGCOLLMIMETYPE, l_type);
> assertEquals(BIGCOLLMEMBERSIZE, l_length);
> l_members += 1;
> }
> assertEquals(BIGCOLLMEMBERS, l_members);
> l_session.refresh(false);
> l_cnt += 1;
> }
>
> long l_elapsed = System.currentTimeMillis() - l_start;
>
> LOG.info(String.format("GetMembers - JCR: %.4fms per call (%d
> iterations)", (double)l_elapsed / l_cnt, l_cnt));
>
> }
> finally {
> if (l_session != null) {
> l_session.logout();
> }
> }
> }
>
> private class ContentGenerator extends InputStream {
>
> private long m_length;
> private long m_position;
>
> public ContentGenerator(long p_length) {
> this.m_length = p_length;
> this.m_position = 0;
> }
>
> public int read() {
>
> if (this.m_position++ < this.m_length) {
> return 0;
> }
> else {
> return -1;
> }
> }
> }
>
Visit Synchronica at GSMA Mobile World Congress, Barcelona, 11-14 Feb, Hall 2,
Booth #2J25