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