]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
Mini desktop graalvm packaging.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / fs / FsContent.java
1 package org.argeo.cms.acr.fs;
2
3 import java.io.Closeable;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.OutputStream;
7 import java.nio.ByteBuffer;
8 import java.nio.charset.StandardCharsets;
9 import java.nio.file.Files;
10 import java.nio.file.Path;
11 import java.nio.file.attribute.FileTime;
12 import java.nio.file.attribute.UserDefinedFileAttributeView;
13 import java.time.Instant;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.Map;
19 import java.util.Optional;
20 import java.util.Set;
21
22 import javax.xml.namespace.QName;
23
24 import org.argeo.api.acr.Content;
25 import org.argeo.api.acr.ContentName;
26 import org.argeo.api.acr.ContentResourceException;
27 import org.argeo.api.acr.CrName;
28 import org.argeo.api.acr.NamespaceUtils;
29 import org.argeo.api.acr.spi.ContentProvider;
30 import org.argeo.api.acr.spi.ProvidedContent;
31 import org.argeo.api.acr.spi.ProvidedSession;
32 import org.argeo.cms.acr.AbstractContent;
33 import org.argeo.cms.acr.ContentUtils;
34 import org.argeo.util.FsUtils;
35
36 /** Content persisted as a filesystem {@link Path}. */
37 public class FsContent extends AbstractContent implements ProvidedContent {
38 final static String USER_ = "user:";
39
40 private static final Map<QName, String> BASIC_KEYS;
41 private static final Map<QName, String> POSIX_KEYS;
42 static {
43 BASIC_KEYS = new HashMap<>();
44 BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
45 BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
46 BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
47 BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
48
49 POSIX_KEYS = new HashMap<>(BASIC_KEYS);
50 POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
51 POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
52 POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
53 }
54
55 private final ProvidedSession session;
56 private final FsContentProvider provider;
57 private final Path path;
58 private final boolean isRoot;
59 private final QName name;
60
61 protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
62 this.session = session;
63 this.provider = contentProvider;
64 this.path = path;
65 this.isRoot = contentProvider.isMountRoot(path);
66 // TODO check file names with ':' ?
67 if (isRoot) {
68 String mountPath = provider.getMountPath();
69 if (mountPath != null && !mountPath.equals("/")) {
70 Content mountPoint = session.getMountPoint(mountPath);
71 this.name = mountPoint.getName();
72 } else {
73 this.name = CrName.ROOT.get();
74 }
75 } else {
76
77 // TODO should we support prefixed name for known types?
78 // QName providerName = NamespaceUtils.parsePrefixedName(provider,
79 // path.getFileName().toString());
80 QName providerName = new QName(path.getFileName().toString());
81 // TODO remove extension if mounted?
82 this.name = new ContentName(providerName, session);
83 }
84 }
85
86 protected FsContent(FsContent context, Path path) {
87 this(context.getSession(), context.getProvider(), path);
88 }
89
90 private boolean isPosix() {
91 return path.getFileSystem().supportedFileAttributeViews().contains("posix");
92 }
93
94 @Override
95 public QName getName() {
96 return name;
97 }
98
99 /*
100 * ATTRIBUTES
101 */
102
103 @SuppressWarnings("unchecked")
104 @Override
105 public <A> Optional<A> get(QName key, Class<A> clss) {
106 Object value;
107 try {
108 // We need to add user: when accessing via Files#getAttribute
109
110 if (POSIX_KEYS.containsKey(key)) {
111 value = Files.getAttribute(path, toFsAttributeKey(key));
112 } else {
113 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path,
114 UserDefinedFileAttributeView.class);
115 String prefixedName = NamespaceUtils.toPrefixedName(provider, key);
116 if (!udfav.list().contains(prefixedName))
117 return Optional.empty();
118 ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName));
119 udfav.read(prefixedName, buf);
120 buf.flip();
121 if (buf.hasArray())
122 value = buf.array();
123 else {
124 byte[] arr = new byte[buf.remaining()];
125 buf.get(arr);
126 value = arr;
127 }
128 }
129 } catch (IOException e) {
130 throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
131 }
132 A res = null;
133 if (value instanceof FileTime) {
134 if (clss.isAssignableFrom(FileTime.class))
135 res = (A) value;
136 Instant instant = ((FileTime) value).toInstant();
137 if (Object.class.isAssignableFrom(clss)) {// plain object requested
138 res = (A) instant;
139 }
140 // TODO perform trivial file conversion to other formats
141 }
142 if (value instanceof byte[]) {
143 res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
144 }
145 if (res == null)
146 try {
147 res = (A) value;
148 } catch (ClassCastException e) {
149 return Optional.empty();
150 }
151 return Optional.of(res);
152 }
153
154 @Override
155 protected Iterable<QName> keys() {
156 Set<QName> result = new HashSet<>(isPosix() ? POSIX_KEYS.keySet() : BASIC_KEYS.keySet());
157 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
158 if (udfav != null) {
159 try {
160 for (String name : udfav.list()) {
161 QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
162 QName sessionName = new ContentName(providerName, session);
163 result.add(sessionName);
164 }
165 } catch (IOException e) {
166 throw new ContentResourceException("Cannot list attributes for " + path, e);
167 }
168 }
169 return result;
170 }
171
172 @Override
173 protected void removeAttr(QName key) {
174 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
175 try {
176 udfav.delete(NamespaceUtils.toPrefixedName(provider, key));
177 } catch (IOException e) {
178 throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
179 }
180 }
181
182 @Override
183 public Object put(QName key, Object value) {
184 Object previous = get(key);
185 UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
186 ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
187 try {
188 udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
189 } catch (IOException e) {
190 throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
191 }
192 return previous;
193 }
194
195 protected String toFsAttributeKey(QName key) {
196 if (POSIX_KEYS.containsKey(key))
197 return POSIX_KEYS.get(key);
198 else
199 return USER_ + NamespaceUtils.toPrefixedName(provider, key);
200 }
201
202 /*
203 * CONTENT OPERATIONS
204 */
205 @Override
206 public Iterator<Content> iterator() {
207 if (Files.isDirectory(path)) {
208 try {
209 return Files.list(path).map((p) -> {
210 FsContent fsContent = new FsContent(this, p);
211 Optional<String> isMount = fsContent.get(CrName.MOUNT.get(), String.class);
212 if (isMount.orElse("false").equals("true")) {
213 QName[] classes = null;
214 ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent,
215 false, classes);
216 Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
217 return mountedContent;
218 } else {
219 return (Content) fsContent;
220 }
221 }).iterator();
222 } catch (IOException e) {
223 throw new ContentResourceException("Cannot list " + path, e);
224 }
225 } else {
226 return Collections.emptyIterator();
227 }
228 }
229
230 @Override
231 public Content add(QName name, QName... classes) {
232 FsContent fsContent;
233 try {
234 Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
235 if (ContentName.contains(classes, CrName.COLLECTION.get()))
236 Files.createDirectory(newPath);
237 else
238 Files.createFile(newPath);
239
240 // for(ContentClass clss:classes) {
241 // Files.setAttribute(newPath, name, newPath, null)
242 // }
243 fsContent = new FsContent(this, newPath);
244 } catch (IOException e) {
245 throw new ContentResourceException("Cannot create new content", e);
246 }
247
248 if (session.getRepository().shouldMount(classes)) {
249 ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent, true, classes);
250 Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
251 fsContent.put(CrName.MOUNT.get(), "true");
252 return mountedContent;
253
254 } else {
255 return fsContent;
256 }
257 }
258
259 @Override
260 public void remove() {
261 FsUtils.delete(path);
262 }
263
264 @Override
265 public Content getParent() {
266 if (isRoot) {
267 String mountPath = provider.getMountPath();
268 if (mountPath == null || mountPath.equals("/"))
269 return null;
270 String[] parent = ContentUtils.getParentPath(mountPath);
271 return session.get(parent[0]);
272 }
273 return new FsContent(this, path.getParent());
274 }
275
276 @SuppressWarnings("unchecked")
277 @Override
278 public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
279 if (InputStream.class.isAssignableFrom(clss)) {
280 if (Files.isDirectory(path))
281 throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
282 return (C) Files.newInputStream(path);
283 } else if (OutputStream.class.isAssignableFrom(clss)) {
284 if (Files.isDirectory(path))
285 throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
286 return (C) Files.newOutputStream(path);
287 }
288 return super.open(clss);
289 }
290
291 /*
292 * MOUNT MANAGEMENT
293 */
294 @Override
295 public ProvidedContent getMountPoint(String relativePath) {
296 Path childPath = path.resolve(relativePath);
297 // TODO check that it is a mount
298 return new FsContent(this, childPath);
299 }
300
301 /*
302 * ACCESSORS
303 */
304 @Override
305 public ProvidedSession getSession() {
306 return session;
307 }
308
309 @Override
310 public FsContentProvider getProvider() {
311 return provider;
312 }
313
314 }