[Registry-dev] svn commit r19876 - in trunk/registry/modules/core/src: main/java/org/wso2/registry main/java/org/wso2/registry/app main/java/org/wso2/registry/jdbc/dao main/java/org/wso2/registry/jdbc/filecache main/java/org/wso2/registry/jdbc/indexing test/java/org/wso2/registry/jdbc
chathura at wso2.com
chathura at wso2.com
Tue Jul 22 21:53:48 PDT 2008
Author: chathura
Date: Tue Jul 22 21:53:48 2008
New Revision: 19876
URL: http://wso2.org/svn/browse/wso2?view=rev&revision=19876
Log:
Improved the content handling of resource implementation. Now contents are always cached in the file system and handled as streams.
This gives more power to handler authors as they can access content streams without limitations.
Improved resource copy operation. Now contents are not duplicated when copying resource.
There are more improvements to be done for the UI layer to handle content efficiently according to the new content handling method.
Added:
trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/ResourcesTest.java
Modified:
trunk/registry/modules/core/src/main/java/org/wso2/registry/CollectionImpl.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/Resource.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceImpl.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceVersionImpl.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RegistryAdapter.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RemoteResourceImpl.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceDAO.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceVersionDAO.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileData.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileManager.java
trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/indexing/Indexer.java
trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/CopyTest.java
trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/JDBCRegistryTest.java
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/CollectionImpl.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/CollectionImpl.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/CollectionImpl.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/CollectionImpl.java Tue Jul 22 21:53:48 2008
@@ -43,7 +43,7 @@
childCount = paths.length;
}
- public void setContent(Object content) {
+ public void setContent(Object content) throws RegistryException {
// note that string contents are allowed in collection to support custom generated UIs.
if (content instanceof String[] ||
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/Resource.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/Resource.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/Resource.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/Resource.java Tue Jul 22 21:53:48 2008
@@ -94,17 +94,19 @@
Object getContent() throws RegistryException;
- void setContent(Object content);
+ void setContent(Object content) throws RegistryException;
String getLastUpdaterUserName();
InputStream getContentStream() throws RegistryException;
- void setContentStream(InputStream contentStream);
+ void setContentStream(InputStream contentStream) throws RegistryException;
List<String> getAspects();
void addAspect(String name);
void removeAspect(String name);
+
+ void discard();
}
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceImpl.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceImpl.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceImpl.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceImpl.java Tue Jul 22 21:53:48 2008
@@ -20,6 +20,7 @@
import org.apache.commons.logging.LogFactory;
import org.wso2.registry.jdbc.dao.ResourceDAO;
import org.wso2.registry.jdbc.utils.Transaction;
+import org.wso2.registry.jdbc.filecache.FileManager;
import org.wso2.registry.utils.UUIDGenerator;
import org.wso2.registry.exceptions.RegistryException;
@@ -42,6 +43,20 @@
* Each resource instance contains a unique path within a Registry instance. Registry.get(...)
* method invocation using this path gives an instance of that resource. This path can be
* combined with the base URL of the registry server to generate a URI for the resource.
+ *
+ * <strong>Handling content</strong>
+ *
+ * Contents of resource can be set either as an input stream or an object. If an input stream is
+ * set, an unique file based content will be created for the given input stream. If an object of
+ * type byte[] or String is set, an inmemeory input stream will be created from it and then a
+ * unique file based input stream is created. If an object of any other type is set, there should
+ * be a handler to convert it to an input stream before reaching the repository.
+ *
+ * When a resource is retrieved from the database layer, its content is not retrieved from the
+ * database. Instead, UUID referring to the database content is stored in dbBasedContentID. When
+ * the content is first accessed, a file based input stream is created from the database content
+ * and fileBasedContentID is set (fileBasedContentID = dbBasedContentID).
+ *
*/
public class ResourceImpl implements Resource {
@@ -153,10 +168,18 @@
protected Properties properties = new Properties();
/**
- * UUID refering to the content of this resource. Note that the content may be shared by
- * multiple resource. Content ID is not exposed outside the Registry API.
+ * UUID refering to the file based content of this resource. When an input stream is set as the
+ * content of the resource, a file based content is created for it. This ID is used refer to
+ * that file based content. File based content ID is not exposed outside the Registry API.
*/
- protected String contentID;
+ protected String fileBasedContentID;
+
+ /**
+ * UUID of the content stored in the database. This ID is set when a resource is retrieved from
+ * the database. Upon the first getContentStream() call, a file based content will be created
+ * from this database based content and file based content ID will be set.
+ */
+ protected String dbBasedContentID;
/**
* Content of the resource. Object and the type stored in this field depends on the resource
@@ -179,7 +202,7 @@
/**
* Content of the resource, represented as a input stream.
*/
- protected InputStream contentStream;
+ //protected InputStream contentStream;
/**
@@ -260,7 +283,7 @@
public void setDescription(String description) {
this.description = description;
- versionableChange = true;
+ versionableChange = true;
}
public String getPath() {
@@ -401,33 +424,34 @@
setPropertiesModified(true);
}
- public String getContentID() {
- return contentID;
+ public String getFileBasedContentID() {
+ return fileBasedContentID;
+ }
+
+ public void setFileBasedContentID(String fileBasedContentID) {
+ this.fileBasedContentID = fileBasedContentID;
+ }
+
+ public String getDbBasedContentID() {
+ return dbBasedContentID;
}
- public void setContentID(String contentID) {
- this.contentID = contentID;
+ public void setDbBasedContentID(String dbBasedContentID) {
+ this.dbBasedContentID = dbBasedContentID;
}
public InputStream getContentStream() throws RegistryException {
- if (contentStream == null) {
- if (content != null) {
+ if (fileBasedContentID != null) {
+ return FileManager.getInstance().getFileBasedInputStream(fileBasedContentID);
+ }
- if (content instanceof byte[]) {
- this.contentStream =
- new BufferedInputStream(new ByteArrayInputStream((byte[]) content));
-
- } else if (content instanceof String) {
- byte[] contentBytes = ((String) content).getBytes();
- this.contentStream =
- new BufferedInputStream(new ByteArrayInputStream(contentBytes));
- }
+ if (dbBasedContentID != null) {
- } else if (Transaction.isStarted()) {
+ if (Transaction.isStarted()) {
Connection conn = Transaction.getConnection();
- contentStream = resourceDAO.getResourceContentStream(contentID, conn);
+ resourceDAO.createFileBasedContentFromDB(dbBasedContentID, conn);
} else if (dataSource != null) {
@@ -435,7 +459,7 @@
try {
conn = dataSource.getConnection();
- contentStream = resourceDAO.getResourceContentStream(contentID, conn);
+ resourceDAO.createFileBasedContentFromDB(dbBasedContentID, conn);
} catch (Exception e) {
String msg =
@@ -453,27 +477,55 @@
}
}
}
+
+ fileBasedContentID = dbBasedContentID;
+ return FileManager.getInstance().getFileBasedInputStream(fileBasedContentID);
}
- return contentStream;
+ if (content != null) {
+
+ if (content instanceof byte[]) {
+ return new BufferedInputStream(new ByteArrayInputStream((byte[]) content));
+
+ } else if (content instanceof String) {
+ byte[] contentBytes = ((String) content).getBytes();
+ return new BufferedInputStream(new ByteArrayInputStream(contentBytes));
+ }
+ }
+ return null;
}
- public void setContentStream(InputStream contentStream) {
+ /**
+ * Invalidates the current file based content and creates a new file based content for the
+ * new content stream. Given content stream will be closed after completing this method.
+ *
+ * @param contentStream input stream containing the new content
+ */
+ public void setContentStream(InputStream contentStream) throws RegistryException {
+
+ if (fileBasedContentID != null) {
+ FileManager.getInstance().activateAutoDelete(fileBasedContentID);
+ }
+
+ fileBasedContentID = UUIDGenerator.generateUUID();
+ FileManager.getInstance().createFileBasedContent(fileBasedContentID, contentStream);
// When a content is set as an input stream, prevoius contents set as object get invalidated
// But we don't want to generate that content till it is explicitly get using getContent
if (contentStream != null) {
- this.content = null;
+ content = null;
+ dbBasedContentID = null;
}
- this.contentStream = contentStream;
+ //this.contentStream = contentStream;
setContentModified(true);
}
public Object getContent() throws RegistryException {
+
if (content == null) {
- contentStream = getContentStream();
+ InputStream contentStream = getContentStream();
if (contentStream != null) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(contentStream);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -504,19 +556,26 @@
return content;
}
- public void setContent(Object content) {
- if (content instanceof InputStream) {
- setContentStream((InputStream)content);
- } else {
- this.content = content;
+ public void setContent(Object content) throws RegistryException {
- // When a new content is set, previous contents set as input stream get invalid
- // But we don't want to generate that content till it is explicitly get
- // using getContentStream
- if (content != null) {
- this.contentStream = null;
- }
+ InputStream contentStream = null;
+ if (content instanceof byte[]) {
+ contentStream = new BufferedInputStream(new ByteArrayInputStream((byte[]) content));
+
+ } else if (content instanceof String) {
+ byte[] contentBytes = ((String) content).getBytes();
+ contentStream = new BufferedInputStream(new ByteArrayInputStream(contentBytes));
+
+ } else if (content instanceof InputStream) {
+ contentStream = (InputStream)content;
}
+
+ if (contentStream != null) {
+ setContentStream(contentStream);
+ }
+
+ this.content = content;
+
setContentModified(true);
}
@@ -593,4 +652,10 @@
if (aspects != null)
aspects.remove(name);
}
+
+ public void discard() {
+ if (fileBasedContentID != null) {
+ FileManager.getInstance().activateAutoDelete(fileBasedContentID);
+ }
+ }
}
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceVersionImpl.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceVersionImpl.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceVersionImpl.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/ResourceVersionImpl.java Tue Jul 22 21:53:48 2008
@@ -20,6 +20,7 @@
import org.apache.commons.logging.LogFactory;
import org.wso2.registry.jdbc.dao.ResourceVersionDAO;
import org.wso2.registry.jdbc.utils.Transaction;
+import org.wso2.registry.jdbc.filecache.FileManager;
import org.wso2.registry.exceptions.RegistryException;
import java.io.BufferedInputStream;
@@ -36,23 +37,16 @@
public InputStream getContentStream() throws RegistryException {
- if (contentStream == null) {
- if (content != null) {
+ if (fileBasedContentID != null) {
+ return FileManager.getInstance().getFileBasedInputStream(fileBasedContentID);
+ }
- if (content instanceof byte[]) {
- this.contentStream =
- new BufferedInputStream(new ByteArrayInputStream((byte[]) content));
-
- } else if (content instanceof String) {
- byte[] contentBytes = ((String) content).getBytes();
- this.contentStream =
- new BufferedInputStream(new ByteArrayInputStream(contentBytes));
- }
+ if (dbBasedContentID != null) {
- } else if (Transaction.isStarted()) {
+ if (Transaction.isStarted()) {
Connection conn = Transaction.getConnection();
- contentStream = resourceVersionDAO.getResourceContentStream(contentID, conn);
+ resourceVersionDAO.createFileBasedContentFromDB(dbBasedContentID, conn);
} else if (dataSource != null) {
@@ -60,12 +54,11 @@
try {
conn = dataSource.getConnection();
- contentStream = resourceVersionDAO.getResourceContentStream(contentID, conn);
+ resourceVersionDAO.createFileBasedContentFromDB(dbBasedContentID, conn);
} catch (Exception e) {
String msg =
- "Failed to get the input stream for the content " +
- "of versioned resource: " + path;
+ "Failed to get the input stream for the content of resource: " + path;
log.error(msg, e);
throw new RegistryException(msg, e);
@@ -79,8 +72,22 @@
}
}
}
+
+ fileBasedContentID = dbBasedContentID;
+ return FileManager.getInstance().getFileBasedInputStream(fileBasedContentID);
+ }
+
+ if (content != null) {
+
+ if (content instanceof byte[]) {
+ return new BufferedInputStream(new ByteArrayInputStream((byte[]) content));
+
+ } else if (content instanceof String) {
+ byte[] contentBytes = ((String) content).getBytes();
+ return new BufferedInputStream(new ByteArrayInputStream(contentBytes));
+ }
}
- return contentStream;
+ return null;
}
}
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RegistryAdapter.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RegistryAdapter.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RegistryAdapter.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RegistryAdapter.java Tue Jul 22 21:53:48 2008
@@ -51,7 +51,7 @@
this.embeddedRegistry = embeddedRegistry;
myRegistry = embeddedRegistry.getSystemRegistry();
}
-
+
/**
* Handle anything out of the ordinary Abdera-supported world.
@@ -109,7 +109,7 @@
// Doing a HEAD on a collection
try {
return buildHeadEntryResponse(request, getId(resource),
- resource.getLastModified());
+ resource.getLastModified());
} catch (ResponseContextException e) {
log.error(e);
return e.getResponseContext();
@@ -276,7 +276,7 @@
Entry entry = (Entry)request.getDocument().getRoot();
String text = entry.getContent();
secureRegistry.editComment(path, text);
- return new EmptyResponseContext(200);
+ return new EmptyResponseContext(200);
} else if (method.equals("DELETE")) {
secureRegistry.delete(path);
return new EmptyResponseContext(200);
@@ -378,7 +378,7 @@
String path = tagPath.getResourcePath();
entry.setTitle(path);
entry.addSimpleExtension(new QName(APPConstants.NAMESPACE, "taggings"),
- "" + tagPath.getTagCount());
+ "" + tagPath.getTagCount());
Map tagCounts = tagPath.getTagCounts();
Iterator iCounts = tagCounts.keySet().iterator();
java.util.Properties properties = new java.util.Properties();
@@ -388,10 +388,10 @@
properties.put(key, count);
}
RemoteRegistry.addPropertyExtensionElement(properties,
- factory,
- entry,
- PropertyExtensionFactory.TAGS,
- PropertyExtensionFactory.TAG);
+ factory,
+ entry,
+ PropertyExtensionFactory.TAGS,
+ PropertyExtensionFactory.TAG);
// entry.addSimpleExtension(new QName("tagCounts"), "" + tagPath.getTagCount())
// entry.addLink(baseUri + "atom" + path, "path");
// entry.addLink(baseUri + "atom" + path);
@@ -407,7 +407,7 @@
String path = request.getTargetPath();
if (!path.startsWith(APPConstants.ATOM)) {
return new StringResponseContext("Bad import path - must start with " +
- APPConstants.ATOM, 400);
+ APPConstants.ATOM, 400);
}
int idx = path.indexOf("?");
path = path.substring(APPConstants.ATOM.length(), idx);
@@ -417,8 +417,8 @@
String location;
try {
location = getSecureRegistry(request).importResource(suggestedPath,
- importURL,
- metadata);
+ importURL,
+ metadata);
} catch (RegistryException e) {
return new StringResponseContext(e, 500);
}
@@ -469,7 +469,7 @@
public static Map<String, String> parseQueryString(String query) {
Map<String, String> map = new HashMap<String, String>();
if (query == null) return map;
-
+
StringTokenizer st = new StringTokenizer(query, "?&=", true);
String previous = "";
while (st.hasMoreTokens()) {
@@ -499,14 +499,14 @@
feed.setTitle("Average Rating for the resource " + path);
String nodeLink = getAbsolutePath(request, path) +
- RegistryConstants.URL_SEPARATOR +
- APPConstants.PARAMETER_RATINGS;
+ RegistryConstants.URL_SEPARATOR +
+ APPConstants.PARAMETER_RATINGS;
feed.addLink(nodeLink);
try {
feed.addSimpleExtension(APPConstants.QNAME_AVGRATING,
- "" + myRegistry.getAverageRating(path));
+ "" + myRegistry.getAverageRating(path));
Collection c = (Collection)myRegistry.get(path + RegistryConstants.URL_SEPARATOR +
- "ratings");
+ "ratings");
String [] ratings = (String[])c.getContent();
if (ratings != null && ratings.length > 0) {
for (String rating : ratings) {
@@ -629,9 +629,9 @@
entry.setContentAsHtml(logentry.getText());
entry.addAuthor(logentry.getUserName());
entry.addSimpleExtension(new QName(APPConstants.NAMESPACE, "action"),
- "" + logentry.getAction());
+ "" + logentry.getAction());
entry.addSimpleExtension(new QName(APPConstants.NAMESPACE, "path"),
- logentry.getResourcePath());
+ logentry.getResourcePath());
feed.addEntry(entry);
}
return buildResponseContextFromFeed(feed);
@@ -679,7 +679,7 @@
entry.setTitle(tag.getTagName());
entry.setContent(tag.getTagName());
entry.addSimpleExtension(new QName(APPConstants.NAMESPACE, "taggings"),
- "" + tag.getTagCount());
+ "" + tag.getTagCount());
feed.addEntry(entry);
}
@@ -699,7 +699,7 @@
}
final EmptyResponseContext response = new EmptyResponseContext(200, "Tag applied");
response.setLocation(getAbsolutePath(request, path) +
- RegistryConstants.URL_SEPARATOR + "tags:" + tag);
+ RegistryConstants.URL_SEPARATOR + "tags:" + tag);
return response;
}
String firstTag = null;
@@ -721,7 +721,7 @@
}
final EmptyResponseContext response = new EmptyResponseContext(200, "Tag applied");
response.setLocation(getAbsolutePath(request, path) +
- RegistryConstants.URL_SEPARATOR + "tags:" + firstTag);
+ RegistryConstants.URL_SEPARATOR + "tags:" + firstTag);
return response;
}
return null;
@@ -748,7 +748,7 @@
modified = ((Feed)base).getUpdatedElement().getText();
}
return EntityTag.generate(id, modified);
- }
+ }
public ResponseContext postEntry(RequestContext request) {
Document<Element> document;
@@ -824,7 +824,7 @@
}
final String [] splitPath = (String [])request.getAttribute(RequestContext.Scope.REQUEST,
- "splitPath");
+ "splitPath");
final String text = content.getText();
if (splitPath != null) {
if ("comments".equals(splitPath[1])) {
@@ -863,8 +863,8 @@
try {
final Registry secureRegistry = getSecureRegistry(request);
location = secureRegistry.importResource(suggestedPath,
- importURL,
- metadata);
+ importURL,
+ metadata);
return secureRegistry.get(location);
} catch (RegistryException e) {
throw new ResponseContextException(new StackTraceResponseContext(e));
@@ -902,7 +902,7 @@
return ret;
}
- private void fillResourceFromEntry(Entry entry, Resource ret) {
+ private void fillResourceFromEntry(Entry entry, Resource ret) throws RegistryException {
Properties properties = entry.getExtension(PropertyExtensionFactory.PROPERTIES);
RemoteRegistry.createPropertiesFromExtensionElement(properties, ret);
String mediaType = entry.getSimpleExtension(new QName(APPConstants.NAMESPACE, "mediaType"));
@@ -997,7 +997,7 @@
// String foo = request.getBaseUri().toString();
// foo += entryObj.getPath();
// e.addLink(foo, "alternate");
-
+
if (entryObj instanceof Comment) {
e.addLink(link, "direct");
e.addLink(((Comment)entryObj).getResourcePath(), "resourcePath");
@@ -1038,10 +1038,10 @@
// }
RemoteRegistry.addPropertyExtensionElement(entryObj.getProperties(),
- factory,
- e,
- PropertyExtensionFactory.PROPERTIES,
- PropertyExtensionFactory.PROPERTY);
+ factory,
+ e,
+ PropertyExtensionFactory.PROPERTIES,
+ PropertyExtensionFactory.PROPERTY);
return super.addEntryDetails(request, e, feedIri, entryObj);
}
@@ -1051,12 +1051,12 @@
super.addFeedDetails(feed, request);
final Resource resource = ((ResourceTarget)request.getTarget()).getResource();
RemoteRegistry.addPropertyExtensionElement(resource.getProperties(),
- factory,
- feed,
- PropertyExtensionFactory.PROPERTIES,
- PropertyExtensionFactory.PROPERTY);
+ factory,
+ feed,
+ PropertyExtensionFactory.PROPERTIES,
+ PropertyExtensionFactory.PROPERTY);
if (request.getTarget().getType() == RegistryResolver.COMMENTS_TYPE) {
- feed.addSimpleExtension(APPConstants.COMMENTS_QNAME, "true");
+ feed.addSimpleExtension(APPConstants.COMMENTS_QNAME, "true");
}
feed.addSimpleExtension(APPConstants.QNAME_LAST_UPDATER, resource.getLastUpdaterUserName());
long snapshotID = ((ResourceImpl)resource).getMatchingSnapshotID();
@@ -1066,7 +1066,7 @@
if (resource instanceof Collection) {
try {
feed.addSimpleExtension(APPConstants.QNAME_CHILD_COUNT,
- "" + ((Collection)resource).getChildCount());
+ "" + ((Collection)resource).getChildCount());
} catch (RegistryException e) {
throw new ResponseContextException(new StackTraceResponseContext(e));
}
@@ -1079,14 +1079,14 @@
Resource resource = ((ResourceTarget)request.getTarget()).getResource();
final String [] splitPath = (String [])request.getAttribute(RequestContext.Scope.REQUEST,
- "splitPath");
+ "splitPath");
if (splitPath != null) {
if ("comments".equals(splitPath[1])) {
// Looking for comments, not the resource itself... so...
try {
resource = getSecureRegistry(request).get(resource.getPath() +
- RegistryConstants.URL_SEPARATOR +
- "comments");
+ RegistryConstants.URL_SEPARATOR +
+ "comments");
} catch (RegistryException e) {
throw new ResponseContextException(new StackTraceResponseContext(e));
}
@@ -1099,7 +1099,7 @@
public Iterator<Resource> iterator() {
try {
return new Resourcerator((Object [])r.getContent(),
- getSecureRegistry(request));
+ getSecureRegistry(request));
} catch (RegistryException e) {
return null;
}
@@ -1243,7 +1243,7 @@
String path = ((ResourceTarget)request.getTarget()).getResource().getPath();
final String [] splitPath = (String [])request.getAttribute(RequestContext.Scope.REQUEST,
- "splitPath");
+ "splitPath");
if (splitPath != null) {
if ("comments".equals(splitPath[1])) {
if (!mimeType.toString().equals("text/plain")) {
@@ -1282,21 +1282,24 @@
}
ret.setMediaType(mimeType.toString());
// System.out.println("Mime type is " + mimeType.toString());
- if (!isCollection) {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- byte [] buffer = new byte[1024];
- try {
- while (inputStream.available() > 0) {
- int amount = inputStream.read(buffer, 0, 1024);
- bos.write(buffer, 0, amount);
+
+ try {
+
+ if (!isCollection) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte [] buffer = new byte[1024];
+ try {
+ while (inputStream.available() > 0) {
+ int amount = inputStream.read(buffer, 0, 1024);
+ bos.write(buffer, 0, amount);
+ }
+ } catch (IOException e) {
+ // nothing here
}
- } catch (IOException e) {
- // nothing here
+ String content = new String(bos.toByteArray());
+ ret.setContent(content);
}
- String content = new String(bos.toByteArray());
- ret.setContent(content);
- }
- try {
+
registry.put(path, ret);
} catch (RegistryException e) {
throw new ResponseContextException(new StackTraceResponseContext(e));
@@ -1350,7 +1353,7 @@
Resource entryObj,
RequestContext request) throws ResponseContextException {
IRI mediaIri = new IRI(getAbsoluteBase(request) +
- RegistryResolver.RESOURCE + getMediaName(entryObj));
+ RegistryResolver.RESOURCE + getMediaName(entryObj));
String mediaLink = mediaIri.toString();
entry.setContent(mediaIri, getContentType(entryObj));
entry.addLink(mediaLink, "edit-media");
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RemoteResourceImpl.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RemoteResourceImpl.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RemoteResourceImpl.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/app/RemoteResourceImpl.java Tue Jul 22 21:53:48 2008
@@ -26,6 +26,7 @@
public class RemoteResourceImpl extends ResourceImpl {
URL contentURL;
String authorizationString;
+ InputStream contentStream;
public void setContentURL(URL contentURL) {
this.contentURL = contentURL;
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceDAO.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceDAO.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceDAO.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceDAO.java Tue Jul 22 21:53:48 2008
@@ -26,6 +26,7 @@
import org.wso2.registry.jdbc.utils.Transaction;
import org.wso2.registry.session.CurrentSession;
import org.wso2.registry.utils.AuthorizationUtils;
+import org.wso2.registry.utils.UUIDGenerator;
import java.io.*;
import java.sql.*;
@@ -438,7 +439,8 @@
deleteResource(resourceID);
if (contentID != null) {
- deleteContent(contentID);
+ // todo: add usage counter column to CONTENT table and delete content only if it is 0
+ //deleteContent(contentID);
}
}
@@ -744,7 +746,7 @@
resourceImpl.setLastUpdaterUserName(result.getString("LAST_UPDATOR"));
resourceImpl.setLastModified(result.getTimestamp("LAST_UPDATED_TIME"));
resourceImpl.setDescription(result.getString("DESCRIPTION"));
- resourceImpl.setContentID(result.getString("CONTENT_ID"));
+ resourceImpl.setDbBasedContentID(result.getString("CONTENT_ID"));
resourceImpl.setMatchingSnapshotID(result.getLong("ASSOCIATED_SNAPSHOT_ID"));
}
@@ -796,7 +798,7 @@
resourceImpl.setLastUpdaterUserName(result.getString("LAST_UPDATOR"));
resourceImpl.setLastModified(result.getTimestamp("LAST_UPDATED_TIME"));
resourceImpl.setDescription(result.getString("DESCRIPTION"));
- resourceImpl.setContentID(result.getString("CONTENT_ID"));
+ resourceImpl.setFileBasedContentID(result.getString("CONTENT_ID"));
resourceImpl.setMatchingSnapshotID(result.getLong("ASSOCIATED_SNAPSHOT_ID"));
}
@@ -812,6 +814,37 @@
}
}
+ public void createFileBasedContentFromDB(String contentID, Connection conn)
+ throws RegistryException {
+
+ if (FileManager.getInstance().lockFileBasedContent(contentID)) {
+ // file based content is already created for this content ID. Locked it so that it will
+ // not be automatically deleted.
+ return;
+ }
+
+ try {
+ String sql = "SELECT CONTENT_DATA FROM CONTENT WHERE CONTENT_ID=?";
+
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ps.setString(1, contentID);
+
+ ResultSet result = ps.executeQuery();
+
+ if (result.next()) {
+ FileManager.getInstance().
+ createFileBasedContent(contentID, result.getBinaryStream("CONTENT_DATA"));
+ }
+ ps.close();
+
+ } catch (SQLException e) {
+ String msg = "Faild to get resource content from the database. " + e.getMessage();
+ log.error(msg, e);
+ throw new RegistryException(msg, e);
+
+ }
+ }
+
/**
* Returns a input stream to fetch content of the resource. Input stream is disconnected from
* the database, so that it is possible to close the connection before reading the content
@@ -884,7 +917,8 @@
ps.setString(7, CurrentSession.getUser());
ps.setTimestamp(8, new Timestamp(now));
ps.setString(9, resourceImpl.getDescription());
- ps.setString(10, resourceImpl.getContentID());
+ ps.setString(10, resourceImpl.getFileBasedContentID() != null?
+ resourceImpl.getFileBasedContentID(): resourceImpl.getDbBasedContentID());
ps.setLong(11, -1); // this is a new resource. so there is no equivalent version.
// we are not aware of any snapshots created for this state of the resource. if a
@@ -923,7 +957,8 @@
ps.setString(2, CurrentSession.getUser());
ps.setTimestamp(3, new Timestamp(now));
ps.setString(4, resourceImpl.getDescription());
- ps.setString(5, resourceImpl.getContentID());
+ ps.setString(5, resourceImpl.getDbBasedContentID() != null?
+ resourceImpl.getDbBasedContentID(): resourceImpl.getFileBasedContentID());
ps.setString(6, resourceID);
ps.executeUpdate();
@@ -1096,53 +1131,75 @@
*/
private void addContent(ResourceImpl resourceImpl) throws RegistryException {
- Connection conn = Transaction.getConnection();
+ String currentContentID = resourceImpl.getDbBasedContentID() != null?
+ resourceImpl.getDbBasedContentID(): resourceImpl.getFileBasedContentID();
+ if (contentExists(currentContentID)) {
+ // content with the current content of the resource already exists in the database.
+ // since content IDs are unique, both contents are same and we don't have to add
+ // the content again.
+ return;
+ }
- String contentID = null;
+ Connection conn = Transaction.getConnection();
InputStream contentStream = resourceImpl.getContentStream();
if (contentStream != null) {
- InputStream tempFileStream = null;
try {
-
- contentID = UUID.randomUUID().toString();
-
- tempFileStream = FileManager.getInstance().
- createFileBasedInputStream(contentID, contentStream);
+ String contentID = currentContentID;
long contentLength = FileManager.getInstance().getContentLength(contentID);
String sql = "INSERT INTO CONTENT (CONTENT_ID, CONTENT_DATA) VALUES (?, ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, contentID);
- ps.setBinaryStream(2, tempFileStream, (int)contentLength);
+ ps.setBinaryStream(2, contentStream, (int)contentLength);
ps.executeUpdate();
ps.close();
- //} catch (FileNotFoundException e) {
- // String msg = "Failed to read resource content from the temporary file. " +
- // e.getMessage();
- // log.error(msg, e);
- // throw new RegistryException(msg, e);
-
} catch (SQLException e) {
String msg = "Failed to write resource content to the database. " + e.getMessage();
log.error(msg, e);
throw new RegistryException(msg, e);
} finally {
- if (tempFileStream != null) {
+ if (contentStream != null) {
try {
- tempFileStream.close();
+ contentStream.close();
} catch (IOException e) {
log.error(e);
}
}
}
}
+ }
+
+ private boolean contentExists(String contentID) throws RegistryException {
+
+ String sql = "SELECT CONTENT_ID FROM CONTENT WHERE CONTENT_ID=?";
+
+ Connection conn = Transaction.getConnection();
- resourceImpl.setContentID(contentID);
+ boolean contentExists = false;
+ try {
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ps.setString(1, contentID);
+
+ ResultSet result = ps.executeQuery();
+ if (result.next()) {
+ contentExists = true;
+ }
+ ps.close();
+
+ } catch (SQLException e) {
+
+ String msg = "Failed to check existance of content with ID " +
+ contentID + ". " + e.getMessage();
+ log.error(msg, e);
+ throw new RegistryException(msg, e);
+ }
+
+ return contentExists;
}
public void updateContent(String contentID, InputStream contentStream)
@@ -1152,32 +1209,6 @@
Connection conn = Transaction.getConnection();
- // we should first write the content stream to a temporary file because JDBC
- // needs the length of the binary stream.
-
- //File tempFile = null;
- //try {
- // tempFile = File.createTempFile("reg", "tmp");
- // OutputStream fileStream = new BufferedOutputStream(new FileOutputStream(tempFile));
- // InputStream bufferedIn = new BufferedInputStream(contentStream);
- //
- // byte[] dataChunk = new byte[1024];
- // int byteCount;
- // while ((byteCount = bufferedIn.read(dataChunk)) != -1) {
- // fileStream.write(dataChunk, 0, byteCount);
- // }
- // fileStream.flush();
- // bufferedIn.close();
- // fileStream.close();
- //
- //} catch (IOException e) {
- //
- // String msg = "Failed to write the resource content to a temporary file, " +
- // "before writing to the database. " + e.getMessage();
- // log.error(msg, e);
- // throw new RegistryException(msg, e);
- //}
-
try {
InputStream tempFileStream = FileManager.getInstance().
@@ -1192,11 +1223,6 @@
ps.executeUpdate();
ps.close();
- //} catch (FileNotFoundException e) {
- // String msg = "Failed to read resource content from the temporary file. " + e.getMessage();
- // log.error(msg, e);
- // throw new RegistryException(msg, e);
-
} catch (SQLException e) {
String msg = "Failed to write resource content to the database. " + e.getMessage();
@@ -1330,7 +1356,7 @@
public String addContent(InputStream contentStream) throws RegistryException {
- String contentID = UUID.randomUUID().toString();
+ String contentID = UUIDGenerator.generateUUID();
Connection conn = Transaction.getConnection();
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceVersionDAO.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceVersionDAO.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceVersionDAO.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/dao/ResourceVersionDAO.java Tue Jul 22 21:53:48 2008
@@ -740,7 +740,7 @@
resourceImpl.setLastUpdaterUserName(result.getString("LAST_UPDATOR"));
resourceImpl.setLastModified(result.getTimestamp("LAST_UPDATED_TIME"));
resourceImpl.setDescription(result.getString("DESCRIPTION"));
- resourceImpl.setContentID(result.getString("CONTENT_ID"));
+ resourceImpl.setDbBasedContentID(result.getString("CONTENT_ID"));
}
ps.close();
@@ -807,6 +807,37 @@
return contentID;
}
+ public void createFileBasedContentFromDB(String contentID, Connection conn)
+ throws RegistryException {
+
+ if (FileManager.getInstance().lockFileBasedContent(contentID)) {
+ // file based content is already created for this content ID. Locked it so that it will
+ // not be automatically deleted.
+ return;
+ }
+
+ try {
+ String sql = "SELECT CONTENT_DATA FROM CONTENT_VERSION WHERE CONTENT_VERSION_ID=?";
+
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ps.setString(1, contentID);
+
+ ResultSet result = ps.executeQuery();
+
+ if (result.next()) {
+ FileManager.getInstance().
+ createFileBasedContent(contentID, result.getBinaryStream("CONTENT_DATA"));
+ }
+ ps.close();
+
+ } catch (SQLException e) {
+ String msg = "Faild to get resource content from the database. " + e.getMessage();
+ log.error(msg, e);
+ throw new RegistryException(msg, e);
+
+ }
+ }
+
public InputStream getResourceContentStream(String contentID, Connection conn)
throws RegistryException {
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileData.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileData.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileData.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileData.java Tue Jul 22 21:53:48 2008
@@ -24,6 +24,12 @@
private long connections;
/**
+ * If set to true, this file data instance will be deleted eventually after connections
+ * become zero.
+ */
+ private boolean autoDelete;
+
+ /**
* Time at which the connections become 0.
*/
private long idleFrom;
@@ -57,6 +63,14 @@
}
}
+ public boolean isAutoDelete() {
+ return autoDelete;
+ }
+
+ public void setAutoDelete(boolean autoDelete) {
+ this.autoDelete = autoDelete;
+ }
+
public long getIdleFrom() {
return idleFrom;
}
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileManager.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileManager.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileManager.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/filecache/FileManager.java Tue Jul 22 21:53:48 2008
@@ -95,6 +95,79 @@
}
}
+ public boolean lockFileBasedContent(String contentID) {
+
+ FileData fileData = fileDataMap.get(contentID);
+ if (fileData != null) {
+ fileData.setAutoDelete(false);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void createFileBasedContent(String contentID, InputStream inputStream)
+ throws RegistryException {
+
+ w.lock();
+
+ try {
+
+ if (inputStream == null) {
+ String msg = "Could not create file based content for null input stream.";
+ log.error(msg);
+ throw new RegistryException(msg);
+ }
+
+ FileData fileData = fileDataMap.get(contentID);
+ if (fileData != null) {
+ File file = new File(fileData.getFilename());
+ if (!file.exists())
+ fileData = null;
+ }
+
+ if (fileData == null) {
+ BufferedOutputStream out = null;
+ try {
+ File tempFile = File.createTempFile("reg", ".bin");
+ tempFile.deleteOnExit();
+
+ out = new BufferedOutputStream(new FileOutputStream(tempFile));
+ byte[] contentChunk = new byte[1024];
+ int byteCount;
+ while ((byteCount = inputStream.read(contentChunk)) != -1) {
+ out.write(contentChunk, 0, byteCount);
+ }
+ out.flush();
+
+ // we increment the file data only after obtaining the input stream from file
+ fileData = new FileData(tempFile.getPath());
+ fileData.setAutoDelete(false); // turn off auto delete
+ fileDataMap.put(contentID, fileData);
+
+ } catch (IOException e) {
+ String msg = "Failed to write data to temporary file." + e.getMessage();
+ log.error(msg, e);
+ throw new RegistryException(msg, e);
+ } finally {
+ try {
+ inputStream.close();
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ String msg = "Failed to close streams used for temporary file. "
+ + e.getMessage();
+ log.error(msg, e);
+ }
+ }
+ }
+
+ } finally {
+ w.unlock();
+ }
+ }
+
public InputStream createFileBasedInputStream(String contentID,
InputStream inputStream)
throws RegistryException {
@@ -219,7 +292,7 @@
String contentID = contentIDs.next();
FileData fileData = fileDataMap.get(contentID);
- if (fileData.getConnections() <= 0 &&
+ if (fileData.isAutoDelete() && fileData.getConnections() <= 0 &&
(currentTime - fileData.getIdleFrom()) >= MAXIMUM_IDLE_TIME_FOR_FILE) {
// We can't call deleteFileBasedContent(...) here, since it will remove the
@@ -260,6 +333,13 @@
}
}
+ public void activateAutoDelete(String contentID) {
+ FileData fileData = fileDataMap.get(contentID);
+ if (fileData != null) {
+ fileData.setAutoDelete(true);
+ }
+ }
+
private File getFile(String contentID) {
r.lock();
Modified: trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/indexing/Indexer.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/indexing/Indexer.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/indexing/Indexer.java (original)
+++ trunk/registry/modules/core/src/main/java/org/wso2/registry/jdbc/indexing/Indexer.java Tue Jul 22 21:53:48 2008
@@ -66,7 +66,7 @@
// }
// is.close();
byte [] content = (byte [])resource.getContent();
- resource.setContentStream(null);
+ //resource.setContentStream(null);
Document document = new Document();
document.add(new Field("id", id, Field.Store.YES, Field.Index.TOKENIZED));
Modified: trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/CopyTest.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/CopyTest.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/CopyTest.java (original)
+++ trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/CopyTest.java Tue Jul 22 21:53:48 2008
@@ -54,6 +54,10 @@
Resource oldR1 = registry.get("/test/copy/c1/copy1");
assertEquals("Original resource should have a property named 'test' with value 'copy'.",
oldR1.getProperty("test"), "copy");
+
+ String newContent = new String((byte[]) newR1.getContent());
+ String oldContent = new String((byte[]) oldR1.getContent());
+ assertEquals("Contents are not equal in copied resources", newContent, oldContent);
}
public void testCollectionCopy() throws RegistryException {
Modified: trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/JDBCRegistryTest.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/JDBCRegistryTest.java?rev=19876&r1=19875&r2=19876&view=diff
==============================================================================
--- trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/JDBCRegistryTest.java (original)
+++ trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/JDBCRegistryTest.java Tue Jul 22 21:53:48 2008
@@ -67,6 +67,8 @@
r1.setDescription("This is test discription");
r1.addProperty("p1", "value1");
registry.put("/c1/c2/c3", r1);
+ r1.discard();
+
r1 = registry.get("/c1/c2/c3/c4/r1");
InputStream inContent = r1.getContentStream();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
@@ -76,6 +78,7 @@
}
inContent.close();
assertEquals(str, new String(outStream.toByteArray()));
+ r1.discard();
}
public void testFlatResourceHandling() throws RegistryException {
@@ -105,6 +108,9 @@
}
assertTrue("Deleted resource /r1 is returned on get.", failed);
+
+ r1.discard();
+ r1f.discard();
}
public void testHierarchicalResourceHandling() throws Exception {
Added: trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/ResourcesTest.java
URL: http://wso2.org/svn/browse/wso2/trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/ResourcesTest.java?pathrev=19876
==============================================================================
--- (empty file)
+++ trunk/registry/modules/core/src/test/java/org/wso2/registry/jdbc/ResourcesTest.java Tue Jul 22 21:53:48 2008
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2006, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wso2.registry.jdbc;
+
+import junit.framework.TestCase;
+import org.wso2.registry.Registry;
+import org.wso2.registry.RegistryConstants;
+import org.wso2.registry.Resource;
+import org.wso2.registry.exceptions.RegistryException;
+import org.wso2.registry.jdbc.realm.RegistryRealm;
+
+public class ResourcesTest extends TestCase {
+
+ protected static EmbeddedRegistry embeddedRegistry = null;
+ protected static Registry registry = null;
+ protected static RegistryRealm realm = null;
+
+ public void setUp() throws RegistryException {
+ if (registry == null) {
+ embeddedRegistry = new InMemoryEmbeddedRegistry();
+ registry = embeddedRegistry.getUserRegistry(
+ RegistryConstants.ADMIN_USER, RegistryConstants.ADMIN_PASSWORD);
+ }
+ }
+
+ public void testResourceUpdate() throws RegistryException {
+
+ String r1Path = "/rtest/r1";
+ Resource r1 = registry.newResource();
+ r1.setContent("c1");
+ r1.setProperty("p1", "v1");
+ registry.put(r1Path, r1);
+
+ Resource r1e1 = registry.get(r1Path);
+ r1e1.setProperty("p1", "v2");
+ registry.put(r1Path, r1e1);
+
+ Resource r1e2 = registry.get(r1Path);
+ assertNotNull("r1 content should not be null", r1e2.getContent());
+
+ String r1e2Content = new String((byte[]) r1e2.getContent());
+ assertEquals("r1 content should be c1", r1e2Content, "c1");
+ }
+}
More information about the Registry-dev
mailing list