OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 20 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
18 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.erp.modules;
15
 
73 ilm 16
import org.openconcerto.utils.FileUtils;
18 ilm 17
import org.openconcerto.utils.StreamUtils;
18
 
19
import java.io.BufferedInputStream;
20
import java.io.BufferedOutputStream;
21
import java.io.File;
19 ilm 22
import java.io.FileFilter;
18 ilm 23
import java.io.FileInputStream;
24
import java.io.FileOutputStream;
25
import java.io.IOException;
26
import java.io.InputStream;
27
import java.util.ArrayList;
19 ilm 28
import java.util.Enumeration;
20 ilm 29
import java.util.HashSet;
18 ilm 30
import java.util.List;
20 ilm 31
import java.util.Set;
18 ilm 32
import java.util.jar.JarEntry;
19 ilm 33
import java.util.jar.JarFile;
18 ilm 34
import java.util.jar.JarOutputStream;
19 ilm 35
import java.util.zip.ZipFile;
18 ilm 36
 
73 ilm 37
import net.jcip.annotations.ThreadSafe;
38
 
18 ilm 39
/**
40
 * Package a module from its properties and classes.
41
 *
42
 * @author Sylvain CUAZ
43
 */
44
public class ModulePackager {
45
    public static final String MODULE_PROPERTIES_PATH = "META-INF/openConcertoModule.properties";
46
 
47
    private final List<File> classesDirs;
19 ilm 48
    private final List<File> jars = new ArrayList<File>();
18 ilm 49
    private final File propsFile;
20 ilm 50
    private final Set<String> dirEntries, fileEntries;
51
    private boolean skipDuplicateFiles;
18 ilm 52
 
53
    public ModulePackager(final File propsFile, final File classes) {
54
        this.propsFile = propsFile;
55
        this.classesDirs = new ArrayList<File>(8);
56
        this.classesDirs.add(classes);
20 ilm 57
        this.dirEntries = new HashSet<String>();
58
        this.fileEntries = new HashSet<String>();
59
        this.setSkipDuplicateFiles(false);
18 ilm 60
    }
61
 
20 ilm 62
    public final void setSkipDuplicateFiles(boolean skipDuplicateFiles) {
63
        this.skipDuplicateFiles = skipDuplicateFiles;
64
    }
65
 
66
    public final boolean getSkipDuplicateFiles() {
67
        return this.skipDuplicateFiles;
68
    }
69
 
18 ilm 70
    public final void addDir(final File classesDir) {
71
        this.classesDirs.add(classesDir);
72
    }
73
 
19 ilm 74
    public final void addJarsFromDir(final File dir) {
75
        if (!dir.exists())
76
            throw new IllegalArgumentException("Directory " + dir.getAbsolutePath() + " not found");
77
        final File[] jarFiles = dir.listFiles(new FileFilter() {
78
            @Override
79
            public boolean accept(File f) {
80
                return f.isFile() && f.getName().endsWith(".jar");
81
            }
82
        });
83
        for (final File jarFile : jarFiles) {
84
            addJarUnsafe(jarFile);
85
        }
86
    }
87
 
88
    public final void addJar(final File jarFile) {
89
        if (!jarFile.isFile())
90
            throw new IllegalArgumentException("File " + jarFile.getAbsolutePath() + " not found");
91
        if (!jarFile.getName().endsWith(".jar"))
92
            throw new IllegalArgumentException("File " + jarFile.getAbsolutePath() + " is not a jar file");
93
        addJarUnsafe(jarFile);
94
    }
95
 
96
    private final void addJarUnsafe(File jarFile) {
97
        this.jars.add(jarFile);
98
    }
99
 
18 ilm 100
    /**
101
     * Write the package to the passed directory.
102
     *
103
     * @param dir where to create the package (its name will contain its id and version).
19 ilm 104
     * @return the created jar.
18 ilm 105
     * @throws IOException if the package cannot be created.
106
     */
19 ilm 107
    public final File writeToDir(final File dir) throws IOException {
18 ilm 108
        final RuntimeModuleFactory f = new RuntimeModuleFactory(this.propsFile);
109
        final File jarFile = new File(dir, f.getID() + "-" + f.getMajorVersion() + "." + f.getMinorVersion() + ".jar");
110
        final JarOutputStream jarStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile)));
20 ilm 111
        this.dirEntries.clear();
112
        this.fileEntries.clear();
18 ilm 113
        try {
114
            if (!this.zipExistingFile(jarStream, this.propsFile, MODULE_PROPERTIES_PATH))
115
                throw new IllegalStateException("Missing properties file : " + this.propsFile);
19 ilm 116
            for (File jarFileToAdd : this.jars) {
117
                this.zipJarFile(jarStream, jarFileToAdd);
118
            }
119
            for (final File classesDir : this.classesDirs) {
18 ilm 120
                this.zipBelow(jarStream, classesDir);
19 ilm 121
            }
18 ilm 122
        } finally {
123
            jarStream.close();
20 ilm 124
            this.dirEntries.clear();
125
            this.fileEntries.clear();
18 ilm 126
        }
19 ilm 127
        return jarFile;
18 ilm 128
    }
129
 
130
    // doesn't zip f
131
    protected void zipBelow(JarOutputStream jarStream, File f) throws IOException {
132
        this.zipRec(jarStream, f, "");
133
    }
134
 
135
    private String getPath(File f) throws IOException {
136
        String res = f.getAbsolutePath();
137
        if (f.isDirectory() && !res.endsWith("/"))
138
            res += '/';
139
        return res;
140
    }
141
 
