1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.dam.app.action;
35
36 import info.magnolia.dam.jcr.AssetNodeTypes;
37 import info.magnolia.jcr.util.NodeNameHelper;
38 import info.magnolia.jcr.util.NodeTypes;
39 import info.magnolia.jcr.util.NodeUtil;
40 import info.magnolia.ui.CloseHandler;
41 import info.magnolia.ui.api.action.ActionExecutionException;
42 import info.magnolia.ui.contentapp.action.CloseAction;
43 import info.magnolia.ui.datasource.jcr.JcrDatasource;
44 import info.magnolia.ui.editor.JcrBinaryHelper;
45 import info.magnolia.ui.datasource.optionlist.Option;
46 import info.magnolia.ui.ValueContext;
47 import info.magnolia.ui.editor.EditorView;
48
49 import java.io.File;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.util.Collections;
53 import java.util.List;
54 import java.util.Optional;
55 import java.util.stream.Collectors;
56
57 import javax.inject.Inject;
58 import javax.jcr.Binary;
59 import javax.jcr.Node;
60 import javax.jcr.RepositoryException;
61
62 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
63 import org.apache.commons.compress.archivers.zip.ZipFile;
64 import org.apache.commons.io.FilenameUtils;
65 import org.apache.commons.lang3.StringUtils;
66 import org.devlib.schmidt.imageinfo.ImageInfo;
67
68 import com.vaadin.data.Binder;
69 import com.vaadin.data.BinderValidationStatus;
70 import com.vaadin.data.HasValue;
71
72
73
74
75 public class UploadAssetsAction extends CloseAction<UploadAssetsActionDefinition> implements JcrBinaryHelper {
76
77 private static final String BACKSLASH_DUMMY = "________backslash________";
78
79 private final EditorView<Node> form;
80 private final ValueContext<Node> valueContext;
81 private final NodeNameHelper nodeNameHelper;
82 private final JcrDatasource jcrDatasource;
83
84 @Inject
85 public UploadAssetsAction(UploadAssetsActionDefinition definition, CloseHandler closeHandler,
86 EditorView<Node> form, ValueContext<Node> valueContext,
87 NodeNameHelper nodeNameHelper, JcrDatasource jcrDatasource) {
88 super(definition, closeHandler);
89 this.form = form;
90 this.valueContext = valueContext;
91 this.nodeNameHelper = nodeNameHelper;
92 this.jcrDatasource = jcrDatasource;
93 }
94
95 @Override
96 public void execute() throws ActionExecutionException {
97 Optional<File> file = getFile();
98 if (file.isPresent()) {
99 try {
100 ZipFile zip = new ZipFile(file.get(), getEncoding());
101
102
103
104
105 final List zipEntries = Collections.list(zip.getEntries()).stream()
106 .sorted((firstEntry, secondEntry) -> {
107 if (firstEntry.isDirectory() != secondEntry.isDirectory()) {
108 return Boolean.compare(secondEntry.isDirectory(), firstEntry.isDirectory());
109 } else {
110 return firstEntry.getName().compareTo(secondEntry.getName());
111 }
112 })
113 .collect(Collectors.toList());
114
115 for (Object zipEntry : zipEntries) {
116 ZipArchiveEntry entry = (ZipArchiveEntry) zipEntry;
117 processEntry(zip, entry);
118 }
119
120 getFolderNode().getSession().save();
121 } catch (IOException | RepositoryException e) {
122 throw new ActionExecutionException(e);
123 }
124 super.execute();
125 }
126 }
127
128 private void processEntry(org.apache.commons.compress.archivers.zip.ZipFile zip, ZipArchiveEntry entry) throws IOException, RepositoryException {
129 if (entry.getName().startsWith("__MACOSX")) {
130 return;
131 } else if (entry.getName().endsWith(".DS_Store")) {
132 return;
133 }
134 if (entry.isDirectory()) {
135 ensureFolder(entry);
136 } else {
137 handleFileEntry(zip, entry);
138 }
139 }
140
141 private void handleFileEntry(ZipFile zip, ZipArchiveEntry entry) throws IOException, RepositoryException {
142 String fileName = entry.getName();
143 if (StringUtils.contains(fileName, "/")) {
144 fileName = StringUtils.substringAfterLast(fileName, "/");
145 }
146 try (InputStream stream = zip.getInputStream(entry);
147 InputStream streamForMetaDataDetection = zip.getInputStream(entry);
148 InputStream streamForMimeTypeDetection = zip.getInputStream(entry)
149 ) {
150 String folderPath = StringUtils.strip(extractEntryPath(entry), "/");
151 Node folder = getFolderNode();
152 if (StringUtils.isNotBlank(folderPath)) {
153 folder = NodeUtil.createPath(folder, folderPath, NodeTypes.Folder.NAME);
154 }
155 Node asset = folder.addNode(
156 nodeNameHelper.getUniqueName(folder, nodeNameHelper.getValidatedName(fileName)),
157 AssetNodeTypes.Asset.NAME);
158 asset.setProperty(AssetNodeTypes.Asset.ASSET_NAME, FilenameUtils.getBaseName(fileName));
159 Node node = asset.addNode(AssetNodeTypes.AssetResource.RESOURCE_NAME, AssetNodeTypes.AssetResource.NAME);
160 Binary binary = node.getSession().getValueFactory().createBinary(stream);
161 node.setProperty(AssetNodeTypes.AssetResource.DATA, binary);
162 node.setProperty(AssetNodeTypes.AssetResource.FILENAME, fileName);
163 node.setProperty(AssetNodeTypes.AssetResource.EXTENSION, FilenameUtils.getExtension(fileName));
164 node.setProperty(AssetNodeTypes.AssetResource.SIZE, binary.getSize());
165 node.setProperty(AssetNodeTypes.AssetResource.MIMETYPE, TIKA.detect(streamForMimeTypeDetection, fileName));
166 ImageInfo imageInfo = new ImageInfo();
167 imageInfo.setInput(streamForMetaDataDetection);
168 imageInfo.check();
169 node.setProperty(AssetNodeTypes.AssetResource.HEIGHT, imageInfo.getHeight());
170 node.setProperty(AssetNodeTypes.AssetResource.WIDTH, imageInfo.getWidth());
171 }
172 }
173
174 private Node getFolderNode() {
175 return valueContext.getSingle().orElse(this.jcrDatasource.getRoot());
176 }
177
178 private void ensureFolder(ZipArchiveEntry entry) throws RepositoryException {
179 String path = extractEntryPath(entry);
180 NodeUtil.createPath(getFolderNode(), path, NodeTypes.Folder.NAME);
181 }
182
183 private String extractEntryPath(ZipArchiveEntry entry) {
184 String entryName = entry.getName();
185 String path = (StringUtils.contains(entryName, "/")) ? StringUtils.substringBeforeLast(entryName, "/") : "/";
186 if (!path.startsWith("/")) {
187 path = "/" + path;
188 }
189
190 path = StringUtils.replace(path, "/", BACKSLASH_DUMMY);
191 path = nodeNameHelper.getValidatedName(path);
192 path = StringUtils.replace(path, BACKSLASH_DUMMY, "/");
193 return path;
194 }
195
196 private Optional<File> getFile() {
197 return form.validate().stream()
198 .filter(BinderValidationStatus::isOk)
199 .map(BinderValidationStatus::getBinder)
200 .flatMap(Binder::getFields)
201 .map(HasValue::getValue)
202 .filter(File.class::isInstance)
203 .map(File.class::cast)
204 .findFirst();
205 }
206
207 private String getEncoding() {
208 return form.validate().stream()
209 .map(BinderValidationStatus::getBinder)
210 .flatMap(Binder::getFields)
211 .map(HasValue::getValue)
212 .filter(Option.class::isInstance)
213 .map(Option.class::cast)
214 .map(Option::getValue)
215 .findFirst()
216 .orElse(null);
217 }
218 }