import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import javax.xml.namespace.QName;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
import org.argeo.cms.acr.ContentUtils;
import org.argeo.util.FsUtils;
+/** Content persisted as a filesystem {@link Path}. */
public class FsContent extends AbstractContent implements ProvidedContent {
final static String USER_ = "user:";
private static final Map<QName, String> POSIX_KEYS;
static {
BASIC_KEYS = new HashMap<>();
- BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
- BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
- BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
- BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+ BASIC_KEYS.put(CrName.creationTime.qName(), "basic:creationTime");
+ BASIC_KEYS.put(CrName.lastModifiedTime.qName(), "basic:lastModifiedTime");
+ BASIC_KEYS.put(CrName.size.qName(), "basic:size");
+ BASIC_KEYS.put(CrName.fileKey.qName(), "basic:fileKey");
POSIX_KEYS = new HashMap<>(BASIC_KEYS);
- POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
- POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
- POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
+ POSIX_KEYS.put(CrName.owner.qName(), "owner:owner");
+ POSIX_KEYS.put(CrName.group.qName(), "posix:group");
+ POSIX_KEYS.put(CrName.permissions.qName(), "posix:permissions");
}
- private final ProvidedSession session;
private final FsContentProvider provider;
private final Path path;
- private final boolean isRoot;
+ private final boolean isMountBase;
private final QName name;
protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.path = path;
- this.isRoot = contentProvider.isRoot(path);
+ this.isMountBase = contentProvider.isMountBase(path);
// TODO check file names with ':' ?
- if (isRoot) {
+ if (isMountBase) {
String mountPath = provider.getMountPath();
if (mountPath != null && !mountPath.equals("/")) {
Content mountPoint = session.getMountPoint(mountPath);
this.name = mountPoint.getName();
} else {
- this.name = CrName.ROOT.get();
+ this.name = CrName.root.qName();
}
} else {
- QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+
+ // TODO should we support prefixed name for known types?
+ // QName providerName = NamespaceUtils.parsePrefixedName(provider,
+ // path.getFileName().toString());
+ QName providerName = new QName(path.getFileName().toString());
+ // TODO remove extension if mounted?
this.name = new ContentName(providerName, session);
}
}
* ATTRIBUTES
*/
+ @SuppressWarnings("unchecked")
@Override
public <A> Optional<A> get(QName key, Class<A> clss) {
Object value;
try {
for (String name : udfav.list()) {
QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
- QName sessionName = new ContentName(providerName, session);
+ QName sessionName = new ContentName(providerName, getSession());
result.add(sessionName);
}
} catch (IOException e) {
UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
try {
- int size = udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
+ udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
} catch (IOException e) {
throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
}
try {
return Files.list(path).map((p) -> {
FsContent fsContent = new FsContent(this, p);
- Optional<String> isMount = fsContent.get(CrName.MOUNT.get(), String.class);
+ Optional<String> isMount = fsContent.get(CrName.mount.qName(), String.class);
if (isMount.orElse("false").equals("true")) {
QName[] classes = null;
- ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent,
- false, classes);
- Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
+ ContentProvider contentProvider = getSession().getRepository()
+ .getMountContentProvider(fsContent, false, classes);
+ Content mountedContent = contentProvider.get(getSession(), "");
return mountedContent;
} else {
return (Content) fsContent;
FsContent fsContent;
try {
Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
- if (ContentName.contains(classes, CrName.COLLECTION.get()))
+ if (ContentName.contains(classes, CrName.collection.qName()))
Files.createDirectory(newPath);
else
Files.createFile(newPath);
throw new ContentResourceException("Cannot create new content", e);
}
- if (session.getRepository().shouldMount(classes)) {
- ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent, true, classes);
- Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
- fsContent.put(CrName.MOUNT.get(), "true");
+ if (getSession().getRepository().shouldMount(classes)) {
+ ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
+ classes);
+ Content mountedContent = contentProvider.get(getSession(), "");
+ fsContent.put(CrName.mount.qName(), "true");
return mountedContent;
} else {
@Override
public Content getParent() {
- if (isRoot) {
+ if (isMountBase) {
String mountPath = provider.getMountPath();
if (mountPath == null || mountPath.equals("/"))
return null;
String[] parent = ContentUtils.getParentPath(mountPath);
- return session.get(parent[0]);
+ return getSession().get(parent[0]);
}
return new FsContent(this, path.getParent());
}
+ @SuppressWarnings("unchecked")
@Override
public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
if (InputStream.class.isAssignableFrom(clss)) {
}
/*
- * ACCESSORS
+ * TYPING
*/
+
@Override
- public ProvidedSession getSession() {
- return session;
+ public List<QName> getContentClasses() {
+ List<QName> res = new ArrayList<>();
+ if (Files.isDirectory(path))
+ res.add(CrName.collection.qName());
+ // TODO add other types
+ return res;
}
+ /*
+ * ACCESSORS
+ */
+
@Override
public FsContentProvider getProvider() {
return provider;
}
+ /*
+ * READ / WRITE
+ */
+ @SuppressWarnings("unchecked")
+ public <A> CompletableFuture<A> write(Class<A> clss) {
+ if (isContentClass(CrName.collection.qName())) {
+ throw new IllegalStateException("Cannot directly write to a collection");
+ }
+ if (InputStream.class.isAssignableFrom(clss)) {
+ CompletableFuture<InputStream> res = new CompletableFuture<>();
+ res.thenAccept((in) -> {
+ try {
+ Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ CompletableFuture<Source> res = new CompletableFuture<Source>();
+ res.thenAccept((source) -> {
+// Path targetPath = path.getParent().resolve(path.getFileName()+".xml");
+ Path targetPath = path;
+ try (OutputStream out = Files.newOutputStream(targetPath)) {
+ StreamResult result = new StreamResult(out);
+ TransformerFactory.newDefaultInstance().newTransformer().transform(source, result);
+ } catch (IOException | TransformerException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ } else {
+ return super.write(clss);
+ }
+ }
}