/* * Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you 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.carbon.registry.webdav; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavCompliance; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceFactory; import org.apache.jackrabbit.webdav.DavResourceIterator; import org.apache.jackrabbit.webdav.DavResourceIteratorImpl; import org.apache.jackrabbit.webdav.DavResourceLocator; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.io.InputContext; import org.apache.jackrabbit.webdav.io.OutputContext; import org.apache.jackrabbit.webdav.lock.ActiveLock; import org.apache.jackrabbit.webdav.lock.LockInfo; import org.apache.jackrabbit.webdav.lock.LockManager; import org.apache.jackrabbit.webdav.lock.Scope; import org.apache.jackrabbit.webdav.lock.Type; import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; import org.apache.jackrabbit.webdav.property.DavPropertySet; import org.apache.jackrabbit.webdav.property.DefaultDavProperty; import org.apache.jackrabbit.webdav.property.ResourceType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.wso2.carbon.registry.core.Collection; import org.wso2.carbon.registry.core.CollectionImpl; import org.wso2.carbon.registry.core.Registry; import org.wso2.carbon.registry.core.Resource; import org.wso2.carbon.registry.core.ResourceImpl; import org.wso2.carbon.registry.core.exceptions.RegistryException; public class RegistryResource implements DavResource { private Resource underLineResource; private Registry registry; private RegistryWebDavContext resourceCache; private DavResourceLocator locator; private String path; private boolean doesNotExists = false; private Map<DavPropertyName,DavProperty> properties = new HashMap<DavPropertyName,DavProperty>(); private boolean lockable = false; private LockManager lockManager; private DavSession session; private static final String COMPLIANCE_CLASSES = DavCompliance.concatComplianceClasses( new String[] {DavCompliance._1_}); public RegistryResource(RegistryWebDavContext webdavContext, DavResourceLocator locator) throws DavException { this.registry = webdavContext.getRegistry(); if(registry == null){ throw new DavException(DavServletResponse.SC_FORBIDDEN, "Registry Not Found"); } this.locator = locator; this.resourceCache = webdavContext; String path = locator.getResourcePath(); if(path.startsWith("/registry/resourcewebdav")){ path = path.substring("/registry/resourcewebdav".length()); } if(path.trim().length() == 0){ path = "/"; } this.path = path.trim(); //this.session = session; } private Resource getUnderlineResource(){ try { this.underLineResource = registry.get(path); // adding the properties requested from the webDAV client addRequiredProperties(); return underLineResource; } catch (RegistryException e) { doesNotExists = true; throw new RuntimeException(e); } } // add the properties required by the webDAV client private void addRequiredProperties() { if (underLineResource instanceof Collection) { addDavProperty(DavPropertyName.RESOURCETYPE, new ResourceType(ResourceType.COLLECTION)); // Windows XP support addDavProperty(DavPropertyName.ISCOLLECTION, "1"); } else { addDavProperty(DavPropertyName.RESOURCETYPE, new ResourceType(ResourceType.DEFAULT_RESOURCE)); // Windows XP support addDavProperty(DavPropertyName.ISCOLLECTION, "0"); addDavProperty(DavPropertyName.GETCONTENTLENGTH, getContentLength()); addDavProperty(DavPropertyName.GETCONTENTTYPE, underLineResource.getMediaType()); } addDavProperty(DavPropertyName.create("author"), underLineResource.getAuthorUserName()); addDavProperty(DavPropertyName.GETETAG, getETag()); addDavProperty(DavPropertyName.DISPLAYNAME, getDisplayName()); addDavProperty(DavPropertyName.CREATIONDATE, DavConstants.creationDateFormat.format( underLineResource.getCreatedTime())); addDavProperty(DavPropertyName.GETLASTMODIFIED, DavConstants.modificationDateFormat.format( underLineResource.getLastModified())); } //TODO: fix ETag // the proper ETag implementation should be applied in the future. private String getETag() { return path; } // returns the size -the number of bytes - of the content private int getContentLength() { int bytesRead = 0; try { Object o = this.underLineResource.getContent(); if (null != o && o instanceof byte[]) { bytesRead = ((byte[]) o).length; } } catch (RegistryException e) { bytesRead = 0; } return bytesRead; } // creates and adds a dav property private void addDavProperty(DavPropertyName dName, Object value) { DavProperty<Object> davProp = new DefaultDavProperty<Object>(dName, value); properties.put(davProp.getName(), davProp); } public void addMember(DavResource resource, InputContext inputContext) throws DavException { // if (isLocked(this) || isLocked(member)) { // throw new DavException(DavServletResponse.SC_LOCKED); // } try { if (resource instanceof RegistryResource) { if (getUnderlineResource() instanceof Collection) { if (resource.getResourcePath().contains(path)) { Resource resourceImpl = ((RegistryResource) resource).getUnderLineResource(); boolean isCollection = resourceImpl instanceof Collection; // 'resourceImpl == null' indicates it's a new non-collection resource created by the client // @see: org.wso2.carbon.registry.webdav.RegistryServlet.doMkCol() if (null == resourceImpl) { resourceImpl = isCollection ? new CollectionImpl() : new ResourceImpl(); //setting path and underline resource only for newly created resources ((ResourceImpl) resourceImpl).setPath(resource.getResourcePath()); ((RegistryResource) resource).setUnderLineResource(resourceImpl); //if (!isCollection) { resourceCache.updateDavResourceMimeType((RegistryResource) resource); // setting the media type to text/plain as the default for newly created resources with // no extensions. if( null == resourceImpl.getMediaType() && resourceImpl.getPath().indexOf('.') == -1) { resourceImpl.setMediaType("text/plain"); } //} } if (!isCollection) { resourceImpl.setContentStream(inputContext.getInputStream()); } resourceCache.getRegistry().put(resource.getResourcePath(), resourceImpl); } else { throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Internal Error, Parent and target path does not match"); } } else { throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Only add resources to Collections"); } } else { throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Only support " + RegistryResource.class + " as members"); } } catch (RegistryException e) { e.printStackTrace(); throw new DavException(DavServletResponse.SC_BAD_REQUEST,e); } } public boolean exists() { if(doesNotExists){ return false; }else{ try{ getUnderlineResource(); return true; } catch (RuntimeException e) { return false; } } } public boolean isCollection() { return exists() && getUnderlineResource() instanceof Collection; } public DavResource getCollection() { try { int index = path.lastIndexOf("/"); if(index >= 0){ String parentPath = path.substring(0,index+1); return resourceCache.getRegistryResource(parentPath); }else{ throw new RuntimeException("Illegal Path "+ path); } } catch (DavException e) { throw new RuntimeException(e); } } public String getComplianceClass() { return COMPLIANCE_CLASSES; } public String getDisplayName() { String fullpath = path; if(fullpath.equals("/")){ return "/"; } String[] tokens = fullpath.split("/"); return tokens[tokens.length-1]+"_"; } // We do not do lock, at least not for now. public ActiveLock getLock(Type type, Scope scope) { if(lockable){ ActiveLock lock = null; if (exists() && Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope)) { lock = lockManager.getLock(type, scope, this); } return lock; }else{ throw new UnsupportedOperationException(); } } public ActiveLock[] getLocks() { if(lockable){ ActiveLock writeLock = getLock(Type.WRITE, Scope.EXCLUSIVE); return (writeLock != null) ? new ActiveLock[]{writeLock} : new ActiveLock[0]; }else{ throw new UnsupportedOperationException(); } } public boolean hasLock(Type type, Scope scope) { if(lockable){ return getLock(type, scope) != null; }else{ throw new UnsupportedOperationException(); } } public boolean isLockable(Type type, Scope scope) { return lockable; } public void addLockManager(LockManager lockmgr) { this.lockManager = lockmgr; } public void unlock(String lockToken) throws DavException { if(lockable){ ActiveLock lock = getLock(Type.WRITE, Scope.EXCLUSIVE); if (lock == null) { throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); } else if (lock.isLockedByToken(lockToken)) { lockManager.releaseLock(lockToken, this); } else { throw new DavException(DavServletResponse.SC_LOCKED); } }else{ throw new UnsupportedOperationException(); } } public ActiveLock lock(LockInfo lockInfo) throws DavException { if(lockable){ if (isLockable(lockInfo.getType(), lockInfo.getScope())) { return lockManager.createLock(lockInfo, this); } else { throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Unsupported lock type or scope."); } }else{ throw new UnsupportedOperationException(); } } public ActiveLock refreshLock(LockInfo lockInfo, String lockToken) throws DavException{ if(lockable){ if (!exists()) { throw new DavException(DavServletResponse.SC_NOT_FOUND); } ActiveLock lock = getLock(lockInfo.getType(), lockInfo.getScope()); if (lock == null) { throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "No lock with the given type/scope present on resource " + getResourcePath()); } lock = lockManager.refreshLock(lockInfo, lockToken, this); /* since lock has infinite lock (simple) or undefined timeout (jcr) return the lock as retrieved from getLock. */ return lock; }else{ throw new UnsupportedOperationException(); } } public void removeMember(DavResource member) throws DavException { try { String path = member.getResourcePath(); resourceCache.saveDavResourceMimeType((RegistryResource) member); resourceCache.removeRegistryResource(path); registry.delete(path); } catch (RegistryException e) { throw new DavException(DavServletResponse.SC_BAD_REQUEST); } } public void removeProperty(DavPropertyName propertyName) throws DavException { getUnderlineResource().removeProperty(propertyName.getName()); } public void setProperty(DavProperty property) throws DavException { getUnderlineResource().setProperty(property.getName().getName(), (String) property.getValue()); } public String getResourcePath() { return path; } public void move(DavResource destination) throws DavException { try { registry.move(path, destination .getResourcePath()); resourceCache.saveDavResourceMimeType(this); } catch (RegistryException e) { throw new DavException(DavServletResponse.SC_BAD_REQUEST); } } public void copy(DavResource destination, boolean shallow) throws DavException { try { if(shallow){ throw new DavException(DavServletResponse.SC_BAD_REQUEST,"Shallow copy/move not supported"); } registry.copy(path, destination .getResourcePath()); } catch (RegistryException e) { throw new DavException(DavServletResponse.SC_BAD_REQUEST,e); } } public MultiStatusResponse alterProperties(List changeList) throws DavException { if (!exists()) { throw new DavException(DavServletResponse.SC_NOT_FOUND); } MultiStatusResponse msr = new MultiStatusResponse(getHref(), null); Iterator it = changeList.iterator(); while(it.hasNext()){ DavProperty property = (DavProperty)it.next(); try{ getUnderlineResource().setProperty(property.getName().getName(), (String)property.getValue()); msr.add(property, DavServletResponse.SC_OK); }catch (Exception e) { e.printStackTrace(); msr.add(property, DavServletResponse.SC_BAD_REQUEST); } } return msr; } public MultiStatusResponse alterProperties(DavPropertySet setProperties, DavPropertyNameSet removePropertyNames) throws DavException { if (!exists()) { throw new DavException(DavServletResponse.SC_NOT_FOUND); } MultiStatusResponse msr = new MultiStatusResponse(getHref(), null); Iterator it = setProperties.iterator(); while(it.hasNext()){ DavProperty property = (DavProperty)it.next(); try{ getUnderlineResource().setProperty(property.getName().getName(), (String)property.getValue()); msr.add(property, DavServletResponse.SC_OK); }catch (Exception e) { e.printStackTrace(); msr.add(property, DavServletResponse.SC_BAD_REQUEST); } } it = setProperties.iterator(); while(it.hasNext()){ DavProperty property = (DavProperty)it.next(); try{ getUnderlineResource().setProperty(property.getName().getName(), (String)property.getValue()); msr.add(property, DavServletResponse.SC_OK); }catch (Exception e) { e.printStackTrace(); msr.add(property, DavServletResponse.SC_BAD_REQUEST); } } return msr; } public DavResourceFactory getFactory() { return resourceCache.getEnviorment().getResourceFactory(); } // white spaces are replaced by '%20' to avoid href space issue with resource names. public String getHref() { return new StringBuffer(resourceCache.getContextPath()).append("/registry/resourcewebdav").append(path).toString().replace(" ","%20"); } public DavResourceLocator getLocator() { return locator; } public DavResourceIterator getMembers() { try { String[] childrenNames = ((CollectionImpl)getUnderlineResource()).getChildren(); List childrenList = new ArrayList(); for(String name:childrenNames){ RegistryResource registryResource = resourceCache.getRegistryResource(name); if(!"true".equals(registryResource.getUnderlineResource().getProperty("registry.link"))) { childrenList.add(registryResource); } } return new DavResourceIteratorImpl(childrenList); } catch (RegistryException e) { throw new RuntimeException(e); } catch (DavException e) { throw new RuntimeException(e); } } public long getModificationTime() { return getUnderlineResource().getLastModified().getTime(); } public DavPropertySet getProperties() { final Properties properties = getUnderlineResource().getProperties(); DavPropertySet davproperties = new DavPropertySet(); Iterator it = properties.keySet().iterator(); while(it.hasNext()){ final Object key = it.next(); davproperties.add(new DavProperty() { public Element toXml(Document document) { return null; } public boolean isInvisibleInAllprop() { return false; } public Object getValue() { return properties.get(key); } public DavPropertyName getName() { return DavPropertyName.create((String)key); } }); } for (DavProperty p : this.properties.values()) { davproperties.add(p); } return davproperties; } public DavProperty getProperty(final DavPropertyName name) { DavProperty property = properties.get(name); if(property != null){ return property; }else{ return new DavProperty() { public Element toXml(Document document) { return null; } public boolean isInvisibleInAllprop() { return false; } public Object getValue() { return getUnderlineResource().getProperty(name.getName()); } public DavPropertyName getName() { return name; } }; } } public DavPropertyName[] getPropertyNames() { List<DavPropertyName> list = new ArrayList<DavPropertyName>(); Iterator it = getUnderlineResource().getProperties().keySet().iterator(); while(it.hasNext()){ list.add(DavPropertyName.create((String)it.next())); } Iterator it2 = properties.keySet().iterator(); while(it2.hasNext()){ list.add(((DavPropertyName)it2.next())); } return list.toArray(new DavPropertyName[0]); } public DavSession getSession() { return resourceCache.getSession(); } public String getSupportedMethods() { return "OPTIONS, GET, HEAD, POST, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY,PUT, DELETE, MOVE, LOCK, UNLOCK, BIND, REBIND, UNBIND"; } public void spool(OutputContext outputContext) throws IOException { try { if (exists()) { if (!isCollection()) { final InputStream in = getUnderLineResource().getContentStream(); final OutputStream out = outputContext.getOutputStream(); if (null != out) { byte[] buf = new byte[1024]; int read; while ((read = in.read(buf)) >= 0) { out.write(buf, 0, read); } } } } else { throw new IOException("Resource " + path + " does not exists"); } } catch (RegistryException e) { throw new IOException("Error writing resource " + path + ":" + e.getMessage()); } } public Resource getUnderLineResource() { return underLineResource; } public void setUnderLineResource(Resource underLineResource) { this.underLineResource = underLineResource; } /** * Return true if this resource cannot be modified due to a write lock * that is not owned by the given session. * * @return true if this resource cannot be modified due to a write lock */ private boolean isLocked(DavResource res) { ActiveLock lock = res.getLock(Type.WRITE, Scope.EXCLUSIVE); if (lock == null) { return false; } else { String[] sLockTokens = session.getLockTokens(); for (int i = 0; i < sLockTokens.length; i++) { if (sLockTokens[i].equals(lock.getToken())) { return false; } } return true; } } }