142
    private String getEntryName(File f, File base) throws IOException {
143
        // needed otherwise we could pass ('abc', 'a')
144
        // but if base is a directory then its path is 'a/'
145
        if (!base.isDirectory())
146
            throw new IllegalArgumentException("Base not a directory : " + base);
147
        final String fPath = getPath(f);
148
        final String basePath = getPath(base);
149
        if (!fPath.startsWith(basePath))
150
            throw new IllegalArgumentException("Base is not a parent :\n" + base + "\n" + f);
151
        return fPath.substring(basePath.length()).replace('\\', '/');
152
    }
153
 
154
    protected void zipRec(JarOutputStream jarStream, File f, File base) throws IOException {
155
        zipRec(jarStream, f, getEntryName(f, base));
156
    }
157
 
158
    protected void zipRec(JarOutputStream jarStream, File f, final String entryName) throws IOException {
159
        if (entryName.length() > 0)
160
            this.zipExistingFile(jarStream, f, entryName);
161
        if (f.isDirectory())
162
            for (final File child : f.listFiles()) {
163
                this.zipRec(jarStream, child, entryName + '/' + child.getName());
164
            }
165
    }
166
 
167
    protected boolean zipExistingFile(JarOutputStream jarStream, File f, File base) throws IOException {
168
        return this.zipExistingFile(jarStream, f, getEntryName(f, base));
169
    }
170
 
171
    protected boolean zipExistingFile(JarOutputStream jarStream, File f, final String entryName) throws IOException {
172
        if (!f.exists())
173
            return false;
174
        final boolean isDir = f.isDirectory();
175
 
176
        String name = entryName;
177
        if (name.startsWith("/"))
178
            name = name.substring(1);
179
        if (isDir && !name.endsWith("/"))
180
            name += "/";
181
        final JarEntry entry = new JarEntry(name);
182
        entry.setTime(f.lastModified());
183
        if (!isDir)
184
            entry.setSize(f.length());
185
 
186
        final InputStream in = isDir ? null : new BufferedInputStream(new FileInputStream(f));
187
        try {
188
            zip(jarStream, entry, in);
189
        } finally {
190
            if (in != null)
191
                in.close();
192
        }
193
        return true;
194
    }
195
 
196
    private void zip(JarOutputStream jarStream, final JarEntry entry, InputStream in) throws IOException {
20 ilm 197
        // ignore duplicate directories
198
        final boolean isDir = entry.isDirectory();
199
        if (isDir && !this.dirEntries.add(entry.getName()))
200
            return;
201
        if (!isDir && this.getSkipDuplicateFiles() && !this.fileEntries.add(entry.getName()))
202
            return;
203
 
18 ilm 204
        jarStream.putNextEntry(entry);
205
        if (in != null) {
206
            StreamUtils.copy(in, jarStream);
207
        }
208
        jarStream.closeEntry();
209
    }
19 ilm 210
 
211
    private void zipJarFile(JarOutputStream out, File file) throws IOException {
212
        final JarFile f = new JarFile(file, false, ZipFile.OPEN_READ);
213
        try {
214
            final Enumeration<JarEntry> entries = f.entries();
215
            while (entries.hasMoreElements()) {
216
                final JarEntry e = entries.nextElement();
217
                if (!e.getName().startsWith("META-INF")) {
218
                    // use copy-constructor to keep all fields
219
                    zip(out, new JarEntry(e), f.getInputStream(e));
220
                }
221
            }
222
        } finally {
223
            f.close();
224
        }
225
    }
73 ilm 226
 
227
    @ThreadSafe
228
    public static class ModuleFiles {
229
        private final File baseDir;
230
        private final File propsFile;
231
 
232
        public ModuleFiles(final File baseDir, final String propsFileName) {
233
            this.baseDir = baseDir;
234
            this.propsFile = new File(this.baseDir, propsFileName == null ? "module.properties" : propsFileName);
235
        }
236
 
237
        public final File getBaseDir() {
238
            return this.baseDir;
239
        }
240
 
241
        public final File getPropertiesFile() {
242
            return this.propsFile;
243
        }
244
 
245
        public File getClassesDir() {
246
            return new File(this.getBaseDir(), "bin");
247
        }
248
 
249
        public File getLibrariesDir() {
250
            return new File(this.getBaseDir(), "lib");
251
        }
252
 
253
        public File getDistDir() {
254
            return new File(this.getBaseDir(), "dist");
255
        }
256
    }
257
 
258
    public static File createDist(final File moduleDir) throws IOException {
259
        return createDist(new ModuleFiles(moduleDir, null));
260
    }
261
 
262
    public static File createDist(final ModuleFiles files) throws IOException {
263
        final File distDir = files.getDistDir();
264
        FileUtils.mkdir_p(distDir);
265
        final ModulePackager pkger = new ModulePackager(files.getPropertiesFile(), files.getClassesDir());
266
        final File libDir = files.getLibrariesDir();
267
        if (libDir.exists()) {
268
            pkger.setSkipDuplicateFiles(true);
269
            pkger.addJarsFromDir(libDir);
270
        }
271
        return pkger.writeToDir(distDir);
272
    }
273
 
274
    public static void main(String[] args) throws IOException {
275
        if (args.length != 1) {
276
            System.err.println("Usage : " + ModulePackager.class.getName() + " moduleBaseDir");
277
            System.exit(1);
278
        }
279
        final File jar = createDist(ModuleLauncher.createModuleFiles(args[0]));
280
        System.out.println("Created " + jar.getCanonicalPath());
281
    }
18 ilm 282
}