]> git.argeo.org Git - lgpl/argeo-commons.git/blob - cms/acr/fs/FsContent.java
Prepare next development cycle
[lgpl/argeo-commons.git] / cms / acr / fs / FsContent.java
1 package org.argeo.cms.acr.fs;
2
3 import java.io.IOException;
4 import java.nio.ByteBuffer;
5 import java.nio.charset.StandardCharsets;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.attribute.FileTime;
9 import java.nio.file.attribute.UserDefinedFileAttributeView;
10 import java.time.Instant;
11 import java.util.Collections;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.Iterator;
15 import java.util.Map;
16 import java.util.Optional;
17 import java.util.Set;
18
19 import javax.xml.namespace.QName;
20
21 import org.argeo.api.acr.Content;
22 import org.argeo.api.acr.ContentName;
23 import org.argeo.api.acr.ContentResourceException;
24 import org.argeo.api.acr.CrName;
25 import org.argeo.api.acr.NamespaceUtils;
26 import org.argeo.api.acr.spi.AbstractContent;
27 import org.argeo.api.acr.spi.ProvidedContent;
28 import org.argeo.api.acr.spi.ProvidedSession;
29 import org.argeo.util.FsUtils;
30
31 public class FsContent extends AbstractContent implements ProvidedContent {
32 final static String USER_ = "user:";
33
34 private static final Map<QName, String> BASIC_KEYS;
35 private static final Map<QName, String> POSIX_KEYS;
36 static {
37 BASIC_KEYS = new HashMap<>();
38 BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
39 BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
40 BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
41 BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
42
43 POSIX_KEYS = new HashMap<>(BASIC_KEYS);
44 POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
45 POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
46 POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
47 }
48
49 private final ProvidedSession session;
50 private final FsContentProvider provider;
51 private final Path path;
52 private final boolean isRoot;
53 private final QName name;
54
55 protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
56 this.session = session;
57 this.provider = contentProvider;
58 this.path = path;
59 this.isRoot = contentProvider.isRoot(path);
60 // TODO check file names with ':' ?
61 if (isRoot)
62 this.name = CrName.ROOT.get();
63 else {
64 QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
65 this.name = new ContentName(providerName, session);
66 }
67 }
68
69 protected FsContent(FsContent context, Path path) {
70 this(context.getSession(), context.getProvider(), path);
71 }
72
73 private boolean isPosix() {
74 return path.getFileSystem().supportedFileAttributeViews().contains("posix");
75 }
76
77 @Override
78 public QName getName() {
79 return name;
80 }
81
82 /*
83 * ATTRIBUTES
84 */
85
86 @Override
87 public <A> Optional<A> get(QName key, Class<A> clss) {
88 Object value;
89 try {
90 // We need to add user: when accessing via Files#getAttribute
91 value = Files.getAttribute(path, toFsAttributeKey(key));
92 } catch (IOException e) {
93 throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
94 }
95 A res = null;
96 if (value instanceof FileTime) {
97 if (clss.isAssignableFrom(FileTime.class))
98 res = (A) value;
99 Instant instant = ((FileTime) value).toInstant();
100 if (Object.class.isAssignableFrom(clss)) {// plain object requested
101 res = (A) instant;
102 }
103 // TODO perform trivial file conversion to other formats
104 }
105 if (value instanceof byte[]) {
106 res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
107 }
108 if (res == null)
109 try {
110 res = (A) value;
111 } catch (ClassCastException e) {
112 return Optional.empty();
113 }
114 return Optional.of(res);
115 }
116
117 @Override
118 protected Iterable<QName> keys() {
119 Set<QName> result = new HashSet<>(isPosix() ? POSIX_KEYS.keySet() : BASIC_KEYS.keySet());
120 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
121 if (udfav != null) {
122 try {
123 for (String name : udfav.list()) {
124 QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
125 QName sessionName = new ContentName(providerName, session);
126 result.add(sessionName);
127 }
128 } catch (IOException e) {
129 throw new ContentResourceException("Cannot list attributes for " + path, e);
130 }
131 }
132 return result;
133 }
134
135 @Override
136 protected void removeAttr(QName key) {
137 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
138 try {
139 udfav.delete(NamespaceUtils.toPrefixedName(provider, key));
140 } catch (IOException e) {
141 throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
142 }
143 }
144
145 @Override
146 public Object put(QName key, Object value) {
147 Object previous = get(key);
148 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
149 ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
150 try {
151 int size = udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
152 } catch (IOException e) {
153 throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
154 }
155 return previous;
156 }
157
158 protected String toFsAttributeKey(QName key) {
159 if (POSIX_KEYS.containsKey(key))
160 return POSIX_KEYS.get(key);
161 else
162 return USER_ + NamespaceUtils.toPrefixedName(provider, key);
163 }
164
165 /*
166 * CONTENT OPERATIONS
167 */
168 @Override
169 public Iterator<Content> iterator() {
170 if (Files.isDirectory(path)) {
171 try {
172 return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
173 } catch (IOException e) {
174 throw new ContentResourceException("Cannot list " + path, e);
175 }
176 } else {
177 return Collections.emptyIterator();
178 }
179 }
180
181 @Override
182 public Content add(QName name, QName... classes) {
183 try {
184 Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
185 if (ContentName.contains(classes, CrName.COLLECTION.get()))
186 Files.createDirectory(newPath);
187 else
188 Files.createFile(newPath);
189
190 // for(ContentClass clss:classes) {
191 // Files.setAttribute(newPath, name, newPath, null)
192 // }
193 return new FsContent(this, newPath);
194 } catch (IOException e) {
195 throw new ContentResourceException("Cannot create new content", e);
196 }
197 }
198
199 @Override
200 public void remove() {
201 FsUtils.delete(path);
202 }
203
204 @Override
205 public Content getParent() {
206 if (isRoot)
207 return null;// TODO deal with mounts
208 return new FsContent(this, path.getParent());
209 }
210
211 /*
212 * ACCESSORS
213 */
214 @Override
215 public ProvidedSession getSession() {
216 return session;
217 }
218
219 @Override
220 public FsContentProvider getProvider() {
221 return provider;
222 }
223
224 }