001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.core.metamodel.services.devutils;
020
021import java.io.ByteArrayOutputStream;
022import java.io.File;
023import java.io.IOException;
024import java.io.OutputStreamWriter;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.List;
028import java.util.zip.ZipEntry;
029import java.util.zip.ZipOutputStream;
030
031import javax.activation.MimeType;
032import javax.activation.MimeTypeParseException;
033
034import com.google.common.base.Predicate;
035import com.google.common.collect.Collections2;
036import com.google.common.collect.Lists;
037
038import org.apache.isis.applib.FatalException;
039import org.apache.isis.applib.RecoverableException;
040import org.apache.isis.applib.annotation.Programmatic;
041import org.apache.isis.applib.services.devutils.DeveloperUtilitiesService;
042import org.apache.isis.applib.value.Blob;
043import org.apache.isis.applib.value.Clob;
044import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
045import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
046import org.apache.isis.core.metamodel.adapter.mgr.AdapterManagerAware;
047import org.apache.isis.core.metamodel.layoutmetadata.json.LayoutMetadataReaderFromJson;
048import org.apache.isis.core.metamodel.spec.ObjectSpecification;
049import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
050import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpiAware;
051import org.apache.isis.core.metamodel.spec.feature.Contributed;
052import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
053import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
054import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
055import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
056
057public class DeveloperUtilitiesServiceDefault implements DeveloperUtilitiesService, SpecificationLoaderSpiAware, AdapterManagerAware {
058
059
060    private final MimeType mimeTypeTextCsv;
061    private final MimeType mimeTypeApplicationZip;
062    private final MimeType mimeTypeApplicationJson;
063
064    public DeveloperUtilitiesServiceDefault() {
065        try {
066            mimeTypeTextCsv = new MimeType("text", "csv");
067            mimeTypeApplicationJson = new MimeType("application", "jzon");
068            mimeTypeApplicationZip = new MimeType("application", "zip");
069        } catch (MimeTypeParseException e) {
070            throw new RuntimeException(e);
071        }
072    }
073
074    // //////////////////////////////////////
075
076    
077    @Override
078    public Clob downloadMetaModel() {
079
080        final Collection<ObjectSpecification> specifications = specificationLoader.allSpecifications();
081
082        final List<MetaModelRow> rows = Lists.newArrayList();
083        for (ObjectSpecification spec : specifications) {
084            if (exclude(spec)) {
085                continue;
086            }
087            final List<ObjectAssociation> properties = spec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.PROPERTIES);
088            for (ObjectAssociation property : properties) {
089                final OneToOneAssociation otoa = (OneToOneAssociation) property;
090                if (exclude(otoa)) {
091                    continue;
092                }
093                rows.add(new MetaModelRow(spec, otoa));
094            }
095            final List<ObjectAssociation> associations = spec.getAssociations(Contributed.EXCLUDED, ObjectAssociation.Filters.COLLECTIONS);
096            for (ObjectAssociation collection : associations) {
097                final OneToManyAssociation otma = (OneToManyAssociation) collection;
098                if (exclude(otma)) {
099                    continue;
100                }
101                rows.add(new MetaModelRow(spec, otma));
102            }
103            final List<ObjectAction> actions = spec.getObjectActions(Contributed.INCLUDED);
104            for (ObjectAction action : actions) {
105                if (exclude(action)) {
106                    continue;
107                }
108                rows.add(new MetaModelRow(spec, action));
109            }
110        }
111
112        Collections.sort(rows);
113
114        final StringBuilder buf = new StringBuilder();
115        buf.append(MetaModelRow.header()).append("\n");
116        for (MetaModelRow row : rows) {
117            buf.append(row.asTextCsv()).append("\n");
118        }
119        return new Clob("metamodel.csv", mimeTypeTextCsv, buf.toString().toCharArray());
120    }
121
122    protected boolean exclude(OneToOneAssociation property) {
123        return false;
124    }
125
126    protected boolean exclude(OneToManyAssociation collection) {
127        return false;
128    }
129
130    protected boolean exclude(ObjectAction action) {
131        return false;
132    }
133
134    protected boolean exclude(ObjectSpecification spec) {
135        return isBuiltIn(spec) || spec.isAbstract();
136    }
137
138    protected boolean isBuiltIn(ObjectSpecification spec) {
139        final String className = spec.getFullIdentifier();
140        return className.startsWith("java") || className.startsWith("org.joda");
141    }
142
143    // //////////////////////////////////////
144    
145    @Override
146    public void refreshServices() {
147        Collection<ObjectSpecification> specifications = Lists.newArrayList(specificationLoader.allSpecifications());
148        for (ObjectSpecification objectSpec : specifications) {
149            if(objectSpec.isService()){
150                specificationLoader.invalidateCache(objectSpec.getCorrespondingClass());
151            }
152        }
153    }
154
155    // //////////////////////////////////////
156
157    @Override
158    public Object refreshLayout(Object domainObject) {
159        specificationLoader.invalidateCacheFor(domainObject);
160        return domainObject;
161    }
162
163    // //////////////////////////////////////
164    
165    @Override
166    public Clob downloadLayout(Object domainObject) {
167        
168        final ObjectAdapter adapterFor = adapterManager.adapterFor(domainObject);
169        final ObjectSpecification objectSpec = adapterFor.getSpecification();
170        
171        final LayoutMetadataReaderFromJson propertiesReader = new LayoutMetadataReaderFromJson();
172        final String json = propertiesReader.asJson(objectSpec);
173        
174        return new Clob(objectSpec.getShortIdentifier() +".layout.json", mimeTypeApplicationJson, json);
175    }
176
177    // //////////////////////////////////////
178
179    @Override
180    public Blob downloadLayouts() {
181        final LayoutMetadataReaderFromJson propertiesReader = new LayoutMetadataReaderFromJson();
182        final Collection<ObjectSpecification> allSpecs = specificationLoader.allSpecifications();
183        final Collection<ObjectSpecification> domainObjectSpecs = Collections2.filter(allSpecs, new Predicate<ObjectSpecification>(){
184            @Override
185            public boolean apply(ObjectSpecification input) {
186                return  !input.isAbstract() && 
187                        !input.isService() && 
188                        !input.isValue() && 
189                        !input.isParentedOrFreeCollection();
190            }});
191        try {
192            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
193            ZipOutputStream zos = new ZipOutputStream(baos);
194            OutputStreamWriter writer = new OutputStreamWriter(zos);
195            for (ObjectSpecification objectSpec : domainObjectSpecs) {
196                zos.putNextEntry(new ZipEntry(zipEntryNameFor(objectSpec)));
197                writer.write(propertiesReader.asJson(objectSpec));
198                writer.flush();
199                zos.closeEntry();
200            }
201            writer.close();
202            return new Blob("layouts.zip", mimeTypeApplicationZip, baos.toByteArray());
203        } catch (final IOException ex) {
204            throw new FatalException("Unable to create zip of layouts", ex);
205        }
206    }
207
208    private static String zipEntryNameFor(ObjectSpecification objectSpec) {
209        final String fqn = objectSpec.getFullIdentifier();
210        return fqn.replace(".", File.separator)+".layout.json";
211    }
212
213
214    // //////////////////////////////////////
215
216    private SpecificationLoaderSpi specificationLoader;
217    private AdapterManager adapterManager;
218
219    @Programmatic
220    @Override
221    public void setSpecificationLoaderSpi(SpecificationLoaderSpi specificationLoader) {
222        this.specificationLoader = specificationLoader;
223    }
224
225    @Override
226    public void setAdapterManager(AdapterManager adapterManager) {
227        this.adapterManager = adapterManager;
228    }
229
230
231}